From c78bb44c236d3a035b1c3f9e219989daee608c19 Mon Sep 17 00:00:00 2001 From: baiqing Date: Mon, 27 Apr 2026 15:25:28 +0800 Subject: [PATCH] chore: cut 1.0.0 release with usage docs and MIT license Bump APP_VERSION to 1.0.0 / BUILD_NUMBER to 1; refresh README to clarify project direction and add quarantine-removal guidance for the unsigned release artifact; add USAGE.md as the end-user walkthrough; add MIT LICENSE. --- LICENSE | 21 ++++++ README.md | 149 ++++++++++++++++++++++++++----------------- USAGE.md | 98 ++++++++++++++++++++++++++++ scripts/build-app.sh | 4 +- 4 files changed, 213 insertions(+), 59 deletions(-) create mode 100644 LICENSE create mode 100644 USAGE.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f6970866 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 OpenLess contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 983cf6f7..79b63ba8 100644 --- a/README.md +++ b/README.md @@ -2,76 +2,86 @@ OpenLess 是一个原生 macOS 语音输入应用:把光标放在任意输入框,按下全局快捷键说话,OpenLess 会录音、转写、轻度整理,并把结果插入当前输入位置。插入失败时会自动复制到剪贴板,尽量保证“用户说过的话不丢”。 -## 当前状态 +## 项目方向 -- 原生 Swift / SwiftUI / AppKit,SwiftPM 项目。 -- 支持 macOS 15+;macOS 26+ 使用 Liquid Glass 效果,旧系统回退到系统 material。 +OpenLess 只做一件事:**把语音变成可用的书面文字,落到当前光标位置。** + +- 不做问答、不做任务执行、不做项目分析。 +- 不做对话上下文累积,每次输入都是独立的整理请求。 +- 输入语音 → 转写 → 整理 → 插入当前输入框。失败时复制到剪贴板。 +- 围绕这条主路径完善体验:模式选择、词典、历史、菜单栏、首页报告。 + +## 当前状态(v1.0) + +- 原生 Swift / SwiftUI / AppKit,SwiftPM 项目;macOS 15+。 +- macOS 26+ 使用 Liquid Glass 效果,旧系统回退到系统 material。 - 默认是切换式录音:按一次开始,再按一次结束;录音中按 `Esc` 取消。 -- 支持火山引擎流式 ASR 和 Ark / DeepSeek 兼容 Chat Completions 润色。 -- 支持 4 种输出模式:原文、轻度润色、清晰结构、正式表达。 -- 支持 Dock 主窗口、菜单栏控制、底部微型状态胶囊、首页报告、历史记录、词典、设置和剪贴板兜底。 +- 接入火山引擎流式 ASR 和 Ark / DeepSeek 兼容 Chat Completions 进行润色。 +- 4 种输出模式:原文、轻度润色、清晰结构、正式表达。 +- 主窗口按「首页 / 历史记录 / 词典 / 设置」组织;菜单栏常驻;底部有微型状态胶囊。 +- 词典支持作为 ASR 热词注入和润色阶段的语义判断。 -## 运行方式 +## 下载与安装(普通用户) + +到 [Releases](../../releases) 下载 `OpenLess-1.0.0.zip`,解压得到 `OpenLess.app`,拖到 `应用程序`。 + +**重要:** 1.0 是开发期 ad-hoc 签名构建(未做 Apple Developer ID 签名和 notarization)。直接打开会被 Gatekeeper 拦下提示“无法验证开发者”。需要在终端中移除隔离属性: ```bash -./scripts/build-app.sh -open build/OpenLess.app +xattr -dr com.apple.quarantine /Applications/OpenLess.app ``` -开发期使用 ad-hoc 签名。每次重新构建后,脚本默认会重置辅助功能、麦克风、AppleEvents 和 ListenEvent 权限,确保 macOS 不继续引用旧二进制授权。 +之后就可以双击启动。首次启动需要在 `系统设置 → 隐私与安全`: -首次启动后: +1. 授予 OpenLess 麦克风权限。 +2. 授予 OpenLess 辅助功能权限。 +3. **退出 OpenLess 并重新打开**(辅助功能授权对全局快捷键生效需要重启进程)。 +4. 从 Dock 打开 OpenLess 首页 → 「设置」 → 填入火山引擎 ASR 和 Ark 凭据。 -1. 在系统设置中授予 OpenLess 辅助功能权限和麦克风权限。 -2. 退出 OpenLess。 -3. 重新打开 `build/OpenLess.app`。 -4. 从 Dock 打开 OpenLess 首页,在「设置」里填写火山引擎 ASR 和 Ark 凭据。 +完整的端用户使用步骤见 [USAGE.md](USAGE.md)。 -实时日志: +## 从源码构建(开发者) ```bash +# 库 / 测试构建 +swift build +swift test + +# 完整 .app 构建(release,ad-hoc 签名,默认重置 TCC) +./scripts/build-app.sh + +# 保留已授予的 TCC 权限 +RESET_TCC=0 ./scripts/build-app.sh + +# 启动 +open build/OpenLess.app + +# 实时日志 tail -f ~/Library/Logs/OpenLess/OpenLess.log ``` +启动参数(在 `AppDelegate.runLaunchActions` 处理): + +```bash +open build/OpenLess.app --args --open-settings +open build/OpenLess.app --args --start-recording +``` + ## 凭据 -开发期凭据保存在本机: +凭据保存在本机 Keychain(service = `com.openless.app`)。开发期同时维护一份明文 JSON 兜底,用于在 Keychain 不可用时回退: ```text -~/.openless/credentials.json +~/.openless/credentials.json # 0600,目录 0700 ``` -文件权限会设置为 `0600`,目录权限为 `0700`。当前没有把 API Key 写入仓库、日志或公开配置。正式发布前建议切换到稳定开发者签名 + Keychain。 +仓库本身不包含任何 API Key、Token 或 Endpoint 之外的私有信息。 -需要配置: +需要配置的字段: - 火山引擎 ASR:APP ID、Access Token、Resource ID。 - Ark 润色:API Key、Model ID、Endpoint。 -## 图标 - -图标源文件放在: - -```text -Resources/Brand/openless-app-icon-source.jpg -``` - -生成脚本: - -```bash -swift scripts/generate-app-icon.swift -``` - -脚本会输出: - -```text -Resources/AppIcon.png -Resources/AppIcon.icns -Resources/Brand/openless-standard-image.png -``` - -`Resources/Brand/openless-standard-image.png` 是 1024×1024 的标准品牌图;`scripts/build-app.sh` 会自动生成图标并把 `AppIcon.icns` 写入 `.app` bundle。 - ## 提示词处理原则 OpenLess 的润色模型只做文本整理,不做问答、不做任务执行、不做项目分析。每次语音输入都会作为独立请求发送,提示词会明确告诉模型: @@ -102,22 +112,47 @@ OpenLess 的润色模型只做文本整理,不做问答、不做任务执行 主窗口按「首页 / 历史记录 / 词典 / 设置」组织;词典页点击“新建”会弹出独立编辑窗口,首页会展示口述时长、总字数、平均每分钟字数、估算节省时间和词典参与记录。 -## 尚未完成的功能 +## 架构概览 -- 按住说话模式:当前是切换式录音,尚未实现真正的 hold-to-talk。 -- 本地 ASR:当前主要接入火山引擎云端 ASR,本地模型路由还没有实现。 -- 常用片段 Snippets:需求中有规划,当前没有 UI 和触发逻辑。 -- 历史增强:当前能查看和清空历史,尚未支持复制按钮、搜索、重新润色、重新插入。 -- 粘贴上一条快捷键:需求中有规划,当前没有独立快捷键。 -- 多屏定位:胶囊目前优先显示在主屏底部,尚未按当前输入焦点所在屏幕定位。 -- 稳定签名与 Keychain:开发期使用 ad-hoc 签名和本机受保护文件,正式发布前需要开发者签名、notarization 和 Keychain 方案。 -- 自动更新和发布流水线:尚未接入 Sparkle、GitHub Release 自动打包或 notarization 流程。 -- 更完整测试环境:当前 `swift build` 可验证编译;本机 CommandLineTools 环境缺少 `XCTest`,需要完整 Xcode 环境后跑 `swift test`。 +SwiftPM 工作区,1 个可执行 + 8 个库。库与库之间无相互依赖,全部只依赖 `OpenLessCore`,由 `OpenLessApp` 统一在 `DictationCoordinator` 里编排。 -## GitHub 发布前检查 +``` +OpenLessCore // Pure value types: DictationSession, PolishMode, HotkeyBinding, + // AudioConsumer protocol, RawTranscript/FinalText, errors. +OpenLessHotkey // CGEventTap-based modifier-key monitor. Requires Accessibility. +OpenLessRecorder // AVAudioEngine → 16 kHz mono Int16 PCM, 推送到 AudioConsumer. +OpenLessASR // 火山引擎 streaming ASR over WebSocket. +OpenLessPolish // Ark / Doubao chat-completions 客户端 + 模式驱动 prompts。 +OpenLessInsertion // AX focused-element 优先;剪贴板 + Cmd+V 兜底;最后 copy-only。 +OpenLessPersistence // CredentialsVault (Keychain), HistoryStore, DictionaryStore, + // UserPreferences。 +OpenLessUI // SwiftUI 胶囊视图 + 状态枚举(不接窗口)。 +OpenLessApp // AppDelegate, 菜单栏, 设置窗口, 胶囊窗口, DictationCoordinator。 +``` -- 确认没有提交 `.build/`、`build/`、`.DS_Store`、本地凭据或临时截图。 +录音 → 转写 → 润色 → 插入的状态机由 `Sources/OpenLessApp/DictationCoordinator.swift` 单一拥有,详见 [CLAUDE.md](CLAUDE.md)。 + +## 1.0 之后的规划 + +下面这些功能在需求文档里有规划,但 1.0 没有发布: + +- 按住说话模式(hold-to-talk):当前仅支持切换式。 +- 本地 ASR:当前仅接入火山引擎云端 ASR。 +- 常用片段 Snippets:尚无 UI 和触发逻辑。 +- 历史增强:复制按钮、搜索、重新润色、重新插入。 +- 粘贴上一条快捷键。 +- 多屏定位:胶囊按当前焦点所在屏幕显示。 +- Developer ID 签名 + Notarization + Sparkle 自动更新。 + +## 维护者:发布前检查 + +- 确认没有提交 `.build/`、`build/`、`.DS_Store`、`~/.openless/credentials.json` 或临时截图。 - 保留 `Resources/Brand/openless-app-icon-source.jpg`、`Resources/AppIcon.png`、`Resources/AppIcon.icns`。 - 运行 `./scripts/build-app.sh`,确认 `build/OpenLess.app` 可启动。 - 在一台干净 macOS 机器上验证权限引导、快捷键、录音、ASR、润色、插入和剪贴板兜底。 -- 正式分发前完成 Developer ID 签名和 notarization。 +- 用 `ditto -c -k --keepParent build/OpenLess.app build/OpenLess-.zip` 打包,确保 ad-hoc 签名和扩展属性保留。 +- 正式分发前请完成 Developer ID 签名和 notarization。 + +## 许可 + +MIT diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 00000000..53802041 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,98 @@ +# OpenLess 使用说明 + +面向普通用户。开发者和架构信息见 [README.md](README.md) 与 [CLAUDE.md](CLAUDE.md)。 + +--- + +## 1. 下载与解除隔离 + +到 [Releases](../../releases) 下载最新的 `OpenLess-1.0.0.zip`,解压会得到 `OpenLess.app`。把它拖到 `应用程序`。 + +由于 1.0 是 ad-hoc 签名构建(没有付费 Apple Developer ID),macOS Gatekeeper 会拦下提示「无法验证开发者」。在终端中执行一次: + +```bash +xattr -dr com.apple.quarantine /Applications/OpenLess.app +``` + +之后双击就能启动。 + +> 这一步只需要做一次。下次升级版本,覆盖 `OpenLess.app` 后再执行一次同样的命令即可。 + +## 2. 授权 + +OpenLess 需要两个系统权限: + +| 权限 | 在哪里授权 | 用来做什么 | +| --- | --- | --- | +| 麦克风 | 系统设置 → 隐私与安全 → 麦克风 | 录音 | +| 辅助功能 | 系统设置 → 隐私与安全 → 辅助功能 | 监听全局快捷键 + 把文本写入当前输入框 | + +**授权辅助功能后必须退出 OpenLess 并重新打开。** macOS 不会把新授权同步给已经在跑的进程,全局快捷键监听需要进程重启才能生效。 + +## 3. 配置 ASR 和润色凭据 + +OpenLess 不内置任何云端 Key,需要你自己提供: + +- **火山引擎流式 ASR**:APP ID、Access Token、Resource ID。在火山引擎控制台开通「语音技术 → 流式语音识别」后获取。 +- **Ark / DeepSeek 兼容 Chat Completions**:API Key、Model ID、Endpoint。Ark 默认 endpoint 是 `https://ark.cn-beijing.volces.com/api/v3/chat/completions`。 + +在 OpenLess 主窗口的「设置」标签页填入即可。凭据保存在系统 Keychain(service = `com.openless.app`),不会上传也不会写入仓库。 + +> 如果不填 Ark:OpenLess 会跳过润色,直接插入 ASR 原文。 +> +> 如果不填火山:OpenLess 会跑一段 mock 流程,并把占位文本复制到剪贴板(不会插入到输入框)。 + +## 4. 日常使用 + +1. 把光标停在任意输入框(聊天框、邮件、代码编辑器、浏览器地址栏……)。 +2. 按一次右 `Option`(默认快捷键)开始录音。底部胶囊会变成红色音量条。 +3. 说话。 +4. 再按一次右 `Option` 结束录音。胶囊会进入「转写 → 润色」状态。 +5. 润色完的文字会自动插入到光标所在位置。如果当前应用拒绝写入,会复制到剪贴板,请手动 `Cmd+V`。 +6. 按 `Esc` 在录音过程中取消,原始音频不会发送到 ASR。 + +每一次会话都会保存到「历史记录」标签页,包含:录音时长、原始转写、润色后文本、采用的模式。 + +## 5. 输出模式 + +在主窗口顶部切换: + +| 模式 | 适合场景 | +| --- | --- | +| 原文 | 不想做任何修改,直接拿 ASR 结果。 | +| 轻度润色 | 去口癖、补标点。最常用。 | +| 清晰结构 | 把连贯口语拆成短句、列表。适合需求/任务描述。 | +| 正式表达 | 用更书面的语气重写。适合邮件、公告。 | + +模式只影响润色 prompt,不会让模型「回答你说的话」。详见 README 的「提示词处理原则」。 + +## 6. 词典 + +「词典」标签页用来教模型识别你的专有名词、产品名、人名和新词。 + +- 每个词条只需要填正确写法、分类(可选)、备注(可选)。 +- 启用的词条会作为火山 ASR 的 `context.hotwords` 提交,识别阶段就尽量正确。 +- 同时会注入润色阶段,让模型根据上下文判断是否需要把同音词修正成你的词。例如词典里有 `Claude`,模型听到 `Cloud` 时会根据语境决定要不要改成 `Claude`。 + +## 7. 排查 + +- **按快捷键无反应**:辅助功能没授权,或授权后未重启 OpenLess。 +- **录音时胶囊一直是灰色**:麦克风没授权,或被其他应用独占。 +- **转写一直在转圈**:火山凭据填错,或网络无法访问火山服务。看日志确认。 +- **文字没插入但被复制了**:当前应用拒绝 AX 写入和 Cmd+V,按 `Cmd+V` 手动粘贴。 +- **看日志**: + + ```bash + tail -f ~/Library/Logs/OpenLess/OpenLess.log + ``` + +## 8. 卸载 + +```bash +rm -rf /Applications/OpenLess.app +rm -rf ~/Library/Application\ Support/OpenLess +rm -rf ~/Library/Logs/OpenLess +rm -rf ~/.openless +``` + +Keychain 里的凭据可以用「钥匙串访问.app」搜索 `com.openless.app` 删除。 diff --git a/scripts/build-app.sh b/scripts/build-app.sh index dbae1e68..a132a2eb 100755 --- a/scripts/build-app.sh +++ b/scripts/build-app.sh @@ -6,8 +6,8 @@ cd "$(dirname "$0")/.." APP_NAME="OpenLess" BUNDLE_ID="com.openless.app" -APP_VERSION="0.1.8" -BUILD_NUMBER="9" +APP_VERSION="1.0.0" +BUILD_NUMBER="1" BUILD_DIR="build" APP_DIR="${BUILD_DIR}/${APP_NAME}.app" BIN_DIR="${APP_DIR}/Contents/MacOS"