# Remote Cli **Repository Path**: sunshinewithmoonlight/remote-cli ## Basic Information - **Project Name**: Remote Cli - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-02-28 - **Last Updated**: 2026-05-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # remote-cli `remote-cli` 用于把飞书会话桥接到本地 `Codex RPC` 服务,并提供基于 macOS `LaunchAgent` 的系统任务运行时、飞书出站发送能力和本地调试入口。 ## 当前模型 - 主服务启动 `codex app-server`,通过 `stdio` 讲 `JSON-RPC 2.0` - 每个已配置的飞书 bot 都有各自独立的 long connection,由 `BotManager` 管理 - 入站飞书会话按 `bot_app_id + chat_id` 映射到独立的 Codex active thread;`/new` 后旧 thread 会转为 retiring,继续排空迟到事件 - Codex 的 `agentMessage/delta` 会通过当前自适应出站 sender 回传飞书,默认使用 `interactive` 卡片并尽量 patch 同一张卡片 - 当前 `interactive` 出站卡片标题会使用展示态前缀:进行中为 dots spinner,完成并等待下一条用户输入时切到 `✓`;状态符号后固定一个半角空格,标题正文按 rune 截断到 `15` - `/new` 会为当前 bot/chat scope 新建 active thread,后续消息继续写入新 thread;旧 thread 仍按 thread id 接收补发 delta / completed 事件 - active thread 空闲超过 `main_chat_auto_compact_idle_timeout_s` 指定的秒数后,会通过同一 bot/chat 入站串行队列触发原生 `thread/compact/start` ## 当前能力 - 交互式配置:`remote-cli config` / `remote-cli setup` - RPC 多 bot 配置:`port`、`codex_full_access`、`claude_full_access`、`latest-reply-only`、`codex_rpc_proxy_ip`、`codex_rpc_proxy_port`、`main_chat_history_auto_resume`、`main_chat_auto_compact_idle_timeout_s`、`debug_codex_stream`、`feishu-api`、`immediately-process`、`audio-processor`、`bots[]` - 飞书文本入站到 Codex RPC - 飞书附件入站下载到运行时根目录下的 `files/` 目录 - 飞书附件可按 `immediately-process` 配置决定是立即开始本轮处理,还是先在当前 chat 下暂存等待后续文本 - 飞书对话级 `/new` 切换 - 飞书出站辅助命令:`send-feishu-text`、`send-feishu-message` - 任务运行时命令: - `task resolve` - `task create` - `task list`(`task find` 兼容别名) - `task status` - `task pause` - `task resume` - `task cancel` - `task delete` - 服务控制:`start`、`stop`、`restart`、`status`、`logs`、`autorun` - 运行路径查看:`service paths` - 健康检查端点:`/livez`、`/readyz`、`/healthz` - 本地入站调试:`inject-feishu-text` ## 当前任务链路 当前系统任务采用两阶段链路: 1. `task resolve` - 是模型参与的自然语义解析步骤 - 会在 `tasks/` 下创建新的任务草稿目录 - 会写入 `resolve.json` 与脚本草稿 - 返回 `task_id`、`task_dir`、`script_dir` - `--json` 输出顶层会直接包含 `create_spec` 2. `task create` - 是纯安装步骤,不承担 AI 语义解析 - 优先消费 `resolve.json` 中的 `create_spec` - 把 `task.json`、`dispatch.sh`、`run.sh`、plist 等产物写回同一草稿目录 - 再注册系统级 `LaunchAgent` 当前约束: - `task create` 当前只支持 `type=time` - 创建行为和执行行为是分离的 - 先完成任务创建,再由系统在计划时刻执行任务 - 相对时间任务会在创建阶段按当前时间和系统时区计算整分触发时刻 - 当前直接创建入口就是 `task resolve -> task create`;仓库根目录不再依赖内置 skill 目录 ## 构建 ```bash make test make build ``` 二进制输出到: ```bash ./bin/remote-cli ``` ## 配置 配置文件查找顺序: 1. `--config ` 2. `~/.config/remotecli.yaml` 当前仓库没有单独维护 `config.example.yaml`;完整配置文件模板以本节为准。`remote-cli config` / `remote-cli setup` 生成的是同一结构的最小可用配置,手工维护自定义配置时可从下面模板删掉不需要覆盖的可选字段。 ```yaml port: 6230 codex_full_access: true claude_full_access: true latest-reply-only: true codex_rpc_proxy_ip: "" codex_rpc_proxy_port: 0 main_chat_history_auto_resume: false main_chat_auto_compact_idle_timeout_s: 3600 debug_codex_stream: false claude-code: command: claude permission_mode: default include_partial_messages: true compact_prompt: "请将本会话压缩为一段用于继续对话的简明上下文。只输出摘要。" feishu-api: timeout_s: 8 upload_timeout_s: 120 immediately-process: image: true file: false audio: true media: false audio-processor: - doubao: false app-id: your-doubao-app-id access-token: your-doubao-access-token bots: - bot-type: feishu backend: codex id: cli_codex_placeholder secret: sec_codex_placeholder prompt_per_turn: "" - bot-type: feishu backend: claude id: cli_claude_placeholder secret: sec_claude_placeholder prompt_per_turn: "" schedule: system_tasks_root: /absolute/path/for/system-tasks ``` 说明: - `port` 缺省为 `6230` - `codex_full_access` 缺省为 `true` - `claude_full_access` 缺省为 `true` - 旧字段 `full_access` 仍可读取;当新字段缺省时会同时作为 Codex 和 Claude 的兼容默认值 - `latest-reply-only` 缺省为 `true` - `codex_rpc_proxy_ip` 缺省为空字符串 - `codex_rpc_proxy_port` 缺省为 `0` - `main_chat_history_auto_resume` 缺省时保持当前主链路行为,即临时 thread、服务重启后不自动恢复历史 - `main_chat_auto_compact_idle_timeout_s` 缺省为 `3600` - `debug_codex_stream` 缺省为 `false` - `feishu-api.timeout_s` 缺省为 `8`,用于飞书认证、发文本、发/patch 卡片、下载资源等轻量 OpenAPI 请求 - `feishu-api.upload_timeout_s` 缺省为 `120`,用于 `send-feishu-message --msg-type image|file|audio|media` 以及卡片本地图片上传;该值必须大于等于普通超时 - `immediately-process` 缺省为: - `image: true` - `file: false` - `audio: true` - `media: false` - `audio-processor` 缺省为: - 一个按顺序生效的服务项列表 - 默认写出一条 `doubao: false` 的占位项 - 当前只支持 `doubao` - `app-id` 与 `access-token` 仅在该服务项 `doubao: true` 时必填,且必须由部署时显式填写 - `bots` 至少需要一个飞书 bot - 每个 `bots[]` 条目通过 `backend` 指定该飞书 bot 使用 `codex` 或 `claude` - `backend` 缺省为 `codex` - `prompt_per_turn` 可选;非空时会在每一轮追加到 remote-cli 运行时注入之后、真实 `User input` 之前,适合放每个 bot 的短策略,不适合放长操作手册 - `bot-type` 是新字段;旧配置里的 `mode: feishu` 仍可读取但新文档不再推荐 - `schedule.system_tasks_root` 可选;为空时默认使用 `~/Library/Application Support/remote-cli` - 模板中的 `secret` 与 `access-token` 都是占位符,不要提交真实凭据 关于关键字段: ### 切换到 Claude Code 后端 ```yaml claude_full_access: true claude-code: command: claude permission_mode: bypassPermissions include_partial_messages: true bots: - bot-type: feishu backend: claude id: cli_placeholder secret: sec_placeholder ``` 切换某个 bot 的 `backend` 后重启 `remote-cli` 服务。已有 `backend=codex` 的 session registry 记录不会被 `backend=claude` 恢复;发送 `/new` 可为当前 bot/chat 创建新的 Claude Code 会话。V1 不使用 `claude --remote-control`,而是由 `remote-cli` 常驻服务按 turn 调用 Claude Code headless CLI。当 `claude_full_access: true` 时,Claude Code 后端会显式跳过权限;当 `claude_full_access: false` 且没有显式配置 `claude-code.permission_mode` 时,保持 Claude Code 默认权限模式。 - `codex_full_access: true` - Codex 后端会在 `thread/start` 与 `turn/start` 中显式传递 danger-full-access sandbox override - `codex_full_access: false` - Codex 后端不强制覆盖 sandbox,回退到 Codex app-server 的默认行为 - `claude_full_access: true` - Claude Code 后端会显式传递 `--dangerously-skip-permissions` - `claude_full_access: false` - Claude Code 后端使用 `claude-code.permission_mode` 或 Claude Code 默认权限模式 - `latest-reply-only: true` - 同一轮回复卡片实时 patch,只展示最新 `agentMessage` 内容 - `latest-reply-only: false` - 同一轮回复卡片仍实时 patch,但会累积这轮所有 `agentMessage` 内容 - `codex_rpc_proxy_ip: ""` 且 `codex_rpc_proxy_port: 0` - 不使用代理;`remote-cli` 会在启动 `codex app-server` 前显式清理继承来的代理环境变量,按直连方式运行 Codex RPC - `codex_rpc_proxy_ip` 与 `codex_rpc_proxy_port` 同时填写 - `remote-cli` 会先对 `ip:port` 做短超时 TCP 连通性探测 - 探测成功时,才会为 `codex app-server` 子进程注入 `HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY` 及其小写变体 - 同时会注入 `NO_PROXY` / `no_proxy=localhost,127.0.0.1,::1`,确保 Codex 到本机 relay 或 loopback 调试端点的请求不被代理接管 - 探测失败时,会自动回退为直连,不会因为代理不可达而阻止主服务或 `task resolve` 启动 - 当前按 HTTP 代理解释为 `http://ip:port` - 如果代理在服务运行期间中途停止,已启动的 Codex 子进程不会热更新代理环境;重启 `remote-cli` 后才会重新探测并回退直连 - `remote-cli` 当前不再提供 `fast/flex` 配置开关 - `thread/start` 与 `turn/start` 默认不显式发送 `serviceTier` - 实际使用哪一档 tier,跟随当前 `codex app-server` 默认值 - `main_chat_history_auto_resume: true` - 只作用于主对话 RPC - 主聊天 thread 使用可恢复会话语义 - `remote-cli` 会把 `(bot_app_id, chat_id)` 会话注册表写到 `/system/runtime/codex_rpc_sessions.json` - 启动时会在 `botManager.StartAll()` 前尝试 `thread/resume`,把已注册会话恢复回内存映射 - 只有在该配置开启后新创建并成功注册的主聊天 thread,才会写入本机 `~/.codex/sessions/...` 并支持后续恢复 - `main_chat_history_auto_resume: false` - 只作用于主对话 RPC - 主聊天 thread 使用临时会话语义 - 服务 `restart` 或意外退出后重新拉起时,不会自动恢复该 chat 的 Codex 历史 - `main_chat_history_auto_resume` 缺省 - 主聊天仍按 `ephemeral=true` 运行,不自动恢复历史 - `main_chat_auto_compact_idle_timeout_s: 3600` - 只作用于主对话 RPC 的 active thread - 同一条 thread 在“最近一次活跃之后”的同一段空闲窗口里,最多自动 compact 一次 - 实现使用 Codex 原生 `thread/compact/start`,并等待 `thread/compacted` - 自动 compact 会进入同一 `(bot_app_id, chat_id)` 的入站串行队列,不会绕过用户消息队列直接抢同一 thread - compact 期间同一 thread 会被标记为 `compacting`;后续用户消息仍可入队,但必须等 `thread/compacted` 或 compact 超时/失败清理后才会提交新的 `turn/start` - `main_chat_auto_compact_idle_timeout_s: 0` - 关闭主对话 idle auto-compact - `task resolve` - 始终保持 `ephemeral=true` - 不受 `main_chat_history_auto_resume` 影响 - 旧字段 `codex_rpc_ephemeral` - 已不再受支持 - 如果配置文件中仍然存在,服务启动时会直接报错并提示改用 `main_chat_history_auto_resume` - 旧字段 `codex_rpc_fast` - 已不再受支持 - 如果配置文件中仍然存在,服务启动时会直接报错并提示删除该行 - `debug_codex_stream: true` - 会把可见的 Codex 中间输出写入 `/tmp/remote-cli/remote-cli.debug.log` - 内容为 `JSONL` - 包括 `turn started`、`agentMessage/delta`、`item completed`、自动审批事件,以及镜像后的飞书 send/patch 事件 - `immediately-process` - 默认 `image` 与 `audio` 一到达就开始本轮处理 - 默认 `file` 与 `media` 仍先进入附件草稿,等待后续文本 - 如果显式覆盖某个子字段,则该类型按配置值决定是否立即处理 - `audio-processor` - 配置是有序列表,运行时只会选择顺序上最前面的一个已启用服务项 - 当前仅支持 `doubao`;后续即使增加 `ollama` 也不会做串联 fallback - 仓库示例不会内置任何固定 Doubao App ID,`app-id` 必须填成你自己的 deployment-specific 值 - 某个服务项 `doubao: true` 时,飞书 `audio` 下载后会先调用豆包流式语音识别大模型的 WebSocket 接口转录 - 当前实现按已验证可行的方案,把归一化后的整段音频作为最后一个音频包发送到 `wss://openspeech.bytedance.com/api/v3/sauc/bigmodel` - 转录成功后,送入 Codex RPC 的是普通文本消息,不再混入附件元数据与 `local_path` - 转录失败时会回退到当前附件描述路径 - 飞书语音若不是可直接使用的格式,会在 macOS 上优先调用 `ffmpeg` 转成标准 `pcm_s16le wav`;若本机没有 `ffmpeg`,再回退到 `afconvert` - 当前豆包配置示例统一写成 `app-id: your-doubao-app-id`、`access-token: your-doubao-access-token` - 当前这台机器的 20 小时时长包归属 `App ID 7499016332`;已有本机 `remotecli.yaml` 不会自动迁移,必须手动更新 - 当前按 `resource_id: volc.bigasr.sauc.duration` 接入,匹配该 App 已开通的 `流式语音识别大模型` - 如果把同一组凭据误接到 `录音文件识别` 的 `auc` 资源,会得到 `45000030 requested resource not granted` ## 交互式配置 推荐命令: ```bash ./bin/remote-cli config ``` `remote-cli setup` 是等价别名。 行为: - 真正的 TTY:使用交互表单 - 非 TTY / 管道 / 测试环境:回退到逐行文本提示 - 默认输出路径:`~/.config/remotecli.yaml` - 生成的默认配置会包含 `codex_full_access: true`、`claude_full_access: true`、`latest-reply-only: true`、空的 `codex_rpc_proxy_ip` / `codex_rpc_proxy_port`、`main_chat_history_auto_resume: false`、`main_chat_auto_compact_idle_timeout_s: 3600`、默认的 `feishu-api` 上传超时、默认的 `immediately-process`,以及一条默认禁用的 `audio-processor` 服务项 如果要使用自定义配置路径,请显式传: ```bash --config ``` ## 校验与运行 只校验配置: ```bash ./bin/remote-cli --dry-run ./bin/remote-cli --config /path/to/remotecli.yaml --dry-run ``` 前台运行服务: ```bash ./bin/remote-cli ./bin/remote-cli --config /path/to/remotecli.yaml ``` ## 服务命令 常用命令: ```bash ./bin/remote-cli autorun ./bin/remote-cli start ./bin/remote-cli restart ./bin/remote-cli stop ./bin/remote-cli status ./bin/remote-cli logs ./bin/remote-cli service paths ``` 说明: - `autorun` 会写入或刷新 `~/Library/LaunchAgents/com.remotecli.service.plist` - 如果已经安装主服务 `LaunchAgent`,后续 `start` / `restart` / `stop` / `status` 会优先操作该 `LaunchAgent` - 运行路径查看当前走 `service paths`,不是顶层 `paths` - 如果要切换 `LaunchAgent` 使用的配置文件,请重新执行 `autorun --config ` - 单独执行 `restart --config ...` 不会重写已安装 plist - 若 `main_chat_history_auto_resume: true`,`restart` 或异常退出后再次拉起服务时,会自动尝试恢复已注册主聊天会话 - 已有的历史 `ephemeral=true` thread 不会被追溯转换;要获得可恢复历史,必须在 `main_chat_history_auto_resume: true` 下新建 thread - 若旧配置仍写着 `codex_rpc_ephemeral`,当前版本会拒绝启动,必须先手工改成 `main_chat_history_auto_resume` ## 健康检查 ```bash ./bin/remote-cli health curl -sS http://127.0.0.1:6230/healthz curl -sS http://127.0.0.1:6230/livez curl -sS http://127.0.0.1:6230/readyz ``` 说明: - `/livez`:进程活着且未进入 shutdown - `/readyz`:服务已经 ready,可接业务 - `/healthz`:兼容入口,当前语义与 `/readyz` 相同 - JSON 里仍保留历史字段名 `main_session_running`,但现在它表示主服务是否健康,不再表示 tmux 会话是否存在 运维上应优先相信: - `./bin/remote-cli health` - `/readyz` - 实际监听端口 - 对应 `LaunchAgent` 状态 不要再把: - 单独存在的 pid - 旧 tmux session - 遗留 helper script 当成唯一事实源。 ## 最小烟测 1. 构建:`make build` 2. 执行配置:`./bin/remote-cli config` 3. 校验配置:`./bin/remote-cli --dry-run` 4. 启动服务:`./bin/remote-cli` 或 `./bin/remote-cli autorun` 5. 检查健康:`./bin/remote-cli health` 6. 从飞书发一条普通消息,确认收到回复 7. 从飞书发送 `/new`,确认后续回复进入新 thread,且旧 thread 的尾部输出不会串到新卡片 8. 如需验证本地记忆接入,检查 Claude Code 的 `UserPromptSubmit` Hook 是否返回 `hookSpecificOutput.additionalContext`;remote-cli 自身不再注入本地记忆包装标签,`remote-cli.debug.log` 中也不应再出现旧包装标签 9. 如需验证 idle auto-compact,可临时把 `main_chat_auto_compact_idle_timeout_s` 调小到几十秒对应的整数值,例如 `30`,等待空闲后检查 `codex_thread_auto_compact_started` / `codex_thread_compacted`,并确认 compact 期间到达的下一条用户消息在 `thread/compacted` 后才启动 `turn/start` ## 任务链路烟测 解析任务: ```bash ./bin/remote-cli task resolve --request '5分钟后给当前会话发送一条测试消息' --json ``` 预期: - 返回顶层 `create_spec` - 返回 `task_id`、`task_dir`、`script_dir` - 对应草稿目录下出现 `resolve.json` 和脚本草稿 创建任务: ```bash ./bin/remote-cli task create --task-dir /path/to/task_dir --json ``` 预期: - 返回安装成功 - 同一草稿目录下出现 `task.json`、`dispatch.sh`、`run.sh`、plist - 之后才能进入计划时刻执行,而不是在创建链路里直接执行 ## 飞书辅助命令 发送纯文本: ```bash ./bin/remote-cli send-feishu-text --text "hello" ``` 发送结构化飞书消息: ```bash ./bin/remote-cli send-feishu-message --msg-type file --local-file /absolute/path/to/report.pdf --file-type stream ./bin/remote-cli send-feishu-message --msg-type interactive --content-file /tmp/card.json ``` 对系统任务 bundle 来说,运行时参数当前按以下顺序解析: 1. 显式 flag 2. `REMOTE_CLI_TASK_DIR` 指向的 bundle `task.json` 3. 兼容性 env 回退: - `REMOTE_CLI_CONFIG` - `REMOTE_CLI_CHAT_ID` - `REMOTE_CLI_BOT_APP_ID` 4. CLI 内部最后回退: - 默认配置 `~/.config/remotecli.yaml` - 当前主会话快照里的 chat/bot 上下文 `task.json` 会持久化这些非敏感运行时上下文: - `chat_id` - `bot_app_id` - `config_path` - `system_tasks_root` 如果你手工执行飞书任务创建: - `task create --config ` 必须使用和任务命令里 `bot_id` 对应的同一份配置文件 - 正常使用 `send-feishu-*` 时不需要手工读取 `current_chat.json`;CLI 会在内部做最后回退 ## 本地入站注入 为了本地烟测,当前提供最小入站调试入口: ```bash ./bin/remote-cli inject-feishu-text --text "请创建一个明天上午 9:30 给当前对话发送提醒的任务" ``` 行为: - CLI 会向 `POST /debug/inject-feishu-text` 发送本地 HTTP 请求 - 该调试端点只允许 loopback 调用 - 缺失的 `chat_id`、`bot_app_id`、`sender_id` 会优先从 `system/runtime/current_chat.json` 回填 - 服务会先更新 `current_chat.json`,再把合成后的入站消息送入当前进程内的 `codexRPCBridge` 这意味着: - 会复用真实的 thread 映射 - 会复用真实的飞书出站 sender - 适合验证“创建任务”这类真实入站链路 - 它不是公开 API,也不是单独的一套 mock runtime ## 飞书附件落盘 当前飞书附件会优先保存到运行时根目录下的 `files/` 目录: ```bash ~/Library/Application Support/remote-cli/files ``` 如果配置了自定义 `schedule.system_tasks_root`,则会保存到: ```bash /files ``` 当前命名策略: - 文件名包含 `message_id` 和 `resource_key` - 会尽量保留原始文件名的可读部分 - 同名附件不会互相覆盖 - 当前仍需要“附件后的下一条文本”来真正提交给 Codex 常见排障路径: ```bash find "$HOME/Library/Application Support/remote-cli/files" -maxdepth 1 -type f | sort | tail -n 20 tail -n 200 /tmp/remote-cli/remote-cli.error.log | rg "attachment|download|local_path|files/" ``` ## 读取 Codex 中间输出 如果 `debug_codex_stream: true`,可以直接查看: ```bash /tmp/remote-cli/remote-cli.debug.log ``` 常见过滤方式: ```bash rg -n 'codex_turn_started|codex_item_started|codex_agent_message_delta|codex_item_completed|codex_turn_completed|codex_turn_completed_empty_output|codex_thread_auto_compact_started|codex_thread_compacted' /tmp/remote-cli/remote-cli.debug.log ``` 如果要绑定到具体一次请求,可按 `turn_id`、`thread_id` 或消息 id 过滤。 如果日志里出现 `[rpc_forwarder] unknown thread for delta|item|turn|compact`,应优先按 thread 映射断裂、旧 thread 过早回收或状态同步缺失排障;这不是预期中的常规噪音。 如果 Codex 发出 `turn/completed` 但本轮没有任何可见 assistant 输出,remote-cli 会记录 `codex_turn_completed_empty_output`,并向飞书发送兜底提示,避免用户侧静默无回复。自动 compact 触发的空完成只用于释放 compact 等待,不发送兜底卡片。 遇到“本轮 Codex 已结束但没有生成可见回复”时,先看 `/tmp/remote-cli/remote-cli.error.log` 里的 `status`。如果是 `failed`,继续查 `~/.codex/logs_2.sqlite` 对应 `thread_id` / `turn_id` 的 Codex 日志;常见根因是 Codex 采样请求失败,例如本机 relay `127.0.0.1:8788` 被代理错误接管,或代理端口在运行中停止。 ## 文档入口 - [docs/index.md](docs/index.md) - [docs/quick-deploy.md](docs/quick-deploy.md) - [docs/architecture.md](docs/architecture.md) - [docs/technical-details/README.md](docs/technical-details/README.md)