* feat(providers): LLM/ASR 供应商改成渠道卡片——同一家可存多把 key,排序即优先级 原来一个供应商只能存一份配置,换 key 只能把旧的覆盖掉;想在主号/备号之间来回切, 每次都要重新粘贴。这个 PR 把「服务 → AI 提供商」从「下拉选厂商 + 一组字段」改成 一张张可命名、可排序、可开关的渠道卡片。 心智只有一条:**排序即优先级,列表里第一个启用的就是当前生效的渠道**。关掉的渠道 自动沉到末尾;后端不另存「当前选中」,避免「列表第一张是 A、实际请求打的是 B」 这种两处真相。 本 PR 只做多渠道的存储、编辑与切换,**不含失败重试与故障转移**(那是下一个 PR, 需要先把听写/润色链路里几十处隐式读 active 凭据的地方显式化)。 存储与迁移 - ChannelMeta(providerType / order / enabled / lastTest)以 serde(flatten) 嵌进 ASR、LLM 两个 entry;v1 老 payload 照常反序列化。 - providerType 与渠道 id 解耦:同一家厂商的多张卡片各有自己的 id,但 providerType 都指向同一个厂商实现。coordinator::resolve_effective_asr_provider 和 commands 里 几十处 `== PROVIDER_ID` 的比较依赖它,拿成 id 会让整个 ASR 路由失效。 - v1→v2 迁移幂等:迁移出的渠道 **id 沿用原 preset id**,不生成 uuid,老用户的 map key 一个字节都不变,重复执行结果一致。 - 迁移只在内存里做、不主动落盘:启动时写 keyring 会在 macOS 触发钥匙串 ACL 弹窗, 留给下一次真实写入顺带固化。 - clean_credentials 原本会 retain(!is_empty) 删掉空 entry —— 刚点「添加渠道」、名字 取好了还没填 key 的卡片会被静默删掉,改为「渠道卡片只能由用户显式删除」。 - ChannelMeta 手写 Default(derive 会让 enabled=false,而 write_account 用 entry().or_default() 建 entry,新写入的渠道会一出生就被禁用)。 - Windows 全新安装预置一张 Foundry 本地 ASR 卡片,保住开箱即用。 IPC - 新增 list/create/rename/delete/set_enabled/reorder/record_test 七个命令。 - set_credential/read_credential 的 provider 作用域原本硬性拒绝 LLM 账户,补上 get/set_for_llm_provider —— 编辑列表里第 3 张 LLM 卡片必须能按 id 定位。 - validate_provider_credentials / list_provider_models 新增可选 channel_id: 卡片上的「测试连通」要测用户点的那一张,而不是当前生效的那张。 前端 - ChannelList:卡片列表、拖拽排序、开关沉底、生效中/失败标红/延迟展示。 - 添加与编辑弹窗;本地引擎与 Codex OAuth 不做预置固定卡片,和云端厂商一样从 「添加渠道」里选,只是编辑时没有 key/地址字段。 - 新手引导(Onboarding)列表为空时直接摊开添加表单,不让新用户对着空列表发呆。 - 五种语言文案齐备。 验证:cargo test --lib persistence::credentials(31 passed)、commands::(104 passed)、 npm test(含 tsc + vite build,退出码 0)、浏览器 mock 预览逐项走查四种卡片状态与两个弹窗。 设计与分期见 docs/provider-channels-plan.md。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(channels): 添加改成一步、拖拽在真机能用、卡片状态不再假装"健康" 三处都是装机自用后暴露的问题。 **添加渠道少一步。** 原来是「先建卡片、再填凭据」两步 —— 那只是实现上需要先有渠道 id 才能写凭据(凭据按 id 作用域存),不该变成用户多点一次。改成点+ 直接开一个完整弹窗, 供应商、名字、密钥、地址、模型、连接检查都在一屏里;草稿卡片在后台先建出来,用户什么 都没填就关掉时由 delete_channel_if_blank 回收,不会在列表里留空卡片。换供应商也不再 需要重建卡片,走新增的 set_channel_provider_type。 **拖拽在打包后的 app 里根本不动。** Tauri 的 webview 默认开着 dragDropEnabled,会把 HTML5 的 dragstart/drop 当成文件拖放吞掉 —— 浏览器预览里是好的,真机里是坏的,只验 前者就会漏。改用 pointer 事件手写,顺带让 Windows / Android 行为一致。 改的过程中炸出第二个:最初用 setPointerCapture,它把后续事件重定向到手柄,浏览器补发 的 click 于是落到设置弹窗的遮罩上(遮罩挂着 onClick={onClose}),**拖一下卡片整个设置 面板就关了**。改成 window 级监听不动事件目标,并在捕获阶段吞掉拖拽后紧跟的那一次 click。 **卡片状态不再假装"健康"。** 原来当前那张有绿点 + 「生效中」文字,两者都被读成"这张 能用",可它只代表排在最前面 —— 一张 key 已经失效的卡片照样排第一。优先级和健康度是两 个正交的维度,被压成了一个视觉。现在: - 绿点与「生效中」全部去掉,当前那张只用左侧一条竖条表达位置,不带健康暗示; - 验证按钮移到卡片上(开关左侧),**按钮自己就是结果容器**:未验过显示「验证」、 通过显示延迟数字(`284ms`,数字本身既说明通了又能比快慢)、失败显示能指导行动的短 标签(`✗ 401` 改 key / `✗ 429` 等会儿 / `✗ 超时` 查网络); - 副行显示上次验证是多久以前,超过一天的结果褪色 —— 让"这条结论会过期"可见; - 按钮宽度固定,避免文字变化把开关和箭头挤来挤去; - **不做自动验证**:验证是真实 API 调用(LLM 走一次真润色、ASR 传一段静音音频), 打开设置就全部验一遍等于按卡片数烧额度,还容易撞进限流。 顺带把 mock 的 reorderChannels 改成真重排:原来是空操作,浏览器预览里松手后顺序被 listChannels 拉回原样,看着像"拖拽坏了",会误导下一个人。 另:补上此前遗漏的 rustfmt(自己编辑范围内的行)。 验证:cargo test --lib persistence::credentials(31 passed)、commands::(101 passed)、 tsc 0;浏览器侧用精确的 pointer 事件序列验了拖动跟手、松手持久化、设置面板不被误关。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(channels): 迁移时优先把「填过凭据」的渠道排第一,别让空卡片顶到最前 `active` 指向一个已不存在的 entry 是真实会发生的:前端 prefs 里的 activeAsrProvider 与凭据库里的 active.asr 是两份数据,历史上可能不同步。原来遇到这种情况纯按字母序挑一张 排第一,完全不看那张有没有填过 key —— 结果很容易把一张空卡排到最前,用户升级后打开就 看到「未配置」,而他真正配好的那张其实还在列表下面躺着。 排序优先级改为:原 active → 填过凭据的 → 字母序(兜底,保证幂等)。 同时补一个边界测试钉死底线:即使 active 指向缺失 entry,迁移也**绝不碰任何凭据** —— 所有 key 原样留在各自 entry 里,用户把想用的那张拖回第一位就能恢复。 验证:cargo test --lib persistence::credentials(33 passed,跑前已备份 preferences.json 且跑后 md5 未变)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(channels): 写明重试尚未实现,并区分代码里已有的连接层重连与渠道故障转移 排查时在代码里搜到 retry 容易误以为渠道重试做了。补两条既有行为的说明: net.rs::send_with_retry 只对连接层失败重连同一个 endpoint(拿到任何 HTTP 响应即返回、 超时不重试),永远不会换卡片;润色失败已经会回落插入 ASR 原文,所以「全渠道失败 → 出原文」这条决策天然满足,P2 要做的是在回落前多试几张卡片。 同时写明 P0 的定位:多渠道现在的价值是存档与手动切换,排第二的卡片不会被自动用上。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(channels): 修复渠道化 Spec 缺陷——禁用语义/order 冲突/默认值回填/LocalAsr 激活 - sync_active_channels 全部禁用时清空 active,运行时不再使用已禁用渠道凭据 - create_channel 落盘前 compact_orders,消除新卡与禁用项 order 冲突 - 换供应商时仅空槽写入 preset 默认 endpoint/model(不覆盖用户已填值) - LocalAsr 激活前确保本地引擎渠道卡存在、启用并置顶 - 更新 provider-channels-plan 状态与待确认项 * fix(channels): 修复 Omni 路由与草稿生命周期 --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Chris233 <h-chris233@outlook.com>
OpenLess
Open-source voice input for macOS & Windows
Hold a hotkey, speak, and watch AI-polished text stream straight to your cursor —
in the writing style you choose.
Website · Download · English / 中文
💬 Join our Discord · QQ Group 1078960553
Sponsors
OpenLess is sustained by the generous support of its sponsors.
悠雾云数据 · Youwu Cloud Data (knin.net)
Developers
![]() TRIP tripmc.top |
![]() Chris233 chris233.qzz.io |
OpenLess is a cross-platform (macOS and Windows) voice-input application — a fully open-source alternative to commercial tools such as Typeless, Wispr Flow, Lazy, and Superwhisper. Official site: openless.top.
Place your cursor in any text field — ChatGPT, Claude, Cursor, Notion, an email draft, a chat box — press a single global hotkey, and speak. OpenLess records the audio, transcribes it, polishes the text in the mode you selected, and inserts the result at the cursor. If insertion is blocked, the text is copied to the clipboard instead, so nothing you said is lost.
Unlike dictation tools that simply emit a word-for-word transcript, OpenLess's headline capability is its AI-prompt mode: you speak freely, and it adds structure, captures your constraints, and produces a context-rich prompt that you can paste directly into ChatGPT, Claude, or Cursor.
From tool to infrastructure
Advanced systems all do the same thing: they turn today's communication into tomorrow's default rules. The Internet won not because HTTP was faster, but because no one negotiates how data is transmitted anymore. Git won not because the code was better, but because teams stopped coordinating versions every day. Stripe won not because payments were faster, but because developers stopped coordinating with banks.
What OpenLess does is not "faster dictation" — it eliminates every act of coordination between a thought and clean text. Turning what's in your head into usable text used to mean deciding, over and over: what tone for this one? how is that name spelled? which input field, copy or paste? where does the model's key live, and who has access? OpenLess sediments those repeated negotiations, layer by layer, into defaults:
- Tone sediments into a style pack — named, switchable, live at a keystroke, never re-decided.
- Proper nouns sediment into dictionary hotwords — recognized by the ASR, hinted to the polish model, never re-corrected.
- Where text lands sediments into a single insertion protocol — hold → speak → it appears at the cursor, with an automatic clipboard fallback.
- Model and credentials sediment into the OS vault — authorized once at launch, never renegotiated.
That is what authorizing the infrastructure once, at launch means: on first run you grant the microphone, accessibility, and cloud credentials exactly once; from that moment, "speak → clean text at the cursor" is no longer a negotiation you run each time, but a default protocol you can forget is even there. Judge OpenLess not by how much faster it is, but by how much coordination it removes.
✨ What's new
Two capabilities that sediment yet more of the coordination you used to repeat every day into defaults:
- 🎨 Style Pack Marketplace. OpenLess no longer ships a single fixed "polish" voice. Build your own style packs with custom system prompts, switch between them with a hotkey, and install community packs in one click — or publish your own to share. When a style is tuned to your exact task (cold emails, commit messages, 小红书 posts, formal reports, your team's tone), the output is not merely cleaner — it is noticeably better, because the model is finally writing the way you intend.
- ⚡ Streaming insertion. Text now flows to your cursor character by character as it is polished, rather than making you wait for the complete result. Perceived latency drops sharply, so dictation feels nearly as fast as thinking — and it automatically falls back to a one-shot paste when an application cannot accept streamed keystrokes.
A concrete example
Hold the hotkey and say:
uh… so… I need to reply to the client about the proposal they sent last time, we talked it over internally and the overall direction is fine but a few details need changing, first the delivery timeline is too tight we'd like to push it back two weeks, second on budget their quote came in about twenty percent higher than we expected can we negotiate that, and overall we're positive about working together so reassure them, keep the tone polite not too blunt
Release the hotkey. A moment later, your input box reads:
Reply to the client about the proposal they sent. After internal discussion, the overall direction is fine, but a few details need adjusting:
1. Delivery timeline
(a) The current timeline is too tight; we'd like to extend it by two weeks.
2. Budget
(a) Their quote is about 20% higher than we expected — can we renegotiate?
Keep the overall message positive about the partnership, and keep the tone polite rather than blunt.
No edits are required — paste it into ChatGPT or Claude and let it draft the email. That is the core idea: compose prompts by speaking, faster and more cleanly than typing them.
Why OpenLess is open source
The closest alternatives are subscription SaaS products: a monthly fee, no option to bring your own model, your audio uploaded to the vendor, and your dictionary and habits stored in their account.
OpenLess targets the same end-user experience, but:
- Fully open source, local-first. The code lives in this repository, and all of your data stays on your machine.
- Bring your own cloud credentials. A dozen-plus ASR and polish providers — Volcengine, iFlytek, Alibaba Cloud Bailian, StepFun, OpenAI-compatible endpoints, and more — or any endpoint you bring. No vendor lock-in.
- Tuned for AI prompts. The structured mode reshapes loose speech into a prompt complete with context, constraints, and requests — ready to paste into ChatGPT, Claude, or Cursor.
- It will not answer for you. The model only cleans up your text. If you say "what features does this app still need?", it returns that as a clean question — it does not hand you a feature list. For that, ask the AI itself.
Use cases
- Writing prompts for ChatGPT / Claude / Cursor / Gemini — dictate a request and OpenLess turns it into a structured, detailed prompt.
- Drafting emails, specifications, and long Slack or WeChat messages — filler is removed, punctuation is corrected, and paragraphs are organized.
- Code comments, commit messages, and PR descriptions — capture your thoughts directly at the cursor.
- Any situation where you must produce written text but would rather not type.
Style packs & the Marketplace
A style pack is a named output style with its own system prompt. Rather than being constrained to one built-in "polish" voice, you can shape exactly how your speech is rendered — and the closer the style matches your real task, the better the result.
- Create and customize. On the Style page, add a pack and write a custom system prompt (for example, "terse engineering commit messages", "warm customer-support replies", or "小红书 copy with emoji"). Switch the active pack with a hotkey.
- Install from the community. Open the Marketplace to browse, search, and install packs others have shared in one click, and like the ones that work for you.
- Publish your own. Sign in with a GitHub identity (Settings → Marketplace), then select Publish to Marketplace from the Style page. Uploads are moderated before they appear publicly.
The marketplace is served by OpenLess's own moderated backend, so the catalog remains curated rather than a free-for-all.
Project direction
OpenLess does one thing: it turns speech into usable written text — AI prompts in particular — at the current cursor.
- It does not answer questions, run tasks, or analyze your project.
- It does not accumulate conversation context; every dictation is an independent cleanup request.
- The pipeline is: speech → transcript → cleanup → insertion at the cursor, with a clipboard fallback on failure.
- Everything else (modes, dictionary, history, menu bar, home report) exists to support that single path.
Comparison
| Tool | Form | How OpenLess differs |
|---|---|---|
| Typeless | Closed-source macOS / Windows / iOS, subscription | Open source; explicit AI-prompt mode; bring-your-own ASR + LLM; data and dictionary stay on your machine |
| Wispr Flow | Closed-source macOS / Windows, subscription | Open source; bring-your-own ASR + LLM; transparent prompt-handling rules |
| Lazy | Closed-source notes / capture tool | Not a notes container — inserts straight into any input field |
| Superwhisper | Closed-source macOS, subscription | Open source; cloud ASR today, local ASR on the roadmap |
Status
Every item below is one more layer sedimented into a default — a capability you authorize once and then never manage again. This is the infrastructure you stand on after launch:
- Tauri 2 backend in Rust with a React/TypeScript frontend. macOS 12+, Windows 10+.
- 🎨 Style Pack Marketplace — browse, install, and like community style packs from the in-app Marketplace, and publish your own (custom system prompt per pack, switchable by hotkey). Backed by a moderated marketplace backend; uploads are reviewed before they go public.
- ⚡ Streaming insertion — polished text is written to the cursor character by character to reduce perceived latency, with an automatic one-shot-paste fallback. Toggle in Settings → Recording.
- Toggle and push-to-talk recording modes, plus a MediaPlayPause trigger so wired-earbud inline controls can start and stop recording.
Esccancels at any phase, including polish and insertion. - Cloud ASR: Volcengine streaming ASR (bigasr), iFlytek realtime ASR (RTASR), Alibaba Cloud Bailian (classic realtime / Qwen3 realtime / Fun-ASR-Flash file transcription), StepFun StepAudio (batch + realtime), Zhipu GLM-ASR, Xiaomi MiMo ASR, ElevenLabs Scribe, OpenAI-compatible batch transcription (OpenAI Whisper / Groq / SiliconFlow SenseVoice / OpenRouter / ZenMux), and Apple Speech (macOS).
- Local ASR: bundled Qwen3-ASR (0.6B / 1.7B) via vendored
Open-Less/qwen-asr(macOS); Windows Foundry Local Whisper and sherpa-onnx (experimental) variants. - Polish providers: Ark (Volcengine), DeepSeek, OpenAI, Google Gemini, Codex OAuth, SiliconFlow, Atlas Cloud, Xiaomi MiMo, CometAPI, OpenRouter, Alibaba Cloud Coding Plan, CodingPlanX, MiniMax, and StepFun — plus any OpenAI-compatible endpoint you bring.
- Four output modes: raw, light polish, structured (AI-prompt mode), and formal. Plus a translation hotkey that converts speech directly into the configured target language (#43).
- Selection-ask QA panel — a separate hotkey opens a floating panel that runs voice Q&A against the highlighted text in any app (#118).
- Main window: Overview / History / Vocab / Style / Marketplace / Settings. Persistent tray icon, plus a mini status capsule that floats on screen and follows the display you are typing on (multi-monitor).
- Local model management — manage on-disk local-ASR model storage from Settings.
- Multilingual UI — Settings → Language switches between 简体中文 / 繁體中文 / English / 日本語 / 한국어 (auto-detected on first launch).
- In-app auto-update — Settings → About → Check; signed updater artifacts via the Tauri updater plugin.
- Beta channel (opt-in) — Settings → About → Join Beta channel exposes the latest pre-release build for manual download. Beta releases never reach Stable users automatically (see Contributing workflow).
- Distribution channels — direct DMG/EXE from Releases, Homebrew Cask (
brew install --cask openless), and a Windows installer. - Single-instance lock — prevents two OpenLess processes from racing the same hotkey edge.
- Dictionary entries are injected as Volcengine ASR
context.hotwordsand as semantic hints during polish; hits accumulate per session. - Platform-native global hotkey: CGEventTap on macOS, low-level keyboard hook (
WH_KEYBOARD_LL) on Windows.
Download & install (end users)
Go to Releases and download:
- macOS:
OpenLess_<version>_aarch64.dmg(Apple Silicon) orOpenLess_<version>_x64.dmg(Intel). Open it, drag the app to/Applications, then run the following once in Terminal to bypass the Gatekeeper "damaged" warning (the build is ad-hoc signed, not Apple-notarized):xattr -cr /Applications/OpenLess.app - Windows:
OpenLess_<version>_x64-setup.exe— run the installer. - Android (release APKs on GitHub Release; debug APKs via Actions artifacts):
- Phones (recommended):
OpenLess_<version>_arm64-v8a.apk - Older 32-bit ARM:
OpenLess_<version>_armeabi-v7a.apk - 64-bit emulator:
OpenLess_<version>_x86_64.apk - 32-bit emulator:
OpenLess_<version>_x86.apk - In-app updates (Settings → About) use
latest-android-{arch}.jsonmanifests; Beta users join Beta in Advanced settings. - Debug smoke builds:
OpenLess-android-debug-{abi}-*.apkfrom workflow_dispatch artifacts. - If unsure, run
adb shell getprop ro.product.cpu.abiand pick the matching APK.
- Phones (recommended):
- macOS (Homebrew):
brew tap Open-Less/openless https://github.com/Open-Less/openless brew install --cask openless xattr -cr /Applications/OpenLess.app # Upgrade to the latest version brew update && brew upgrade openless
On first launch, grant the permissions the app requests.
macOS:
- Grant Microphone access.
- Grant Accessibility access.
- Quit and reopen the app — Accessibility only takes effect after a restart.
- Open Settings and fill in your Volcengine ASR + Ark credentials.
Windows:
- Grant Microphone access when prompted.
- Open Settings → Permissions to verify the global hotkey listener is active.
- Fill in your Volcengine ASR + Ark credentials in Settings.
For the full end-user walkthrough, see USAGE.md.
Build from source (developers)
The active codebase lives in openless-all/app/ (Tauri 2 + Rust + React/TS). The macOS build links a vendored C ASR engine (Open-Less/qwen-asr, forked from antirez/qwen-asr) pulled in as a git submodule under src-tauri/vendor/qwen-asr/, so initialize submodules on first clone.
# First clone only — pull in vendored submodules
git submodule update --init --recursive
cd "openless-all/app"
npm ci
# Dev: Vite at :1420 + Tauri shell
npm run tauri dev
# macOS release build (signs, installs, resets TCC)
./scripts/build-mac.sh
INSTALL=0 ./scripts/build-mac.sh # build only, skip install
# Rust type-check without a full compile
cargo check --manifest-path src-tauri/Cargo.toml
# Frontend TS check
npm run build
Logs: ~/Library/Logs/OpenLess/openless.log (macOS) / %LOCALAPPDATA%\OpenLess\Logs\openless.log (Windows).
Windows build — see openless-all/README.md for the MSVC and GNU/MinGW routes.
Contributing workflow
OpenLess uses a two-channel branching model.
beta— the Beta channel. The default branch and integration buffer; all in-progress development lands here. Beta builds may exist but are not pushed to regular users — they reach only those who explicitly opt into the Beta channel.main— the Stable channel (正式版). Always releasable; the build everyone receives by default.
your fork / topic branch
│ (test locally on your target platform first)
▼
PR → beta ← AI review (one pass, advisory only)
│ ← maintainer lightweight glance (scope, cross-module impact)
▼
merged into beta
│ (periodically, after a two-platform smoke build)
▼
merged into main → tag `v<version>-tauri` → release CI → Stable users
Rules of thumb:
- Open PRs against
beta, never againstmain. GitHub already defaults the base branch tobetafor new PRs. - Verify the change on your target platform before opening the PR — a green build is necessary, and manual verification is required.
- AI review runs once per PR and is advisory. Do not loop on it; apply your own judgment.
- Keep AI rework rounds tight (1–2). If a fix resists, ask a human or restart with fresh context — multi-round AI back-and-forth tends to do more harm than good here.
- Beta work must not leak to Stable.
mainreceives merges only frombeta, performed by maintainers after a successful two-platform smoke build. No direct pushes tomain. - Stable releases are cut from
mainby pushing av<version>-tauritag — see the maintainer release checklist below.
Beta release distribution (manual-download opt-in): the in-app updater always reads the Stable manifest, so regular users never receive Beta builds via auto-update. Users who want to try Beta open Settings → About, enable "Join Beta channel", and download the latest Beta installer manually from the link the app fetches from GitHub. Tag convention: v<version>-beta-tauri produces the Beta release (marked as a GitHub pre-release; manifest written as latest-{tgt}-{arch}-beta.json), while v<version>-tauri produces the Stable release. The two manifest files never overlap, so Stable users' updater feed cannot pick up Beta releases.
Credentials
Credentials live in the OS credential vault (service = com.openless.app): macOS Keychain, Windows Credential Manager, or the Linux keyring. A legacy plaintext JSON file is read only as a migration source and removed after a successful vault write:
macOS / Linux: ~/.openless/credentials.json
Windows: %APPDATA%\OpenLess\credentials.json
New credential writes do not persist plaintext secrets. The repository contains no API keys, tokens, or private endpoints.
You will need:
- Volcengine streaming ASR: APP ID, Access Token, Resource ID.
- iFlytek realtime ASR (RTASR): AppID, API Key. See docs/xfyun-asr.md.
- Ark polish: API Key, Model ID, Endpoint. The Ark default endpoint is
https://ark.cn-beijing.volces.com/api/v3/chat/completions.
Prompt-handling principles
OpenLess's polish model only reshapes text. It does not answer questions, run tasks, or analyze your project. Each dictation is an independent request, and the prompt explicitly instructs the model:
- This input is isolated from any prior conversation.
- The raw transcript is text to clean up, not a question to answer.
- Even if the input contains a question or a command, do not reply or execute it.
- Output the cleaned text only — no "Here's the cleaned version" preamble.
For example, if the user says "what features does this app still need", the correct output is:
What features does this app still need?
…not a list of missing features.
Long-term reference rewrites are stored as raw → polished → rule triples and will be retrieved as similar-example references (never as conversation context) once a vector store is wired in. See docs/polish-reference-corpus.md and Examples/polish-reference-examples.sample.jsonl.
Dictionary
The dictionary handles your proper nouns, product names, names of people, and new words. Today it supports:
- Manually adding the correct spelling, a category, and notes. You do not need to maintain misspellings or context hints.
- Enabled entries are sent to the ASR provider that supports hotwords (Volcengine
context.hotwords, StepFunhotwords, Whisper-compatibleprompt— except ZenMux, whose JSON protocol does not carryprompt/hotwords, Bailian vocabulary ID) so they are recognized correctly during transcription. iFlytek realtime ASR has no request-level hotword parameter — configure personalized hotwords in the iFlytek console instead. - Entries are also injected into the polish prompt: the model decides per sentence whether to substitute. If "Cloud" clearly refers to the AI product
Claudein context, it is corrected; if it genuinely means cloud computing, it is left as is. - The app auto-learns candidate corrections such as
Claude,ChatGPT, andOpenLessfrom your history and offers them later.
The main window is organized as Home / History / Dictionary / Settings. The Dictionary tab opens a separate editor window when you click "New". The Home tab shows total dictation time, total characters, average characters per minute, estimated time saved, and dictionary participation statistics.
Architecture
The active implementation is Tauri 2 (openless-all/app/). Releases are split into two channels: Stable (v<v>-tauri tag, auto-updated for all users) and Beta (v<v>-beta-tauri tag, GitHub pre-release, manually downloaded by opt-in users). Signed updater artifacts are produced by CI on every release tag.
Tauri backend (Rust) — each module depends only on types.rs:
types.rs Pure value types: DictationSession, PolishMode, HotkeyBinding, errors
hotkey.rs Global hotkey (CGEventTap on macOS, WH_KEYBOARD_LL on Windows, rdev on Linux)
recorder.rs Mic → 16 kHz mono Int16 PCM, RMS callback
asr/ Streaming ASR clients (Volcengine / Bailian / Qwen3 / StepFun / iFlytek over WebSocket) + Whisper-compatible batch HTTP
polish.rs OpenAI-compatible chat completions (Ark / DeepSeek / etc.)
insertion.rs AX focused-element → clipboard + Cmd+V → copy-only fallback
persistence.rs History / preferences / vocab JSON + platform credential vault
permissions.rs TCC checks (Accessibility / Microphone)
coordinator.rs State machine: Idle → Starting → Listening → Processing
commands.rs Tauri IPC surface
React frontend (src/) — state via Recoil atoms (pages/_atoms.tsx); hotkey capability and binding via HotkeySettingsContext; all backend calls go through lib/ipc.ts.
The dictation pipeline: hotkey edge → Recorder.start + ASR.openSession → [audio frames] → hotkey edge → Recorder.stop + ASR.sendLastFrame → Polish → Insert → History.save.
See CLAUDE.md for invariants and module-wiring rules.
Roadmap
Planned but not yet shipped:
- Cross-session style memory: polish learns the user's tone over time (#46).
- Snippets (no UI or trigger logic yet).
- History enhancements: copy button, search, re-polish, re-insert.
- A "Paste last result" hotkey.
Maintainer release checklist
OpenLess ships two release channels. The branch name equals the channel name (see Contributing workflow).
Common prep (both channels)
- Bump the version in all five files:
package.json,package-lock.json(root + nested entry underpackages.""),src-tauri/tauri.conf.json,src-tauri/Cargo.toml, andCargo.lock(look for thename = "openless"block). CI'sVerify version syncstep will otherwise fail the build. - Run
INSTALL=0 ./scripts/build-mac.shand confirm the.applaunches. - Smoke-test on a clean machine: permission flow, hotkey, recording, ASR, polish, insertion, and clipboard fallback.
- Confirm that
TAURI_SIGNING_PRIVATE_KEYand (for macOS) the Apple signing/notarization secrets are set on the repo.
Beta channel — v<v>-beta-tauri
- Land changes onto the
betabranch via PR review. - Push the tag on
beta:git tag v<v>-beta-tauri && git push origin v<v>-beta-tauri. - CI tags the GitHub Release as
Pre-releaseand uploads onlylatest-{tgt}-{arch}-beta.jsonupdater manifests. The Stable users'releases/latestredirect is unaffected. - Announce in the appropriate channel (issue thread, QQ group) that opt-in Beta users can grab it from Settings → About → Join Beta channel.
Stable channel — v<v>-tauri
- Merge
beta → mainafter the Beta release has soaked sufficiently (or run a final two-platform smoke build directly). - Push the tag on
main:git tag v<v>-tauri && git push origin v<v>-tauri. - CI publishes a normal GitHub Release and uploads
latest-{tgt}-{arch}.json(no-betasuffix). All Stable users receive the update through the in-app updater.
Post-release verification (always run)
Run the 5-step checklist in CLAUDE.md → Branch & release-channel workflow → Channel distribution: page status (pre-release flag), asset-filename channel correctness, Stable user flow, Beta opt-in flow, and raw endpoint sanity.
Acknowledgements
OpenLess sincerely thanks its sponsors, developers and contributors, and the broader LinuxDo community.
We appreciate our sponsors for making sustained project work possible, and we thank our developers and contributors for building, reviewing, and improving OpenLess.
OpenLess also recognizes and appreciates the LinuxDo community for its open, practical, and developer-friendly atmosphere. Many of the ideas, discussions, and early feedback around OpenLess were inspired by the broader open-source spirit that LinuxDo represents.
This acknowledgement does not imply official endorsement or affiliation.
License
OpenLess is released under the MIT License.

