[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81535":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":16,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":10,"pushedAt":10,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":15,"starSnapshotCount":15,"syncStatus":13,"lastSyncTime":25,"discoverSource":26},81535,"twinny","hachiwii\u002Ftwinny","hachiwii","A Bridge between Feishu\u002FLark and Codex","",null,"TypeScript",35,2,26,0,7,45.63,"MIT License",false,"master",[],"2026-06-11 04:07:33","# Twinny\n\n![Twinny banner](.\u002Fconfigs\u002Fbanner.png)\n\n[![npm version](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fv\u002Ftwinny.svg)](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ftwinny)\n\n[English Version](.\u002FREADME.en.md)\n\n## 环境要求\n\n- macOS、Linux、WSL2 或 Windows。installer 在 macOS 上管理 LaunchAgent（或 `--system-daemon` LaunchDaemon），在 Linux\u002F启用 systemd 的 WSL2 上管理 systemd user service，在 Windows 上管理用户级计划任务。\n- Node.js 22.18.0 或更新版本。Twinny 使用 Node.js 内置 `node:sqlite` 模块，不需要额外安装 SQLite native addon。\n- `PATH` 中有 Codex CLI 0.130.0 或更新版本，或者设置 `CODEX_BINARY`；如果缺少 Codex，installer 可以自动安装。\n- 一个已配置下方权限和事件订阅的 Feishu\u002FLark 机器人应用。\n\n## 安装\n\n### Codex 安装指南\n\n把下面这段内容复制给你的 Codex，让它按指南完成 agent 安装：\n\n```text\nRead https:\u002F\u002Fraw.githubusercontent.com\u002Fhachiwii\u002Ftwinny\u002Fmaster\u002Fagent_installation_guide.md and follow instructions in it.\n```\n\n### 手动安装\n\n通过 `npx` 运行交互式 installer：\n\n```sh\nnpx twinny@latest install\n```\n\nmacOS 默认安装为当前 GUI session 的 LaunchAgent。如果在 SSH、CI 或其他没有 GUI LaunchAgent 的环境中安装，installer 会退出并提示改用 system daemon 模式：\n\n```sh\nnpx twinny@latest install --system-daemon\n```\n\n`--system-daemon` 会通过 `sudo` 把 plist 写入 `\u002FLibrary\u002FLaunchDaemons`，并用当前用户写入 `UserName`；后续 `start`、`stop`、`restart`、`status` 会根据 `config.toml` 中的 service 配置继续操作 LaunchDaemon。\n\nWindows 原生安装会创建用户级 Task Scheduler 任务，随用户登录启动。WSL2 如果没有启用 systemd，installer 不会安装托管服务；这种环境需要用 `TWINNY_HOME=\u002Fpath\u002Fto\u002Fhome twinny run` 前台运行，或先启用 WSL systemd 后重新安装。\n\n常用服务命令：\n\n```sh\nnpx twinny@latest doctor\nnpx twinny@latest status\nnpx twinny@latest update\nnpx twinny@latest start\nnpx twinny@latest stop\nnpx twinny@latest restart\nnpx twinny@latest uninstall\n```\n\n如果不使用默认 home，给任意命令加上 `TWINNY_HOME=\u002Fpath\u002Fto\u002Fhome`。\n\nsecret 会存放在 `config.toml` 之外。非 macOS 安装（包括 Windows）默认把 Lark `app_secret` 写入 `TWINNY_HOME\u002Fauth.json` 的 `lark_app_secret` 字段。macOS 安装默认使用系统 Keychain；如果加 `--disable-keychain` 或 Keychain 写入失败，也会写入 `auth.json`。启动时优先读取 `auth.json`，然后才回退到 `TWINNY_LARK_APP_SECRET` 和旧 secret store（macOS Keychain，其他平台 `runtime\u002Fsecrets.json`）。\n\n## 飞书\u002FLark 应用配置\n\n你需要在飞书\u002FLark 开发者后台申请这些必要 API 权限：\n\n```text\nim:message.p2p_msg:readonly\nim:message.group_at_msg:readonly\nim:message:readonly\nim:message:send_as_bot\nim:message:update\nim:message:recall\nim:message.reactions:write_only\nim:chat:read\nim:chat:create\nim:chat:update\nim:resource\ncontact:user.base:readonly\ndocs:document.comment:read\ndocs:document.comment:create\ndocs:document.comment:write_only\ndocs:document.media:download\nwiki:node:read\n```\n\n推荐同时申请 `im:message.group_msg`。它允许 `\u002Factivate owner` 和 `\u002Factivate all` 在群聊中接收非 @ 消息；如果只使用 `owner_at` 或 `all_at`，基础的 `im:message.group_at_msg:readonly` 已足够。安装指引页面的导入 JSON 会预填这个推荐权限，便于后续切换群响应模式。\n\n订阅这些事件\u002F回调：\n\n```text\nim.message.receive_v1\nim.message.recalled_v1\ndrive.notice.comment_add_v1\napplication.bot.menu_v6\ncard.action.trigger\n```\n\n事件订阅方式请选择飞书\u002FLark 事件长连接（WebSocket）。Twinny 不需要为消息事件暴露公网 HTTP callback URL。\n\n可选机器人快捷菜单可以配置这些 `event_key`：\n\n| Event key | 动作 |\n| --- | --- |\n| `help` | 发送指令帮助。 |\n| `status` | 查看当前会话和 thread 状态。 |\n| `queue` | 切换下一条消息排队模式。 |\n| `new` | 在当前会话中新开 Codex thread。 |\n| `stop` | 停止当前 turn 并清空队列。 |\n\n## 用法\n\n向机器人发送普通消息即可开始或继续 Codex turn。在群聊中，owner 必须先激活群聊，普通消息才会被路由给 Codex。\n\n### 会话指令\n\n| 指令 | 用法 |\n| --- | --- |\n| `\u002Fhelp` | 查看可用指令。 |\n| `\u002Fstatus` | 查看会话、Codex thread、模型、token 和队列状态。 |\n| `\u002Fnew` | 停止当前任务、清空队列，并新开 Codex thread。 |\n| `\u002Fstop [all\\|\u003Cside_id>]` | 停止当前任务并清空队列。用 `all` 同时停止 side turn，或传 side id 停止指定 side turn。 |\n| `\u002Fnext` | 打断当前任务，并开始执行下一条排队消息。 |\n| `\u002Fsteer` | 把队列中的下一批消息注入当前正在运行的 Codex turn。 |\n| `\u002Fqueue [message]` | 不带 message 时让你的下一条消息排队；带 message 时把该消息加入下一轮。 |\n| `\u002Fgoal \u003Cobjective>` | 设置并运行 Codex goal。goal 运行中再次发送 `\u002Fgoal` 会更新目标。 |\n| `\u002Fplan [message]` | 进入 plan mode。带 message 时直接用 plan mode 处理该消息。 |\n| `\u002Fexit` | 在下一轮队列控制步骤中退出 plan mode。 |\n| `\u002Fside \u003Cmessage>` 或 `\u002Fbtw \u003Cmessage>` | 基于当前 Codex thread 发起临时 side conversation。 |\n| `\u002Fcompact` | 在下一轮队列控制步骤中压缩当前 Codex thread 上下文。 |\n| `\u002Fthread [message]` | 创建一个新的 Lark 话题，并绑定新的 Codex thread。带 `message` 时会把消息代理到新话题内。 |\n| `\u002Ffork [message]` | 从当前 Codex thread fork 出一个新的 Lark 话题。带 `message` 时会把消息代理到新话题内。 |\n| `\u002Fwatch \u003Clark_doc_url> [owner\\|all\\|none]` | 监听飞书\u002FLark 文档中的 @bot 评论，并把评论路由到当前 thread。不带参数时列出当前 thread 的监听；`owner` 只响应 owner，`all` 响应所有人，`none` 关闭监听。 |\n| `\u002Fworkspace [dir\\|num]` | 仅 owner 可用。查看或设置当前 conversation workspace，并同步主会话 thread。可传绝对路径、`~\u002F...`，或最近 workspace 列表中的序号。 |\n| `\u002Fcd [dir\\|num]` | 仅 owner 可用。查看或设置当前非主 thread workspace。可传绝对路径、`~\u002F...`，或最近 workspace 列表中的序号。 |\n| `\u002Fresume [thread_id\\|num] [session\\|local]` | 仅 owner 可用。列出本机可恢复 Codex thread，或把指定 thread 恢复为 Twinny 话题；默认使用原会话 cwd，`local` 使用当前会话 cwd。 |\n| `\u002Fmodel \u003Cmodel> \u003Ceffort>` | 设置当前 thread 后续 turn 使用的模型和推理强度。 |\n| `\u002Flogo` | 发送 Twinny logo 图片。 |\n| `\u002Ftwinny` 或 `\u002Fbanner` | 发送 Twinny banner 卡片。 |\n\n### 群管理指令\n\n只有配置中的 owner 可以执行这些指令：\n\n| 指令 | 用法 |\n| --- | --- |\n| `\u002Factivate \u003Cowner_at\\|owner\\|all_at\\|all> [profile]` | 激活群聊，设置谁可以把消息路由给 Codex，刷新群名，并可选绑定 profile。 |\n| `\u002Fdeactivate` | 停用当前群聊并清空待处理任务。 |\n| `\u002Fpair {guest_ou_id} \u003Cprofile>` | 授权非 owner 的 P2P 用户，并绑定到某个 profile。 |\n| `\u002Freload [profile]` | 修改配置后重载所有 Codex profiles，或只重载指定 profile。 |\n\n响应模式：\n\n- `owner_at`：只响应 owner 且提及 bot 的消息。\n- `owner`：响应 owner 的所有消息。\n- `all_at`：响应任意群成员且提及 bot 的消息。\n- `all`：响应任意群成员的所有消息。\n\n## 推荐实践\n\n为项目创建一个专用飞书\u002FLark 群。在该群的 workspace 中写一份 [AGENTS.md](http:\u002F\u002FAGENTS.md)。由 owner 用尽量小的可用权限激活群聊，然后为每个开发任务创建一个独立话题：\n\n```text\n\u002Factivate all host\n\u002Fthread fix the login callback race\n\u002Fthread add the GitHub README\n```\n\n当一个任务需要尝试替代方向，同时保留原 Codex thread 历史时，用 `\u002Ffork`：\n\n```text\n\u002Ffork try the smaller refactor path\n```\n\n把每个任务的讨论留在对应话题里。这样 Codex 上下文、本地 workspace 状态、Lark 讨论和状态卡都会按任务隔离。\n\n## 安全说明\n\nTwinny 运行在 owner 的本地机器上。请把它当作本地自动化桥接工具，而不是已经加固过的多人共享执行服务。\n\n当前默认配置还没有 fully ready for 广泛多人共享。尤其是当你用 `host` profile 和 `all` 响应模式激活群聊时，群里所有能发言的成员都可以用 owner 的 Codex 执行权限来运行任务：\n\n```text\n\u002Factivate all host\n```\n\n也要谨慎使用 `all_at`：如果群聊绑定到高权限 profile，所有能提及 bot 的成员都可以提交任务。\n\n在共享群聊中使用 Twinny 前：\n\n- 优先给 guest 或团队 profile 配置独立的 `codex_home`，不要共享 owner 的 `~\u002F.codex`。\n- 为该 profile 配置 Codex sandbox、filesystem、network 和 approval 相关安全设置。\n- 如果你的 Codex 设置支持项目级安全策略，在 workspace 的 `.codex` 中加入 override。\n- 除非你明确希望未配对 P2P 用户获得访问权限，否则保持 `permissions.p2p_default_profile = \"none\"`。\n- 除非你明确希望新群聊在第一条满足触发条件的消息上自动激活，否则保持 `permissions.group_default_profile = \"none\"` 和 `permissions.group_default_mode = \"none\"`。\n- 除非群成员范围非常可控，否则优先使用 `owner_at` 或 `owner`，不要直接使用 `all` 或 `all_at`。\n\n## 进阶配置\n\nTwinny 从 `TWINNY_HOME` 读取 `config.toml`。\n\n支持的字段：\n\n| 字段 | 含义和取值 |\n| --- | --- |\n| `[codex].binary` | Codex CLI 可执行文件路径或命令名。默认是 `codex`。如果托管服务不能通过 `PATH` 找到 Codex，建议使用绝对路径。Windows 上通常是 `codex.cmd`。 |\n| `[codex].masquerade_as_codex_cli` | 初始化 Codex app-server 时把 Twinny 的 clientInfo 伪装为 Codex TUI：`name = \"codex-tui\"`、`title = null`，`version` 从启动时运行的 `codex --version` 输出中解析。默认是 `false`。 |\n| `[lark.reaction].working` | Twinny 工作中给 Lark 消息添加的 emoji type。默认是 `JubilantRabbit`。 |\n| `[lark.reaction].queued` | 消息进入队列时添加的 Lark emoji type。默认是 `OneSecond`。 |\n| `[lark.redaction].email` | 发往 Lark 的 payload 中邮箱地址的脱敏策略。`mask` 保留域名并遮蔽邮箱用户名，例如 `alice@example.com` 会变成 `a***e@example.com`；`whitespace` 插入空格，例如 `alice @ example.com`；`none` 发送明文邮箱。飞书可能会拦截包含明文邮箱或手机号的 bot message。默认是 `mask`。 |\n| `[lark.redaction].chinese_phone_number` | 发往 Lark 的 payload 中中国手机号的脱敏策略。`mask` 保留前 3 位和后 4 位，例如 `138****5678`；`whitespace` 插入空格，例如 `138 1234 5678`；`none` 发送明文手机号。飞书可能会拦截包含明文邮箱或手机号的 bot message。默认是 `mask`。 |\n| `[permissions].p2p_default_profile` | 未配对 P2P 用户第一次给 Twinny 发消息时使用的 profile。用 `none` 表示默认拒绝，也可以填已配置的 profile 名称来自动授权。默认是 `none`。 |\n| `[permissions].p2p_default_workspace` | 新 P2P conversation 的默认 workspace 模板。支持 `{{twinny_home}}` 和 `{{conversation_key}}` 变量；默认是 `{{twinny_home}}\u002Fworkspaces\u002F{{conversation_key}}`。如果渲染后的目录已存在会直接使用，否则会创建。 |\n| `[permissions].group_default_profile` | 新群聊自动激活时使用的 profile。必须和 `group_default_mode` 同时为非 `none` 才会生效；默认是 `none`。 |\n| `[permissions].group_default_mode` | 新群聊自动激活后的群响应模式，取值为 `owner_at`、`owner`、`all_at`、`all` 或 `none`。当它和 `group_default_profile` 都不是 `none` 时，新群聊收到第一条满足该模式触发条件的消息会自动创建 workspace 并激活。默认是 `none`。 |\n| `[permissions].group_default_workspace` | 新 group conversation 的默认 workspace 模板。支持 `{{twinny_home}}` 和 `{{conversation_key}}` 变量；默认是 `{{twinny_home}}\u002Fworkspaces\u002F{{conversation_key}}`。如果渲染后的目录已存在会直接使用，否则会创建。 |\n| `[service.launchd].mode` | macOS launchd 安装位置。`gui` 使用当前 `gui\u002F\u003Cuid>` LaunchAgent（默认）；`daemon` 使用系统 LaunchDaemon。通常由 `twinny install --system-daemon` 写入。 |\n| `[service.launchd].user_name` | `mode = \"daemon\"` 时写入 plist 的 `UserName`。通常由 `twinny install --system-daemon` 自动写入当前用户。 |\n| `[profiles.\u003Cname>].codex_home` | 该 profile 使用的 `CODEX_HOME`。绝对路径会直接使用；相对路径会以 `TWINNY_HOME` 为基准解析。`host` 默认是 `~\u002F.codex`；其他 profile 未设置时继承 `host`。 |\n| `[profiles.\u003Cname>].default_model` | 该 profile 新 thread 的默认模型。`host` 默认是 `gpt-5.5`；其他 profile 未设置时继承 `host`。 |\n| `[profiles.\u003Cname>].default_effort` | 该 profile 新 thread 的默认推理强度。常见取值是 `minimal`、`low`、`medium`、`high` 和 `xhigh`；`host` 默认是 `medium`；其他 profile 未设置时继承 `host`。 |\n| `[telemetry].enabled` | 支持 telemetry 的构建使用的布尔关闭开关。设置为 `false` 会关闭事件采集。详见 [Telemetry](#telemetry)。 |\n\nTelemetry 的数据范围和关闭方式见文末的 [Telemetry](#telemetry)。\n\n示例 `config.toml`：\n\n```toml\n[codex]\nbinary = \"\u002Fopt\u002Fhomebrew\u002Fbin\u002Fcodex\"\nmasquerade_as_codex_cli = false\n\n[lark.reaction]\nworking = \"JubilantRabbit\"\nqueued = \"OneSecond\"\n\n[lark.redaction]\nemail = \"mask\"\nchinese_phone_number = \"mask\"\n\n[permissions]\np2p_default_profile = \"none\"\np2p_default_workspace = \"{{twinny_home}}\u002Fworkspaces\u002F{{conversation_key}}\"\ngroup_default_profile = \"none\"\ngroup_default_mode = \"none\"\ngroup_default_workspace = \"{{twinny_home}}\u002Fworkspaces\u002F{{conversation_key}}\"\n\n[profiles.host]\ncodex_home = \"~\u002F.codex\"\ndefault_model = \"gpt-5.5\"\ndefault_effort = \"medium\"\n\n[profiles.guest]\ncodex_home = \".\u002Fprofiles\u002Fguest-codex\"\ndefault_model = \"gpt-5.5\"\ndefault_effort = \"medium\"\n```\n\n相对路径形式的 `codex_home` 会以 `TWINNY_HOME` 为基准解析。每个 profile 会启动独立的 Codex app-server 进程，并把 `CODEX_HOME` 设置为该 profile 的 `codex_home`。\n\n修改 profile 配置后，可以在 Lark 中发送 `\u002Freload [profile]`，或者重启 daemon。\n\n## 通过 `TWINNY_HOME` 多实例部署\n\n给每个实例指定独立 home，就可以运行多个隔离的 Twinny 实例：\n\n```sh\nTWINNY_HOME=\"$HOME\u002F.twinny-work\" npx twinny@latest install\nTWINNY_HOME=\"$HOME\u002F.twinny-personal\" npx twinny@latest install\n\nTWINNY_HOME=\"$HOME\u002F.twinny-work\" npx twinny@latest status\nTWINNY_HOME=\"$HOME\u002F.twinny-personal\" npx twinny@latest logs\n```\n\n每个 home 都有独立的 config，并且需要一个独立的飞书机器人应用。\n\n## Telemetry\n\n启用 telemetry 的 Twinny 构建可能会发送匿名、best-effort 的使用和可靠性事件。这些数据用于监测产品质量、理解失败模式，并支持维护者围绕本地 agent 工作流的个人研究兴趣。\n\nTwinny 不会收集或上传对话内容和凭据。这包括 Lark 消息正文、prompt、Codex 回答、飞书\u002FLark app secret 或 token、Codex 凭据或 session token、群名、发送者名称、原始 Lark 或 Codex ID、本地路径、环境变量值、API key 以及其他 secret。install、conversation、thread、turn、sender、message、Codex binary 等标识会先在本地加 salt 并 hash，再上传 hash 后的值。\n\nTelemetry 可能包含：\n\n- 安装和启动生命周期状态、启动耗时、托管服务配置状态；\n- 运行时健康信号，例如 heartbeat、uptime、队列和 active turn 数量、内存使用量、Lark\u002FCodex ready 状态；\n- 消息路由元数据，例如 conversation type、message 或 action type、route kind、queue depth、resource count；\n- turn 元数据，例如状态、类型、模型、reasoning effort、token 数、耗时、生成图片数量，以及失败时的 error code\u002Fcategory；\n- 环境元数据，例如 Twinny、Codex、Node、OS version\u002Fplatform\u002Farch、Lark brand、profile count。\n\nTelemetry 上报失败不会影响主流程，不应阻断安装、启动、消息处理或 Codex turn。\n\n可以在 `config.toml` 中关闭 telemetry：\n\n```toml\n[telemetry]\nenabled = false\n```\n\n也可以用环境变量关闭当前进程：\n\n```sh\nTWINNY_TELEMETRY_ENABLED=false npx twinny@latest start\n```\n\n## License\n\nMIT\n","Twinny 是一个连接飞书\u002FLark 与 CodeX 的桥梁工具。它通过利用 Node.js 内置的 SQLite 模块，无需额外安装数据库驱动即可运行，并且支持跨平台部署（macOS、Linux、WSL2 和 Windows），能够自动管理后台服务或计划任务。用户可以通过简单的交互式安装过程来配置 Twinny，并且只需确保环境中有最新版本的 Node.js 和 Codex CLI 即可开始使用。此项目非常适合需要在团队协作软件中集成代码审查、开发辅助等功能的场景，特别是对于已经采用飞书\u002FLark 作为主要沟通工具的技术团队而言，Twinny 提供了一种无缝集成 CodeX 能力的方式，从而提升工作效率。","2026-06-11 04:05:26","CREATED_QUERY"]