{"id":"023f0709-5986-4891-82f1-b9e04ceda48b","slug":"textwren","kind":"mcp","name":"TextWren","summary":"Translate, fix grammar and dictate in any macOS app. ⌘C twice for a popup, fn⌃ to talk; cloud models or fully on-device.","owner":"itoropitsin","repoUrl":"https://github.com/itoropitsin/TextWren","license":"MIT","licenseTier":"permissive","trust":"community","stars":1,"match":null,"fewStars":true,"format":"mcp","createdAt":"2025-12-15T15:45:01.000Z","addedAt":"2026-10-21T08:11:59.496Z","logoUrl":"https://avatars.githubusercontent.com/u/46603598?u=c0ee3ff396acc4ed69f245fce0287b9bfc324444&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["writing","design-media"],"tags":["dictation","gemini","grammar","llama-cpp","llm","macos","on-device-ai","openai"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-27T01:16:38.100Z","sample":false,"url":"https://conduits.dev/tools/textwren","language":"Swift","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"<p align=\"center\">\n  <img src=\"docs/icon.png\" alt=\"TextWren icon: an origami wren on a deep blue tile\" width=\"128\">\n</p>\n<h1 align=\"center\">TextWren</h1>\n<p align=\"center\">\n  Translate, fix grammar and dictate in any macOS app.<br>\n  Cloud models or fully on-device.\n</p>\n<p align=\"center\">\n  <a href=\"https://github.com/itoropitsin/TextWren/releases/latest\">Download</a> ·\n  <a href=\"#install\">Install</a> ·\n  <a href=\"#use\">Use</a> ·\n  <a href=\"LICENSE\">MIT License</a>\n</p>\n\nTextWren is a small macOS app for everyday writing. It translates, fixes grammar, and runs your own\nprompts on any text, either in its own window or right where you are typing.\n\n<p align=\"center\">\n  <img src=\"docs/popup.png\" alt=\"TextWren popup over a web page: the selected text translated into Japanese with formatting kept, and a Grammar result, each with Replace\" width=\"420\">\n</p>\n<p align=\"center\">\n  <img src=\"docs/main-window.png\" alt=\"TextWren main window: formatted English source with links, its Japanese translation and the Grammar result\" width=\"720\">\n</p>\n<p align=\"center\">\n  <img src=\"docs/dictation.png\" alt=\"Dictation popup under the menu bar icon, showing live text while listening\" width=\"420\">\n</p>\n\n- **Popup in any app.** Select text, press **⌘C twice**, get the result, then **Replace** the\n  selection or **Copy** it.\n- **Main window** for longer text, with results that update as you type.\n- **Formatting is kept.** Lists, links, bold, italic and code survive the round trip to the model\n  and back, including when you paste into Slack, Notes or Google Docs.\n- **Up to five custom actions** such as Grammar, Summarize or \"Create task\", each with its own\n  model and reasoning level.\n- **Voice dictation.** Hold a shortcut and speak into any app, with a local speech model (audio\n  stays on your Mac) or OpenAI. A menu bar icon and popup show what is happening.\n- **Agents and live conversation (Beta).** Ask remote agents (HTTP APIs or MCP servers) by voice\n  and hear the answer, or talk live with GPT-Live-1 while it uses your tools.\n\n## Requirements\n\n- macOS 14.6 or later (Apple Silicon recommended for the on-device models)\n- An OpenAI or Google Gemini API key, or none at all: the on-device text and speech models run\n  without a key\n\n## Install\n\n1. Download `TextWren.zip` from the [latest release](https://github.com/itoropitsin/TextWren/releases/latest)\n   and open it.\n2. Move `TextWren.app` to **Applications**.\n3. The app is ad hoc signed, not notarized. If macOS blocks the first launch, Control-click\n   `TextWren.app` and choose **Open**, or use **System Settings → Privacy & Security → Open Anyway**.\n\nTextWren was called **TinyAI** before version 2.1.0. Updating keeps your settings, API keys and\ndownloaded models. After installing `TextWren.app`, delete the old `TinyAI.app`; if Accessibility\ndoes not work, remove the old entry in System Settings and add `TextWren.app`.\n\n### Build from source\n\nOpen `TinyAI.xcodeproj` in Xcode and run the `TinyAI` scheme (the app target keeps its original name). The build phase downloads the pinned\ntranscribe.cpp and llama.cpp frameworks into `Vendor/`. To compile the release app from the command\nline without installing it, run:\n\n```bash\nzsh scripts/build_and_install.sh --build-only\n```\n\nWithout `--build-only` the script signs the app, installs it into `/Applications`, replaces an\nolder version and relaunches it. It needs one of the maintainer's signing identities, described\nbelow.\n\nFor the maintainer's Mac, the script uses the `TinyAI Local Code Signing` identity in the login Keychain.\nIt pins that certificate's SHA-1 fingerprint in the script so a different certificate with the\nsame name cannot silently sign an update. If that identity is unavailable, the script can use an\nApple Development certificate for team `Y29LYS5D8M`. The script refuses to install a version\nthat is not newer than the installed one. Bump `MARKETING_VERSION` and\n`CURRENT_PROJECT_VERSION` in Xcode and the matching `version` and `build_number` at the top of the\nscript together. Use `--build-only` to compile without installing. For an ad hoc signed\nrelease archive without a certificate, run `zsh scripts/sign_release_ad_hoc.sh /path/to/TextWren.app`\nafter building and before creating the ZIP. Ad hoc signatures can change between builds, so macOS\nmay require Accessibility to be granted again after an update.\n\nThe local identity is for this Mac only. Keep its private key in the login Keychain, and back up\nthe certificate and private key securely before migrating to another Mac. Creating a new local\ncertificate changes the app's signing identity. Local signatures cannot be notarized or used for\npublic distribution. macOS still requires each permission to be granted initially; stable signing\nonly gives later updates a consistent code identity, and permission retention should be checked\non the target Mac after updating.\nThe local build uses a Library Validation runtime exception to load its bundled CTranscribe\nframework because a self-signed certificate has no Apple Team ID. The framework is still signed\nwith the same local certificate and verified before installation. Apple Development builds use\nthe standard entitlements without this exception.\n\n## Set up\n\n1. Open **Settings → API** and paste an OpenAI and/or Gemini key. Keys are checked before they are\n   saved. To stay fully offline, download the on-device models there instead.\n2. In **Settings → Primary**, choose what the two result panels show: the built-in Translate or\n   one of your actions.\n3. When macOS asks, allow **Accessibility** for TextWren (System Settings → Privacy & Security).\n   The popup hotkey, voice shortcuts and Replace need it. If a previously enabled entry does not\n   work after an update, remove it, add `/Applications/TextWren.app`,\n   enable it and relaunch.\n4. For voice features, allow **Microphone** access when asked (or in **Settings → Voice**).\n\nSettings are applied when you press **Save**; **Cancel** discards the changes.\n\n## Use\n\n### Popup\n\n- Select text in any app and press **⌘C twice**. The hotkey and single/double press mode can be\n  changed in **Settings → Hotkeys**.\n- **Replace** returns to the original app and pastes the result over the selection. It does not\n  paste if that app is no longer in front.\n- **Copy** puts the result on the clipboard with formatting.\n- Run other actions with the buttons or **⌘1 … ⌘5**.\n\n### Main window\n\n- Paste or type into **Source text**. A new request starts shortly after you stop typing and\n  replaces any request still running.\n- Choose a target language, or **Auto** to translate between your main and additional languages\n  (set in **Settings → Primary**).\n\n### Dictation\n\nBy default, hold **fn⌃** (fn and Control together) and speak, or tap them to start and tap again to\nstop; **Esc** cancels. The text is pasted where the cursor was, and your clipboard is put back\nafterwards. If no text field has focus, the text appears in the popup under the menu bar icon with a\n**Copy** button. A shortcut can be modifiers alone, like fn⌃, or modifiers with a key, like ⌃V.\nShortcuts with a key use the physical key, so they work in any keyboard layout (⌃V and ⌃М are the\nsame shortcut). Existing saved shortcuts are preserved. Change the shortcut and engine in **Settings → Voice**:\n\n| Engine | Model | Good for |\n| --- | --- | --- |\n| Local | [Voxtral Mini 4B Realtime](https://huggingface.co/handy-computer/Voxtral-Mini-4B-Realtime-2602-gguf) | Live text, 13 languages. Examples: English, Mandarin, Hindi, Spanish, Arabic. Model weights need about 2.8 GB RAM, plus runtime memory. |\n| Local | [Nemotron Streaming 3.5](https://huggingface.co/handy-computer/nemotron-3.5-asr-streaming-0.6b-gguf) | Fast live text, 28 languages in TextWren. Examples: English, Mandarin, Hindi, Spanish, Arabic. Model weights need about 750 MB RAM, plus runtime memory. |\n| Local | [Canary 180M Flash](https://huggingface.co/handy-computer/canary-180m-flash-gguf) | Small offline model. Its four languages are English, Spanish, French and German. Model weights need about 220 MB RAM, plus runtime memory. |\n| OpenAI | GPT Live Transcribe (default) | Streams while you talk. [OpenAI list price](https://developers.openai.com/api/docs/pricing): $0.017/minute. |\n| OpenAI | GPT Transcribe | Transcribes after recording stops. [OpenAI list price](https://developers.openai.com/api/docs/pricing): $0.0045/minute. |\n\nWhen a new installation has neither an OpenAI API key nor a downloaded local model, TextWren offers\nto download Nemotron for local dictation. Download and RAM figures are approximate; actual memory\nuse depends on the runtime and recording length.\n\nLocal models are the ones Handy uses, run with transcribe.cpp on the Mac's GPU. Download them in\n**Settings → Voice** or **Settings → API**; each file is checked against a pinned SHA-256 and stored in\n`~/Library/Application Support/TinyAI/Models` (the folder keeps the app's original name). Audio for local models never leaves the Mac.\n\n### Agents (Beta)\n\nAgents and live conversation are a beta. Turn them on in **Help → Agents and live conversation\n(Beta)**; this adds the **Agents** and **Live** tabs to Settings and their shortcuts to the menu\nbar.\n\n**Settings → Agents** has two lists:\n\n- **Connections**: an **HTTP API** (URL, method, JSON body template, and the path to the answer\n  in the response) or an **MCP server** (Streamable HTTP). Authorization is either a secret\n  header stored in the Keychain, or, for MCP servers, **Sign in with browser**: OAuth in your\n  default browser with automatic client registration. **Test & load tools** lists the server's\n  tools.\n- **Agents**: each has its own voice shortcut and a connection (for MCP, the tool and a JSON\n  arguments template). Templates can use `{{transcript}}`, `{{sessionId}}`, `{{language}}` and\n  `{{agent}}`. Follow-ups within the configured minutes reuse the same `{{sessionId}}`.\n\nHold an agent's shortcut and ask. The answer can appear in a floating panel, be pasted at the\ncursor, be copied, and be spoken with an OpenAI voice (with a style prompt) or a macOS voice.\nRequests wait up to the connection's timeout, so agents can think before answering.\n\n### Live conversation (Beta)\n\n**Settings → Live** sets a shortcut that starts and ends a full-duplex conversation with\n**GPT-Live-1**. It keeps talking while its backend model (for example GPT-6 Luna) reasons and\ncalls tools: web search, the tools of the MCP servers you pick, and your agents. Tool calls run\non the Mac with each connection's sign-in. The transcript can be shown in a panel.\n\n### Menu bar\n\nThe menu bar icon shows the state: ready, recording (red, pulsing), transcribing, an agent\nthinking, speaking, live, or an error (orange). While you record, a popup under the icon shows the\nmicrophone level and the text as it is recognised; errors appear there too.\n\nThe menu starts dictation (and, with the beta on, agents or a live conversation), cancels the\ncurrent one, lists the **five most recent transcripts** (click one to copy it), shows the last\nagent answer and the model download progress.\n\n### Custom actions\n\nCreate up to five actions in **Settings → Actions**: a title, a prompt, a model and a reasoning\nlevel. The text you select is treated as input for the prompt: requests or questions inside it are\nprocessed, not answered.\n\n- `{{targetLanguage}}` in a prompt is replaced with the language chosen in the menu.\n- **Settings → Style** adds shared tone or terminology to the actions you pick.\n\nExample prompts:\n\n| Action | Prompt |\n| --- | --- |\n| Summary | Summarize in 3 bullet points, under 60 words. |\n| Polite email | Rewrite as a polite, concise email. Keep names, dates and action items. |\n| Shorter | Make the text about half as long without losing meaning. |\n\n## Models\n\nTextWren supports a fixed set of models, each with the reasoning levels its provider accepts.\n\n| Provider | Models |\n| --- | --- |\n| OpenAI | GPT-6 Luna, GPT-6 Sol, GPT-6 Astra, GPT-5.6 Luna, GPT-5.6 Terra, GPT-5.6 Sol, GPT-5.5 |\n| Google Gemini | Gemini 3.8 Flash, Gemini 3.5 Flash-Lite, Gemini 3.1 Pro |\n| On-device | Qwen3.5 4B (2.7 GB download, about 3 GB RAM while loaded) |\n\nThe default is **GPT-6 Luna** with **Low** reasoning. In testing it was as accurate as higher\nlevels for translation and grammar, and faster. Use Medium or High for explanations and harder\ntasks. Settings that refer to a model that is no longer supported switch to the default.\n\n**Qwen3.5 4B** runs on this Mac with llama.cpp, needs no API key, and the text never leaves the\ndevice. Download it in Settings → API, then pick it for translation or any action. In a blind\ncomparison with GPT-6 Luna (Low) it scored 4.7 vs 4.8 of 5 for grammar but 3.8 vs 4.9 for\ntranslation. Reasoning is Off by default (it scored best). Low, Medium and High turn thinking on\nwith a growing budget and are much slower. Typical wait for a sentence or two on Apple Silicon:\n\n| Reasoning | Wait |\n| --- | --- |\n| Off | 1.5–2.5 s |\n| Low | about 30 s |\n| Medium | about 2 min |\n| High | about 6 min |\n\nThe model loads on first use and is freed after five idle minutes.\n\nModel output has em dashes (—) replaced with hyphens (-); code is left unchanged.\n\n## Privacy\n\n- API keys, connection header values and OAuth tokens are stored in the macOS Keychain.\n- The microphone records only while a voice shortcut is active or a live conversation runs.\n  With a local model, audio stays on the Mac; with OpenAI, the recording goes to OpenAI; agent\n  requests send only the transcript to your connection.\n- Text is sent to the selected provider only when an action runs: when you open the popup, or\n  after a short pause while editing in the main window.\n\n## Development\n\n- Unit tests: `xcodebuild test -scheme TinyAI-UnitTests -destination 'platform=macOS'`\n  (CI runs them and the `--build-only` release build on every PR: `.github/workflows/tests.yml`)\n- Manual checks: open `scripts/manual-test-kit.html` in a browser. It has rich-text samples\n  (lists, links, code, tables, a long text) with the expected result for each.\n- Model catalog and request rules (reasoning, token budget): `ModelCatalog` and\n  `LLMRequestPolicy` in `TinyAI/TranslationService.swift`.\n- Local speech engine: `scripts/fetch_transcribe_cpp.sh` downloads the pinned transcribe.cpp\n  framework into `Vendor/` (the build script and the Xcode build phase run it).\n  `scripts/fetch_llama_cpp.sh` does the same for llama.cpp, which runs the on-device text model\n  (`LocalLLMEngine` in `TinyAI/LocalLLM.swift`); its tests in `LocalLanguageModelTests` run only\n  when the model is downloaded. The local-engine\n  tests in `TinyAITests/VoiceFeatureTests.swift` run only when the model files are downloaded.\n- Voice logs: `/usr/bin/log show --last 5m --predicate 'subsystem == \"IT.TinyAI\"' --style compact`\n  shows hotkeys, recording level, transcripts and failures.\n- Voice flow: `VoiceCoordinator` (hotkeys → recording → transcription → dictation, agents, live),\n  `MCPClient`, `OAuthBrowserAuthorizer`, `HTTPAgentClient`, and the GPT-Live wire format in\n  `LiveProtocol`.\n- HTML handling: `RichTextHTMLParser` and `RichTextHTMLSanitizer` in\n  `TinyAI/RichTextPayload.swift`. TextWren parses HTML itself instead of using AppKit's importer,\n  which crashes on macOS 27 for HTML with links.\n\n## License\n\nTextWren is released under the [MIT License](LICENSE). The bundled transcribe.cpp and llama.cpp\nframeworks and the downloaded models keep their own licenses.\n"}