ai-cli-gateway 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 thepower
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-cli-gateway
3
+ Version: 1.1.0
4
+ Summary: 统一 CLI 进程调用网关:全量日志落盘 + 回放 + 结构化返回——AI 调 CLI 的可观测层
5
+ Author: ningL32
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ningL32/cli-gateway
8
+ Project-URL: Changelog, https://github.com/ningL32/cli-gateway/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/ningL32/cli-gateway/issues
10
+ Keywords: subprocess,cli,gateway,audit,observability,ai-agents
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: System :: Monitoring
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # cli-gateway
31
+
32
+ 统一 CLI 进程调用网关:让"AI 调 CLI 是否真跑、输出能否逐字核对"变得可观测、可审计、可回放。
33
+
34
+ **v1 定位**:本地 subprocess 薄包装。不涉及任何 URL/网络请求。
35
+
36
+ ## 为什么
37
+
38
+ 团队有 9 个 CLI(claude / codex / kilo / mcode / goose / opencode / qoder / crush / hermes)作为 agent 运行时。痛点:
39
+ - 调用了但无法直接观测原始 stdout/stderr,只能靠进程日志+时间戳佐证"跑了";
40
+ - 无法逐字核对输出、无法回放、无法统一审计。
41
+
42
+ `cli-gateway` 把任何 CLI 调用包装成"全量日志落盘 + 可回放 + 结构化返回",让每次调用可观测、可审计、可验证。
43
+
44
+ ## 快速开始
45
+
46
+ ```bash
47
+ pip install ai-cli-gateway # PyPI 安装(PyPI 上 `cli-gateway` 名字已被占,故用此名)
48
+ pip install -e . # 或本仓开发安装(或直接 PYTHONPATH=.)
49
+
50
+ # 作为库
51
+ from cli_gateway import run, replay
52
+ res = run(["claude", "--version"])
53
+ print(res.exit_code, res.duration_ms, res.log_path)
54
+ print(replay(res.log_path)) # 逐字回放
55
+
56
+ # 作为 CLI
57
+ # 注意:--timeout/--dir 必须写在 -- 之前;-- 之后的参数全部属于被调命令
58
+ cli-gateway run --timeout 10 -- echo hello
59
+ cli-gateway replay ~/.cli-gateway/logs/20261018-190000-echo-abc12345.log
60
+ cli-gateway logs
61
+ cli-gateway doctor
62
+ ```
63
+
64
+ ## 返回结构(GatewayResult)
65
+
66
+ | 字段 | 说明 |
67
+ |---|---|
68
+ | `command` | 原始命令 |
69
+ | `exit_code` | 退出码:命令未找到=**127**,启动失败(不可执行等)=**126**,超时=**124**,其余为命令真实退出码(POSIX 被信号杀死时为负信号值) |
70
+ | `duration_ms` | 耗时毫秒 |
71
+ | `log_path` | 全量日志文件路径 |
72
+ | `stdout_tail` / `stderr_tail` | 输出尾部(默认各 2000 字符,从日志文件取尾,内存有界) |
73
+ | `timestamp` / `pid` / `success` | 时间戳 / 真实进程号 / 是否成功 |
74
+ | `state` | 枚举:`completed` / `timed_out` / `not_found` / `spawn_error` / `killed`(spawn_error 是对 shell 惯例枚举的扩展) |
75
+ | `cwd` | 本次调用的 working directory(未传为 None) |
76
+ | `truncated` | 输出超过 tail 长度被截断时为 True |
77
+ | `timed_out` | 超时标志(等价 `state == "timed_out"`) |
78
+ | `env_overrides` | 显式传入的 env 覆盖项(含 KEY/TOKEN/SECRET/PASSWORD 的值脱敏为 `***`,不记录全量环境) |
79
+
80
+ ## 日志格式
81
+
82
+ 每次调用写一个文件 `<ts>-<cmd>-<id>.log`:元数据头(command/timestamp/pid/cwd/env_overrides/timeout)+ STDOUT 段 + STDERR 段 + END + 结果尾(exit_code/duration_ms/state),可逐字回放、可审计。超时路径保留已产生的部分输出(partial)。
83
+
84
+ ## 注册表(run_cli,白名单路径)
85
+
86
+ ```python
87
+ from cli_gateway import REGISTRY, CliSpec, run_cli
88
+
89
+ REGISTRY["claude"] = CliSpec(default_timeout=600, tail=2000) # 零依赖顶层 dict
90
+ res = run_cli("claude", "--version") # 未注册名字 → KeyError;run() 不查注册表
91
+ ```
92
+
93
+ `CliSpec` 字段:`default_timeout` / `capture` / `tail` / `cwd` / `env` / `retry_on`。重试只在显式传 `retry_count` 时发生,且只重试"启动前失败"(`not_found` / `spawn_error`;超时与非零退出绝不自动重试),每次尝试独立日志文件。`retry_on` 字段当前仅支持 `"spawn"`,其他值保留但不生效。
94
+
95
+ ## Windows 须知:npm shim 与直调
96
+
97
+ Windows 上 `Popen` 直控走 `CreateProcess`,只解析 `.exe`——npm 全局安装的 CLI(claude / kilo / mcode / opencode 等)目录里只有 `xxx.cmd` shim,直接传名字会得到 `127/not_found`。两种调用方式:
98
+
99
+ - **原生 exe 直调**:codex / goose / qoderclicn / hermes;crush 的 npm shim 没有 `.exe` 同名文件,`CreateProcess` 会继续搜 PATH 命中 WinGet 的 `crush.exe`,同样直调可用;
100
+ - **npm shim 用 `cmd /c <name> ...` 包装**:gateway 的 `taskkill /T` 树杀仍覆盖 cmd 与其下的整棵进程树。
101
+
102
+ Git Bash 传参注意:MSYS 会把独立参数 `/c` 转换成 `C:\`,命令行路径须写 `cmd //c` 或设 `MSYS_NO_PATHCONV=1`;走 Python 库传 argv 列表不受影响。
103
+
104
+ ## 真机验证矩阵(2026-10-18,Windows Server 2022)
105
+
106
+ 9 个 CLI 全部经网关真实验证:`--version` 探测 + 真实模型调用(提示词"只回复两个字:收到")各一次。
107
+
108
+ | CLI | 版本 | 直调方式 | 非交互用法 | 真实调用 |
109
+ |---|---|---|---|---|
110
+ | claude | 2.1.177 | `cmd /c claude` | `claude -p "<prompt>"` | ✅ |
111
+ | codex | 0.162.1 | 直调 exe | `codex exec "<prompt>"` | ✅ |
112
+ | kilo | 7.7.5 | `cmd /c kilo` | `kilo run "<prompt>"` | ✅ |
113
+ | mcode | 0.4.12 | `cmd /c mcode` | `mcode exec "<prompt>"` | ✅ |
114
+ | goose | 1.51.0 | 直调 exe | `goose run -t "<prompt>"` | ✅ |
115
+ | opencode | 1.18.31 | `cmd /c opencode` | `opencode run "<prompt>"` | ✅ |
116
+ | qoder | 1.1.72 | 直调 exe(命令名 `qoderclicn`) | `qoderclicn -p "<prompt>"` | ✅ |
117
+ | crush | v0.88.0 | 直调 exe(自动命中 WinGet 版) | `crush run "<prompt>"` | ✅ |
118
+ | hermes | v0.21.3 | 直调 exe | `hermes -z "<prompt>"` | ✅ |
119
+
120
+ 矩阵可在你自己的机器上实时复现:
121
+
122
+ ```bash
123
+ cli-gateway doctor # 体检内置 9 个编码 CLI:形态/版本/日志路径
124
+ cli-gateway doctor --json # 结构化输出(agent 消费友好)
125
+ cli-gateway doctor --no-probe # 静态快查:只看安装形态,不实跑版本探测
126
+ cli-gateway doctor --only codex # 只查指定命令
127
+ ```
128
+
129
+ 形态判定以网关真实调用结果为准(先直调,Windows 下 not_found 自动再试 `cmd /c` 包装),不是静态猜测;全部可用退出码 0,否则 1——可直接接进 CI 或 agent 的启动自检。
130
+
131
+ ## 子智能体编队(agents/)
132
+
133
+ [agents/](agents/) 内置 9 个编码 CLI 的 **ZCode 子代理操作员提示词**——上表 9 个 CLI 各配一个"专属操作员":主会话(工头)负责拆解派工与验收,子代理通过 Bash 调用对应 CLI 真进程干活。方法论(C4/ADR/TDD/变异测试/八维审查等)全部内置在提示词里,开箱即用。
134
+
135
+ | 文件 | 角色 | 擅长 |
136
+ |---|---|---|
137
+ | [claude.md](agents/claude.md) | 首席架构 | C4/ADR 架构规格、多维评审、技术决策 |
138
+ | [codex.md](agents/codex.md) | 资深攻坚 | 五 Why 根因、性能剖析、Strangler Fig 重构 |
139
+ | [opencode.md](agents/opencode.md) | 全栈 | React/Next + Node/Python + DB 前后端通吃 |
140
+ | [mcode.md](agents/mcode.md) | 功能实现 | TDD 红绿重构一棒到底 |
141
+ | [kilo.md](agents/kilo.md) | 功能实现 | 补位实现、修测试、环境坑 |
142
+ | [crush.md](agents/crush.md) | 功能实现 | 快速编码、LSP 语义辅助 |
143
+ | [goose.md](agents/goose.md) | QA | 测试金字塔、Playwright 真机验证、变异测试防假绿 |
144
+ | [qoder.md](agents/qoder.md) | 代码评审 | 八维审查、异构模型交叉复核 |
145
+ | [hermes.md](agents/hermes.md) | 研究员 | 技术调研、复盘 postmortem、经验库回写 |
146
+
147
+ ### 安装
148
+
149
+ ```bash
150
+ # 用户级(所有项目生效)
151
+ cp agents/*.md ~/.zcode/agents/
152
+
153
+ # 或项目级(仅当前项目生效)
154
+ cp agents/*.md .zcode/agents/
155
+ ```
156
+
157
+ 重启 ZCode 会话后生效,Agent 工具即可按 `name` 派工(如 `subagent_type: "codex"`)。其他支持自定义子代理的框架同样可用:这些 `.md` 本质是"角色化操作员提示词 + 调用配方",粘贴为任意 agent 的系统提示词即可复用。
158
+
159
+ ### 使用
160
+
161
+ 主会话按岗位派工,派单三要素(目标/范围/完成标准)写清,例如:
162
+
163
+ ```markdown
164
+ # 任务
165
+ 修复 tests/test_api.py 中 3 个失败用例(并发写竞态)
166
+
167
+ # 完成标准
168
+ pytest 全绿 + 亲自运行过验证命令
169
+
170
+ # 约束
171
+ 只改 src/worker.py,不动接口签名
172
+ ```
173
+
174
+ 子代理按固定三段式汇报(修改了什么 / 如何验证 / 哪些仍未验证);经 cli-gateway 留证调用的,汇报附 `log=` 路径,验收方 `cli-gateway replay <log>` 逐字回放复核——**验收不轻信汇报文字**。
175
+
176
+ ### 提示词结构(自定义时照此改)
177
+
178
+ 每个文件的固定骨架:`frontmatter`(`name` 派工标识 / `description` 主会话路由依据 / `tools` 工具白名单 / `injectAgentsMd`)+ 正文(岗位定位与方法论 / 完成条件 / 预检三问 / 固定汇报格式 / 退化路径 / 调用方式含 cli-gateway 留证配方 / 红线 / 实测档案)。
179
+
180
+ ### 定制指引
181
+
182
+ - **CLI 名称/版本不同**:改「调用方式」节的命令与核对版本即可,方法论不绑定具体 CLI
183
+ - **后端不是 GLM**:这些操作员原本跑在 GLM 后端,换 provider 只需改 CLI 侧的 provider 配置,提示词无需动
184
+ - **POSIX 环境**:调用配方中 `cmd //c` 包装与 MSYS 注意事项仅适用 Windows + Git Bash,POSIX 直接写命令名
185
+ - **开源版说明**:`agents/` 为脱敏模板(团队名/内网配置/实测记录已替换为占位或通用表述),各文件末尾的「实测档案」留作你的团队自行回写
186
+
187
+ ## 超时与进程组
188
+
189
+ 超时(`timeout` 秒,默认无限等)会按进程组/进程树终止:POSIX 用 `start_new_session` + `os.killpg`(先 SIGTERM,宽限 2s 后 SIGKILL);Windows 用 `CREATE_NEW_PROCESS_GROUP` + `taskkill /F /T`。这会大幅降低孤儿孙进程残留的概率,但不是绝对保证——极端场景(第三方进程自行脱离进程组/进程树、或孙进程继承管道写句柄导致输出流不关闭)可能有残留或输出不完整,此时结果会置 `truncated=True` 并在日志尾部追加 `# warning:` 行,由调用方按需兜底。
190
+
191
+ POSIX 下进程被信号杀死时 `state="killed"`、`exit_code` 为负信号值;Windows 正常退出(含非零退出码)均为 `completed`。
192
+
193
+ 输出读取统一按 UTF-8(`errors="replace"`),日志始终是合法 UTF-8。gateway 默认向子进程注入 `PYTHONUTF8=1`(调用方或父环境已显式设置该变量时不覆盖),使 python 类子进程在 Windows 传统代码页(CP936 等)环境下也输出 UTF-8——注意子进程会看到这个变量。
194
+
195
+ ## 设计边界(v1)
196
+
197
+ - **只做本地 subprocess**,不发起 URL/网络请求——未来 v2 若加远程/HTTP 能力,服务端请求 URL 时仅允许 http/https,且发请求前校验 host、拒绝 localhost/环回/私有/保留地址;
198
+ - 不做沙箱(用 Docker 的 MCP 网关管沙箱,见下);不做 Web UI;不做多租户。
199
+
200
+ ## 与 docker/mcp-gateway 的关系(不同层)
201
+
202
+ | | docker/mcp-gateway | cli-gateway |
203
+ |---|---|---|
204
+ | 管 | MCP 服务器(外部工具数据源,Docker 容器) | CLI 进程调用(本地 subprocess) |
205
+ | 解决 | AI 统一访问 MCP 服务器 + 认证/密钥 | CLI 调用日志落盘/回放/结构化返回 |
206
+ | 层 | 协议层(MCP) | 进程层(subprocess) |
207
+
208
+ 两者互补不冲突:MCP 网关管"AI 连外部工具",cli-gateway 管"AI 调本地 CLI"。
209
+
210
+ ## License
211
+
212
+ MIT
213
+
214
+ ## 安全约束(Mimosa)
215
+
216
+ 本仓库 v1 不发起任何网络请求。若未来扩展远程调用,必须遵守:仅允许 http/https;发请求前校验 host,拒绝 localhost、环回(127.0.0.0/8, ::1)、私有(RFC1918)与保留地址。
@@ -0,0 +1,187 @@
1
+ # cli-gateway
2
+
3
+ 统一 CLI 进程调用网关:让"AI 调 CLI 是否真跑、输出能否逐字核对"变得可观测、可审计、可回放。
4
+
5
+ **v1 定位**:本地 subprocess 薄包装。不涉及任何 URL/网络请求。
6
+
7
+ ## 为什么
8
+
9
+ 团队有 9 个 CLI(claude / codex / kilo / mcode / goose / opencode / qoder / crush / hermes)作为 agent 运行时。痛点:
10
+ - 调用了但无法直接观测原始 stdout/stderr,只能靠进程日志+时间戳佐证"跑了";
11
+ - 无法逐字核对输出、无法回放、无法统一审计。
12
+
13
+ `cli-gateway` 把任何 CLI 调用包装成"全量日志落盘 + 可回放 + 结构化返回",让每次调用可观测、可审计、可验证。
14
+
15
+ ## 快速开始
16
+
17
+ ```bash
18
+ pip install ai-cli-gateway # PyPI 安装(PyPI 上 `cli-gateway` 名字已被占,故用此名)
19
+ pip install -e . # 或本仓开发安装(或直接 PYTHONPATH=.)
20
+
21
+ # 作为库
22
+ from cli_gateway import run, replay
23
+ res = run(["claude", "--version"])
24
+ print(res.exit_code, res.duration_ms, res.log_path)
25
+ print(replay(res.log_path)) # 逐字回放
26
+
27
+ # 作为 CLI
28
+ # 注意:--timeout/--dir 必须写在 -- 之前;-- 之后的参数全部属于被调命令
29
+ cli-gateway run --timeout 10 -- echo hello
30
+ cli-gateway replay ~/.cli-gateway/logs/20261018-190000-echo-abc12345.log
31
+ cli-gateway logs
32
+ cli-gateway doctor
33
+ ```
34
+
35
+ ## 返回结构(GatewayResult)
36
+
37
+ | 字段 | 说明 |
38
+ |---|---|
39
+ | `command` | 原始命令 |
40
+ | `exit_code` | 退出码:命令未找到=**127**,启动失败(不可执行等)=**126**,超时=**124**,其余为命令真实退出码(POSIX 被信号杀死时为负信号值) |
41
+ | `duration_ms` | 耗时毫秒 |
42
+ | `log_path` | 全量日志文件路径 |
43
+ | `stdout_tail` / `stderr_tail` | 输出尾部(默认各 2000 字符,从日志文件取尾,内存有界) |
44
+ | `timestamp` / `pid` / `success` | 时间戳 / 真实进程号 / 是否成功 |
45
+ | `state` | 枚举:`completed` / `timed_out` / `not_found` / `spawn_error` / `killed`(spawn_error 是对 shell 惯例枚举的扩展) |
46
+ | `cwd` | 本次调用的 working directory(未传为 None) |
47
+ | `truncated` | 输出超过 tail 长度被截断时为 True |
48
+ | `timed_out` | 超时标志(等价 `state == "timed_out"`) |
49
+ | `env_overrides` | 显式传入的 env 覆盖项(含 KEY/TOKEN/SECRET/PASSWORD 的值脱敏为 `***`,不记录全量环境) |
50
+
51
+ ## 日志格式
52
+
53
+ 每次调用写一个文件 `<ts>-<cmd>-<id>.log`:元数据头(command/timestamp/pid/cwd/env_overrides/timeout)+ STDOUT 段 + STDERR 段 + END + 结果尾(exit_code/duration_ms/state),可逐字回放、可审计。超时路径保留已产生的部分输出(partial)。
54
+
55
+ ## 注册表(run_cli,白名单路径)
56
+
57
+ ```python
58
+ from cli_gateway import REGISTRY, CliSpec, run_cli
59
+
60
+ REGISTRY["claude"] = CliSpec(default_timeout=600, tail=2000) # 零依赖顶层 dict
61
+ res = run_cli("claude", "--version") # 未注册名字 → KeyError;run() 不查注册表
62
+ ```
63
+
64
+ `CliSpec` 字段:`default_timeout` / `capture` / `tail` / `cwd` / `env` / `retry_on`。重试只在显式传 `retry_count` 时发生,且只重试"启动前失败"(`not_found` / `spawn_error`;超时与非零退出绝不自动重试),每次尝试独立日志文件。`retry_on` 字段当前仅支持 `"spawn"`,其他值保留但不生效。
65
+
66
+ ## Windows 须知:npm shim 与直调
67
+
68
+ Windows 上 `Popen` 直控走 `CreateProcess`,只解析 `.exe`——npm 全局安装的 CLI(claude / kilo / mcode / opencode 等)目录里只有 `xxx.cmd` shim,直接传名字会得到 `127/not_found`。两种调用方式:
69
+
70
+ - **原生 exe 直调**:codex / goose / qoderclicn / hermes;crush 的 npm shim 没有 `.exe` 同名文件,`CreateProcess` 会继续搜 PATH 命中 WinGet 的 `crush.exe`,同样直调可用;
71
+ - **npm shim 用 `cmd /c <name> ...` 包装**:gateway 的 `taskkill /T` 树杀仍覆盖 cmd 与其下的整棵进程树。
72
+
73
+ Git Bash 传参注意:MSYS 会把独立参数 `/c` 转换成 `C:\`,命令行路径须写 `cmd //c` 或设 `MSYS_NO_PATHCONV=1`;走 Python 库传 argv 列表不受影响。
74
+
75
+ ## 真机验证矩阵(2026-10-18,Windows Server 2022)
76
+
77
+ 9 个 CLI 全部经网关真实验证:`--version` 探测 + 真实模型调用(提示词"只回复两个字:收到")各一次。
78
+
79
+ | CLI | 版本 | 直调方式 | 非交互用法 | 真实调用 |
80
+ |---|---|---|---|---|
81
+ | claude | 2.1.177 | `cmd /c claude` | `claude -p "<prompt>"` | ✅ |
82
+ | codex | 0.162.1 | 直调 exe | `codex exec "<prompt>"` | ✅ |
83
+ | kilo | 7.7.5 | `cmd /c kilo` | `kilo run "<prompt>"` | ✅ |
84
+ | mcode | 0.4.12 | `cmd /c mcode` | `mcode exec "<prompt>"` | ✅ |
85
+ | goose | 1.51.0 | 直调 exe | `goose run -t "<prompt>"` | ✅ |
86
+ | opencode | 1.18.31 | `cmd /c opencode` | `opencode run "<prompt>"` | ✅ |
87
+ | qoder | 1.1.72 | 直调 exe(命令名 `qoderclicn`) | `qoderclicn -p "<prompt>"` | ✅ |
88
+ | crush | v0.88.0 | 直调 exe(自动命中 WinGet 版) | `crush run "<prompt>"` | ✅ |
89
+ | hermes | v0.21.3 | 直调 exe | `hermes -z "<prompt>"` | ✅ |
90
+
91
+ 矩阵可在你自己的机器上实时复现:
92
+
93
+ ```bash
94
+ cli-gateway doctor # 体检内置 9 个编码 CLI:形态/版本/日志路径
95
+ cli-gateway doctor --json # 结构化输出(agent 消费友好)
96
+ cli-gateway doctor --no-probe # 静态快查:只看安装形态,不实跑版本探测
97
+ cli-gateway doctor --only codex # 只查指定命令
98
+ ```
99
+
100
+ 形态判定以网关真实调用结果为准(先直调,Windows 下 not_found 自动再试 `cmd /c` 包装),不是静态猜测;全部可用退出码 0,否则 1——可直接接进 CI 或 agent 的启动自检。
101
+
102
+ ## 子智能体编队(agents/)
103
+
104
+ [agents/](agents/) 内置 9 个编码 CLI 的 **ZCode 子代理操作员提示词**——上表 9 个 CLI 各配一个"专属操作员":主会话(工头)负责拆解派工与验收,子代理通过 Bash 调用对应 CLI 真进程干活。方法论(C4/ADR/TDD/变异测试/八维审查等)全部内置在提示词里,开箱即用。
105
+
106
+ | 文件 | 角色 | 擅长 |
107
+ |---|---|---|
108
+ | [claude.md](agents/claude.md) | 首席架构 | C4/ADR 架构规格、多维评审、技术决策 |
109
+ | [codex.md](agents/codex.md) | 资深攻坚 | 五 Why 根因、性能剖析、Strangler Fig 重构 |
110
+ | [opencode.md](agents/opencode.md) | 全栈 | React/Next + Node/Python + DB 前后端通吃 |
111
+ | [mcode.md](agents/mcode.md) | 功能实现 | TDD 红绿重构一棒到底 |
112
+ | [kilo.md](agents/kilo.md) | 功能实现 | 补位实现、修测试、环境坑 |
113
+ | [crush.md](agents/crush.md) | 功能实现 | 快速编码、LSP 语义辅助 |
114
+ | [goose.md](agents/goose.md) | QA | 测试金字塔、Playwright 真机验证、变异测试防假绿 |
115
+ | [qoder.md](agents/qoder.md) | 代码评审 | 八维审查、异构模型交叉复核 |
116
+ | [hermes.md](agents/hermes.md) | 研究员 | 技术调研、复盘 postmortem、经验库回写 |
117
+
118
+ ### 安装
119
+
120
+ ```bash
121
+ # 用户级(所有项目生效)
122
+ cp agents/*.md ~/.zcode/agents/
123
+
124
+ # 或项目级(仅当前项目生效)
125
+ cp agents/*.md .zcode/agents/
126
+ ```
127
+
128
+ 重启 ZCode 会话后生效,Agent 工具即可按 `name` 派工(如 `subagent_type: "codex"`)。其他支持自定义子代理的框架同样可用:这些 `.md` 本质是"角色化操作员提示词 + 调用配方",粘贴为任意 agent 的系统提示词即可复用。
129
+
130
+ ### 使用
131
+
132
+ 主会话按岗位派工,派单三要素(目标/范围/完成标准)写清,例如:
133
+
134
+ ```markdown
135
+ # 任务
136
+ 修复 tests/test_api.py 中 3 个失败用例(并发写竞态)
137
+
138
+ # 完成标准
139
+ pytest 全绿 + 亲自运行过验证命令
140
+
141
+ # 约束
142
+ 只改 src/worker.py,不动接口签名
143
+ ```
144
+
145
+ 子代理按固定三段式汇报(修改了什么 / 如何验证 / 哪些仍未验证);经 cli-gateway 留证调用的,汇报附 `log=` 路径,验收方 `cli-gateway replay <log>` 逐字回放复核——**验收不轻信汇报文字**。
146
+
147
+ ### 提示词结构(自定义时照此改)
148
+
149
+ 每个文件的固定骨架:`frontmatter`(`name` 派工标识 / `description` 主会话路由依据 / `tools` 工具白名单 / `injectAgentsMd`)+ 正文(岗位定位与方法论 / 完成条件 / 预检三问 / 固定汇报格式 / 退化路径 / 调用方式含 cli-gateway 留证配方 / 红线 / 实测档案)。
150
+
151
+ ### 定制指引
152
+
153
+ - **CLI 名称/版本不同**:改「调用方式」节的命令与核对版本即可,方法论不绑定具体 CLI
154
+ - **后端不是 GLM**:这些操作员原本跑在 GLM 后端,换 provider 只需改 CLI 侧的 provider 配置,提示词无需动
155
+ - **POSIX 环境**:调用配方中 `cmd //c` 包装与 MSYS 注意事项仅适用 Windows + Git Bash,POSIX 直接写命令名
156
+ - **开源版说明**:`agents/` 为脱敏模板(团队名/内网配置/实测记录已替换为占位或通用表述),各文件末尾的「实测档案」留作你的团队自行回写
157
+
158
+ ## 超时与进程组
159
+
160
+ 超时(`timeout` 秒,默认无限等)会按进程组/进程树终止:POSIX 用 `start_new_session` + `os.killpg`(先 SIGTERM,宽限 2s 后 SIGKILL);Windows 用 `CREATE_NEW_PROCESS_GROUP` + `taskkill /F /T`。这会大幅降低孤儿孙进程残留的概率,但不是绝对保证——极端场景(第三方进程自行脱离进程组/进程树、或孙进程继承管道写句柄导致输出流不关闭)可能有残留或输出不完整,此时结果会置 `truncated=True` 并在日志尾部追加 `# warning:` 行,由调用方按需兜底。
161
+
162
+ POSIX 下进程被信号杀死时 `state="killed"`、`exit_code` 为负信号值;Windows 正常退出(含非零退出码)均为 `completed`。
163
+
164
+ 输出读取统一按 UTF-8(`errors="replace"`),日志始终是合法 UTF-8。gateway 默认向子进程注入 `PYTHONUTF8=1`(调用方或父环境已显式设置该变量时不覆盖),使 python 类子进程在 Windows 传统代码页(CP936 等)环境下也输出 UTF-8——注意子进程会看到这个变量。
165
+
166
+ ## 设计边界(v1)
167
+
168
+ - **只做本地 subprocess**,不发起 URL/网络请求——未来 v2 若加远程/HTTP 能力,服务端请求 URL 时仅允许 http/https,且发请求前校验 host、拒绝 localhost/环回/私有/保留地址;
169
+ - 不做沙箱(用 Docker 的 MCP 网关管沙箱,见下);不做 Web UI;不做多租户。
170
+
171
+ ## 与 docker/mcp-gateway 的关系(不同层)
172
+
173
+ | | docker/mcp-gateway | cli-gateway |
174
+ |---|---|---|
175
+ | 管 | MCP 服务器(外部工具数据源,Docker 容器) | CLI 进程调用(本地 subprocess) |
176
+ | 解决 | AI 统一访问 MCP 服务器 + 认证/密钥 | CLI 调用日志落盘/回放/结构化返回 |
177
+ | 层 | 协议层(MCP) | 进程层(subprocess) |
178
+
179
+ 两者互补不冲突:MCP 网关管"AI 连外部工具",cli-gateway 管"AI 调本地 CLI"。
180
+
181
+ ## License
182
+
183
+ MIT
184
+
185
+ ## 安全约束(Mimosa)
186
+
187
+ 本仓库 v1 不发起任何网络请求。若未来扩展远程调用,必须遵守:仅允许 http/https;发请求前校验 host,拒绝 localhost、环回(127.0.0.0/8, ::1)、私有(RFC1918)与保留地址。
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-cli-gateway
3
+ Version: 1.1.0
4
+ Summary: 统一 CLI 进程调用网关:全量日志落盘 + 回放 + 结构化返回——AI 调 CLI 的可观测层
5
+ Author: ningL32
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ningL32/cli-gateway
8
+ Project-URL: Changelog, https://github.com/ningL32/cli-gateway/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/ningL32/cli-gateway/issues
10
+ Keywords: subprocess,cli,gateway,audit,observability,ai-agents
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: System :: Monitoring
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # cli-gateway
31
+
32
+ 统一 CLI 进程调用网关:让"AI 调 CLI 是否真跑、输出能否逐字核对"变得可观测、可审计、可回放。
33
+
34
+ **v1 定位**:本地 subprocess 薄包装。不涉及任何 URL/网络请求。
35
+
36
+ ## 为什么
37
+
38
+ 团队有 9 个 CLI(claude / codex / kilo / mcode / goose / opencode / qoder / crush / hermes)作为 agent 运行时。痛点:
39
+ - 调用了但无法直接观测原始 stdout/stderr,只能靠进程日志+时间戳佐证"跑了";
40
+ - 无法逐字核对输出、无法回放、无法统一审计。
41
+
42
+ `cli-gateway` 把任何 CLI 调用包装成"全量日志落盘 + 可回放 + 结构化返回",让每次调用可观测、可审计、可验证。
43
+
44
+ ## 快速开始
45
+
46
+ ```bash
47
+ pip install ai-cli-gateway # PyPI 安装(PyPI 上 `cli-gateway` 名字已被占,故用此名)
48
+ pip install -e . # 或本仓开发安装(或直接 PYTHONPATH=.)
49
+
50
+ # 作为库
51
+ from cli_gateway import run, replay
52
+ res = run(["claude", "--version"])
53
+ print(res.exit_code, res.duration_ms, res.log_path)
54
+ print(replay(res.log_path)) # 逐字回放
55
+
56
+ # 作为 CLI
57
+ # 注意:--timeout/--dir 必须写在 -- 之前;-- 之后的参数全部属于被调命令
58
+ cli-gateway run --timeout 10 -- echo hello
59
+ cli-gateway replay ~/.cli-gateway/logs/20261018-190000-echo-abc12345.log
60
+ cli-gateway logs
61
+ cli-gateway doctor
62
+ ```
63
+
64
+ ## 返回结构(GatewayResult)
65
+
66
+ | 字段 | 说明 |
67
+ |---|---|
68
+ | `command` | 原始命令 |
69
+ | `exit_code` | 退出码:命令未找到=**127**,启动失败(不可执行等)=**126**,超时=**124**,其余为命令真实退出码(POSIX 被信号杀死时为负信号值) |
70
+ | `duration_ms` | 耗时毫秒 |
71
+ | `log_path` | 全量日志文件路径 |
72
+ | `stdout_tail` / `stderr_tail` | 输出尾部(默认各 2000 字符,从日志文件取尾,内存有界) |
73
+ | `timestamp` / `pid` / `success` | 时间戳 / 真实进程号 / 是否成功 |
74
+ | `state` | 枚举:`completed` / `timed_out` / `not_found` / `spawn_error` / `killed`(spawn_error 是对 shell 惯例枚举的扩展) |
75
+ | `cwd` | 本次调用的 working directory(未传为 None) |
76
+ | `truncated` | 输出超过 tail 长度被截断时为 True |
77
+ | `timed_out` | 超时标志(等价 `state == "timed_out"`) |
78
+ | `env_overrides` | 显式传入的 env 覆盖项(含 KEY/TOKEN/SECRET/PASSWORD 的值脱敏为 `***`,不记录全量环境) |
79
+
80
+ ## 日志格式
81
+
82
+ 每次调用写一个文件 `<ts>-<cmd>-<id>.log`:元数据头(command/timestamp/pid/cwd/env_overrides/timeout)+ STDOUT 段 + STDERR 段 + END + 结果尾(exit_code/duration_ms/state),可逐字回放、可审计。超时路径保留已产生的部分输出(partial)。
83
+
84
+ ## 注册表(run_cli,白名单路径)
85
+
86
+ ```python
87
+ from cli_gateway import REGISTRY, CliSpec, run_cli
88
+
89
+ REGISTRY["claude"] = CliSpec(default_timeout=600, tail=2000) # 零依赖顶层 dict
90
+ res = run_cli("claude", "--version") # 未注册名字 → KeyError;run() 不查注册表
91
+ ```
92
+
93
+ `CliSpec` 字段:`default_timeout` / `capture` / `tail` / `cwd` / `env` / `retry_on`。重试只在显式传 `retry_count` 时发生,且只重试"启动前失败"(`not_found` / `spawn_error`;超时与非零退出绝不自动重试),每次尝试独立日志文件。`retry_on` 字段当前仅支持 `"spawn"`,其他值保留但不生效。
94
+
95
+ ## Windows 须知:npm shim 与直调
96
+
97
+ Windows 上 `Popen` 直控走 `CreateProcess`,只解析 `.exe`——npm 全局安装的 CLI(claude / kilo / mcode / opencode 等)目录里只有 `xxx.cmd` shim,直接传名字会得到 `127/not_found`。两种调用方式:
98
+
99
+ - **原生 exe 直调**:codex / goose / qoderclicn / hermes;crush 的 npm shim 没有 `.exe` 同名文件,`CreateProcess` 会继续搜 PATH 命中 WinGet 的 `crush.exe`,同样直调可用;
100
+ - **npm shim 用 `cmd /c <name> ...` 包装**:gateway 的 `taskkill /T` 树杀仍覆盖 cmd 与其下的整棵进程树。
101
+
102
+ Git Bash 传参注意:MSYS 会把独立参数 `/c` 转换成 `C:\`,命令行路径须写 `cmd //c` 或设 `MSYS_NO_PATHCONV=1`;走 Python 库传 argv 列表不受影响。
103
+
104
+ ## 真机验证矩阵(2026-10-18,Windows Server 2022)
105
+
106
+ 9 个 CLI 全部经网关真实验证:`--version` 探测 + 真实模型调用(提示词"只回复两个字:收到")各一次。
107
+
108
+ | CLI | 版本 | 直调方式 | 非交互用法 | 真实调用 |
109
+ |---|---|---|---|---|
110
+ | claude | 2.1.177 | `cmd /c claude` | `claude -p "<prompt>"` | ✅ |
111
+ | codex | 0.162.1 | 直调 exe | `codex exec "<prompt>"` | ✅ |
112
+ | kilo | 7.7.5 | `cmd /c kilo` | `kilo run "<prompt>"` | ✅ |
113
+ | mcode | 0.4.12 | `cmd /c mcode` | `mcode exec "<prompt>"` | ✅ |
114
+ | goose | 1.51.0 | 直调 exe | `goose run -t "<prompt>"` | ✅ |
115
+ | opencode | 1.18.31 | `cmd /c opencode` | `opencode run "<prompt>"` | ✅ |
116
+ | qoder | 1.1.72 | 直调 exe(命令名 `qoderclicn`) | `qoderclicn -p "<prompt>"` | ✅ |
117
+ | crush | v0.88.0 | 直调 exe(自动命中 WinGet 版) | `crush run "<prompt>"` | ✅ |
118
+ | hermes | v0.21.3 | 直调 exe | `hermes -z "<prompt>"` | ✅ |
119
+
120
+ 矩阵可在你自己的机器上实时复现:
121
+
122
+ ```bash
123
+ cli-gateway doctor # 体检内置 9 个编码 CLI:形态/版本/日志路径
124
+ cli-gateway doctor --json # 结构化输出(agent 消费友好)
125
+ cli-gateway doctor --no-probe # 静态快查:只看安装形态,不实跑版本探测
126
+ cli-gateway doctor --only codex # 只查指定命令
127
+ ```
128
+
129
+ 形态判定以网关真实调用结果为准(先直调,Windows 下 not_found 自动再试 `cmd /c` 包装),不是静态猜测;全部可用退出码 0,否则 1——可直接接进 CI 或 agent 的启动自检。
130
+
131
+ ## 子智能体编队(agents/)
132
+
133
+ [agents/](agents/) 内置 9 个编码 CLI 的 **ZCode 子代理操作员提示词**——上表 9 个 CLI 各配一个"专属操作员":主会话(工头)负责拆解派工与验收,子代理通过 Bash 调用对应 CLI 真进程干活。方法论(C4/ADR/TDD/变异测试/八维审查等)全部内置在提示词里,开箱即用。
134
+
135
+ | 文件 | 角色 | 擅长 |
136
+ |---|---|---|
137
+ | [claude.md](agents/claude.md) | 首席架构 | C4/ADR 架构规格、多维评审、技术决策 |
138
+ | [codex.md](agents/codex.md) | 资深攻坚 | 五 Why 根因、性能剖析、Strangler Fig 重构 |
139
+ | [opencode.md](agents/opencode.md) | 全栈 | React/Next + Node/Python + DB 前后端通吃 |
140
+ | [mcode.md](agents/mcode.md) | 功能实现 | TDD 红绿重构一棒到底 |
141
+ | [kilo.md](agents/kilo.md) | 功能实现 | 补位实现、修测试、环境坑 |
142
+ | [crush.md](agents/crush.md) | 功能实现 | 快速编码、LSP 语义辅助 |
143
+ | [goose.md](agents/goose.md) | QA | 测试金字塔、Playwright 真机验证、变异测试防假绿 |
144
+ | [qoder.md](agents/qoder.md) | 代码评审 | 八维审查、异构模型交叉复核 |
145
+ | [hermes.md](agents/hermes.md) | 研究员 | 技术调研、复盘 postmortem、经验库回写 |
146
+
147
+ ### 安装
148
+
149
+ ```bash
150
+ # 用户级(所有项目生效)
151
+ cp agents/*.md ~/.zcode/agents/
152
+
153
+ # 或项目级(仅当前项目生效)
154
+ cp agents/*.md .zcode/agents/
155
+ ```
156
+
157
+ 重启 ZCode 会话后生效,Agent 工具即可按 `name` 派工(如 `subagent_type: "codex"`)。其他支持自定义子代理的框架同样可用:这些 `.md` 本质是"角色化操作员提示词 + 调用配方",粘贴为任意 agent 的系统提示词即可复用。
158
+
159
+ ### 使用
160
+
161
+ 主会话按岗位派工,派单三要素(目标/范围/完成标准)写清,例如:
162
+
163
+ ```markdown
164
+ # 任务
165
+ 修复 tests/test_api.py 中 3 个失败用例(并发写竞态)
166
+
167
+ # 完成标准
168
+ pytest 全绿 + 亲自运行过验证命令
169
+
170
+ # 约束
171
+ 只改 src/worker.py,不动接口签名
172
+ ```
173
+
174
+ 子代理按固定三段式汇报(修改了什么 / 如何验证 / 哪些仍未验证);经 cli-gateway 留证调用的,汇报附 `log=` 路径,验收方 `cli-gateway replay <log>` 逐字回放复核——**验收不轻信汇报文字**。
175
+
176
+ ### 提示词结构(自定义时照此改)
177
+
178
+ 每个文件的固定骨架:`frontmatter`(`name` 派工标识 / `description` 主会话路由依据 / `tools` 工具白名单 / `injectAgentsMd`)+ 正文(岗位定位与方法论 / 完成条件 / 预检三问 / 固定汇报格式 / 退化路径 / 调用方式含 cli-gateway 留证配方 / 红线 / 实测档案)。
179
+
180
+ ### 定制指引
181
+
182
+ - **CLI 名称/版本不同**:改「调用方式」节的命令与核对版本即可,方法论不绑定具体 CLI
183
+ - **后端不是 GLM**:这些操作员原本跑在 GLM 后端,换 provider 只需改 CLI 侧的 provider 配置,提示词无需动
184
+ - **POSIX 环境**:调用配方中 `cmd //c` 包装与 MSYS 注意事项仅适用 Windows + Git Bash,POSIX 直接写命令名
185
+ - **开源版说明**:`agents/` 为脱敏模板(团队名/内网配置/实测记录已替换为占位或通用表述),各文件末尾的「实测档案」留作你的团队自行回写
186
+
187
+ ## 超时与进程组
188
+
189
+ 超时(`timeout` 秒,默认无限等)会按进程组/进程树终止:POSIX 用 `start_new_session` + `os.killpg`(先 SIGTERM,宽限 2s 后 SIGKILL);Windows 用 `CREATE_NEW_PROCESS_GROUP` + `taskkill /F /T`。这会大幅降低孤儿孙进程残留的概率,但不是绝对保证——极端场景(第三方进程自行脱离进程组/进程树、或孙进程继承管道写句柄导致输出流不关闭)可能有残留或输出不完整,此时结果会置 `truncated=True` 并在日志尾部追加 `# warning:` 行,由调用方按需兜底。
190
+
191
+ POSIX 下进程被信号杀死时 `state="killed"`、`exit_code` 为负信号值;Windows 正常退出(含非零退出码)均为 `completed`。
192
+
193
+ 输出读取统一按 UTF-8(`errors="replace"`),日志始终是合法 UTF-8。gateway 默认向子进程注入 `PYTHONUTF8=1`(调用方或父环境已显式设置该变量时不覆盖),使 python 类子进程在 Windows 传统代码页(CP936 等)环境下也输出 UTF-8——注意子进程会看到这个变量。
194
+
195
+ ## 设计边界(v1)
196
+
197
+ - **只做本地 subprocess**,不发起 URL/网络请求——未来 v2 若加远程/HTTP 能力,服务端请求 URL 时仅允许 http/https,且发请求前校验 host、拒绝 localhost/环回/私有/保留地址;
198
+ - 不做沙箱(用 Docker 的 MCP 网关管沙箱,见下);不做 Web UI;不做多租户。
199
+
200
+ ## 与 docker/mcp-gateway 的关系(不同层)
201
+
202
+ | | docker/mcp-gateway | cli-gateway |
203
+ |---|---|---|
204
+ | 管 | MCP 服务器(外部工具数据源,Docker 容器) | CLI 进程调用(本地 subprocess) |
205
+ | 解决 | AI 统一访问 MCP 服务器 + 认证/密钥 | CLI 调用日志落盘/回放/结构化返回 |
206
+ | 层 | 协议层(MCP) | 进程层(subprocess) |
207
+
208
+ 两者互补不冲突:MCP 网关管"AI 连外部工具",cli-gateway 管"AI 调本地 CLI"。
209
+
210
+ ## License
211
+
212
+ MIT
213
+
214
+ ## 安全约束(Mimosa)
215
+
216
+ 本仓库 v1 不发起任何网络请求。若未来扩展远程调用,必须遵守:仅允许 http/https;发请求前校验 host,拒绝 localhost、环回(127.0.0.0/8, ::1)、私有(RFC1918)与保留地址。
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ai_cli_gateway.egg-info/PKG-INFO
5
+ ai_cli_gateway.egg-info/SOURCES.txt
6
+ ai_cli_gateway.egg-info/dependency_links.txt
7
+ ai_cli_gateway.egg-info/entry_points.txt
8
+ ai_cli_gateway.egg-info/requires.txt
9
+ ai_cli_gateway.egg-info/top_level.txt
10
+ cli_gateway/__init__.py
11
+ cli_gateway/__main__.py
12
+ cli_gateway/doctor.py
13
+ tests/test_doctor.py
14
+ tests/test_gateway.py
15
+ tests/test_v11.py