openclaw-diag-cli 0.1.0 → 0.1.1

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.
package/README.md CHANGED
@@ -1,259 +1,133 @@
1
1
  # openclaw-diag-cli
2
2
 
3
- > OpenClaw / ArkClaw 故障诊断工具集。零依赖、只读、可组合的纯 Python 脚本。
3
+ > OpenClaw / ArkClaw 故障诊断 CLI。零依赖、只读、人和机器都能用。
4
4
 
5
- ## 快速开始
6
-
7
- 无需 git clone,通过 npm 拉一份缓存即可(之后离线可用):
5
+ ## 安装
8
6
 
9
7
  ```bash
10
- # 一次性运行(npm 缓存后离线可用)
11
- npx openclaw-diag-cli list
12
- npx openclaw-diag-cli run gateway
13
- npx openclaw-diag-cli run all --json | jq -s '.'
8
+ # 一次性运行(无需安装,npm 缓存后离线可用)
9
+ npx openclaw-diag-cli
14
10
 
15
- # 装到 PATH(更短的命令)
11
+ # 装到 PATH
16
12
  npm install -g openclaw-diag-cli
17
- openclaw-diag list
18
- openclaw-diag doctor # 检查环境是否就绪
19
- openclaw-diag bundle gateway > gw.py # 生成单文件诊断脚本
13
+ openclaw-diag
20
14
  ```
21
15
 
22
- 依赖:Node 18+(npx)和 Python 3.8+。Node 层是零 npm 依赖的薄壳,只负责定位
23
- `python3` 并把参数透传给现有的 dispatcher,所以 `python3 diag/04_gateway.py`
24
- 和 `python3 bin/ocdiag run gateway` 仍然完全可用。
25
-
26
- ## 为什么存在
27
-
28
- 排查 OpenClaw 故障时面对的真实痛点:
29
-
30
- - **数据散在多个角落**:session.jsonl 在 agents/ 下,配置在 openclaw.json,进程行为在 journalctl,cron 状态在 cron/jobs.json,模型耗时藏在 trajectory 里…… 手敲 jq + grep 组合费时且易漏。
31
- - **`openclaw-diag.sh` 已成为 4391 行单体 bash**,里面塞着 10 段 heredoc 嵌入的 Python,难修改、难单测、难复用。
32
- - **诊断脚本应该是"原子操作"**:每条数据有明确来源,每个模块解决一类问题,可以单独跑、可以组合管道、可以被自动化驱动。
33
-
34
- 这个仓库就是把那个 4391 行 bash 拆开重写——每个采集动作独立成一个 Python 脚本,按一组公理设计,让"采集 → 分析 → 上报"变成可推理的工程而不是手工活。
35
-
36
- ---
37
-
38
- ## 设计公理(First Principles)
39
-
40
- 下面 6 条是**不可让步**的硬约束。所有目录结构、API、输出格式都从这 6 条推导出来。
41
-
42
- ### 1. 只读(Read-Only)
43
- 诊断脚本**永远不能**修改文件、写配置、重启服务。代价:再难拿的数据也要靠"读"获得;不允许走 `openclaw <subcmd>` 修改类入口。
44
- **收益**:在生产环境、在排查事故现场、在客户机器上跑都安全。
16
+ 依赖:Node 18+ Python 3.8+。
45
17
 
46
- ### 2. 零运行时依赖(Zero Runtime Dependencies)
47
- **只用 Python 3.8+ 标准库**。不写 `requirements.txt`,不要 `pip install`。唯一例外:`croniter` 在 `06_cron_jobs.py` 中可选导入(缺失时退化到从历史 runs 推算间隔)。
48
- **收益**:任何能跑 OpenClaw 的节点都能跑诊断(OpenClaw 自己依赖 Node.js,但诊断脚本不依赖 OpenClaw 装在 Python 端的任何包)。`git clone` 完直接 `python3 diag/04_gateway.py`。
18
+ ## 五分钟上手
49
19
 
50
- ### 3. 独立可执行(Independent)
51
- **每个诊断脚本必须能单独跑通**,不依赖 dispatcher、不需要 source 任何 env 文件、不需要先执行别的脚本。
52
- **推论**:脚本顶部用 `sys.path.insert(0, ...)` 把仓库根加进去再 import 共享库;不强制装包。
53
-
54
- ### 4. 可组合(Composable)
55
- 默认输出是人类可读文本(中文,带 emoji 装饰),加 `--json` 输出**结构化 JSON**。
56
- - 单脚本:`{"module": "<id>", "status": "ok|error", "data": {...}}`
57
- - `bin/ocdiag run all --json` 输出 **NDJSON**(每行一个模块的 JSON),可以 `... | jq -s '.'` 聚合,或者 `... | jq 'select(.module=="cron_jobs") | .data'` 抽取。
58
- **推论**:进度信息走 stderr,永远不污染 stdout 的 JSON 流。
59
-
60
- ### 5. 数据可靠(Data Fidelity)
61
- 脚本输出的每个数字、每个状态都必须能溯源:
62
- - 系统数据 → `subprocess.run(["free","-m"])`、`/proc/<pid>/environ`、`journalctl ...`
63
- - OpenClaw 数据 → `~/.openclaw/openclaw.json`、`~/.openclaw/cron/jobs.json`、`~/.openclaw/agents/*/sessions/*.jsonl`
64
- - 日志数据 → `/tmp/openclaw/openclaw-*.log`(按 mtime 取今日)
65
-
66
- 数据来源在文档里逐模块列清,不允许"看上去合理就行"。同一字段,文本输出和 JSON 输出必须**值一致**。
20
+ ```bash
21
+ # 1. 看看能做什么(直接列出所有模块 + 常用命令)
22
+ openclaw-diag
67
23
 
68
- ### 6. 故障隔离(Failure Isolation)
69
- - 单个模块崩溃**不能**带崩 `run all`:dispatcher 在 `runpy.run_path` 外包 try/except。
70
- - 单个数据源缺失(配置不存在、日志没生成、session 文件被删)**不能**抛异常,要明确报告"未找到"。
71
- - 不要 swallow 异常变 silent:失败要在 stderr 留 traceback,rc 非 0。
24
+ # 2. 检查环境是否就绪
25
+ openclaw-diag doctor
72
26
 
73
- ---
27
+ # 3. 跑单个模块
28
+ openclaw-diag run gateway
74
29
 
75
- ## 推导出的架构
30
+ # 4. 全部跑一遍(任一模块崩了不影响其他)
31
+ openclaw-diag run all
76
32
 
77
- ```
78
- openclaw-diag-cli/
79
- ├── ocdiag/ 共享原语(公理 #2 推论:库小而稳)
80
- │ ├── paths.py 路径常量 + 环境变量覆盖
81
- │ ├── jsonlog.py OpenClaw JSON 日志解析(公理 #5)
82
- │ ├── timeutil.py ISO/epoch 时间转换 + 人类友好格式化
83
- │ ├── tokens.py fmt_tokens / percentile / human_size
84
- │ ├── sensitive.py 密钥/Token 脱敏(公理 #1 的延伸:输出也要安全)
85
- │ ├── output.py 双模式输出(人类可读 + JSON)— 公理 #4 实现
86
- │ ├── recent_logs.py 发现今日更新日志
87
- │ ├── cli.py 公共 argparse(--config / --log-dir / --json)
88
- │ └── dispatcher.py bin/ocdiag 复用的入口
89
-
90
- ├── diag/ 诊断模块(公理 #3:每个能独立跑)
91
- │ ├── 01_sys_health.py 系统健康(DNS/网络/CPU/内存/磁盘/IO/进程/时间同步)
92
- │ ├── 02_environment.py OpenClaw 基础环境(版本一致性、Gateway 进程 env)
93
- │ ├── 03_configuration.py openclaw.json 展平(脱敏后)
94
- │ ├── 04_gateway.py Gateway 状态(WS 生命周期 + 错误码统一视图)
95
- │ ├── 05_recent_errors.py 近期错误(多日志聚合 + journalctl + tool 错误)
96
- │ ├── 06_cron_jobs.py 定时任务(jobs.json + state + runs/ 三源合并)
97
- │ ├── 07_performance.py 模型/工具性能(慢调用 Top 20 / E2E 延迟 / Cache)
98
- │ ├── 08_sessions.py Session 数据(六维分析 + Stuck 探测)
99
- │ ├── 09_plugin_diag.py 插件诊断(一致性 + ERROR/WARN + Hook + Channel + DNS)
100
- │ └── 10_shell_history.py Shell 历史(高危命令 + openclaw 命令)
101
-
102
- ├── tools/ 单点深挖工具(不是采集,是分析特定对象)
103
- │ ├── oc_session_trace.py 跟踪一条 user 消息从进入到响应的完整时间轴
104
- │ └── oc_session_extract.py 把 session jsonl 导出为可读格式(含 reset/bak/deleted 全状态)
105
-
106
- └── bin/
107
- └── ocdiag 可选的总入口(list / run <id> / run all)
33
+ # 5. 输出结构化 JSON
34
+ openclaw-diag run gateway --json
108
35
  ```
109
36
 
110
- ---
37
+ ## 诊断模块(10 个)
111
38
 
112
- ## 数据来源(每条数据从哪里读)
113
-
114
- 公理 #5 的具体落地——下游用任何字段都能查到它从哪来:
115
-
116
- | 模块 | 数据来源 |
39
+ | 模块 | 看什么 |
117
40
  |---|---|
118
- | 01_sys_health | `dig`/`getent`、`free -m`、`df -m`、`/proc/<pid>/limits`、`timedatectl` |
119
- | 02_environment | `openclaw --version`、`/proc/<gw-pid>/environ`、`~/.config/systemd/user/openclaw-gateway.service.d/env.conf` |
120
- | 03_configuration | `~/.openclaw/openclaw.json`(脱敏:key/secret/token/password 等关键词命中后 mask) |
121
- | 04_gateway | `systemctl status` + `journalctl --since 24h` + `~/.openclaw/openclaw.json:gateway.port` + `/tmp/openclaw/openclaw-*.log`(subsystem 白名单过滤) |
122
- | 05_recent_errors | 今日 `openclaw-*.log` 的 ERROR/FATAL + `journalctl --priority err` + 最近 session.jsonl toolResult.isError |
123
- | 06_cron_jobs | `~/.openclaw/cron/jobs.json` + `jobs-state.json` + `runs/<jobId>.jsonl`(合并三源) |
124
- | 07_performance | 最近 20 `agents/*/sessions/*.jsonl`(含 reset 文件,按 mtime) |
125
- | 08_sessions | 同上 + `/tmp/openclaw/openclaw-*.log` 中 subsystem=diagnostic 的 stuck-session 行 |
126
- | 09_plugin_diag | 今日日志 `_meta.name` 解析 + `~/.openclaw/openclaw.json:plugins.entries` + `~/.openclaw/extensions/` + DNS 探测 |
127
- | 10_shell_history | `~/.bash_history` + `~/.zsh_history` |
128
- | oc_session_trace | session.jsonl + 同目录 `*.trajectory.jsonl`(可选) + Gateway 日志(可选) |
129
- | oc_session_extract | session.jsonl + 兄弟文件 `.deleted` / `.reset.N` / `.bak-*` |
130
-
131
- ---
132
-
133
- ## 用法
134
-
135
- ### 最小用法(独立脚本)
41
+ | `sys_health` | DNS / 网络 / CPU / 内存 / 磁盘 / IO / 进程 / 时间同步 |
42
+ | `environment` | OpenClaw 版本一致性、Gateway 进程环境变量 |
43
+ | `configuration` | `openclaw.json` 展平(敏感字段已脱敏) |
44
+ | `gateway` | Gateway 进程、端口、24h 启停、WS 生命周期、错误码 |
45
+ | `recent_errors` | 应用日志 / journalctl / session 工具调用错误聚合 |
46
+ | `cron_jobs` | 定时任务状态、连续失败、调度漂移、静默检测 |
47
+ | `performance` | 模型/工具耗时 P50/P95、慢调用 Top 20、E2E 延迟、Cache 命中率 |
48
+ | `sessions` | Session 总览、活跃度、Stuck 探测 |
49
+ | `plugin_diag` | 插件状态一致性、ERROR/WARN、Hook 异常、Channel、外部依赖 DNS |
50
+ | `shell_history` | 高危命令、openclaw 命令、最近操作 |
51
+
52
+ ## 单点工具(2 个)
136
53
 
137
54
  ```bash
138
- git clone https://github.com/wujiaming88/openclaw-diag-cli.git
139
- cd openclaw-diag-cli
140
- python3 diag/04_gateway.py # 直接跑,零配置
141
- python3 diag/04_gateway.py --json # 同样的数据,JSON 格式
142
- ```
143
-
144
- ### 总入口(可选)
55
+ # 跟踪一条用户消息从进入到响应的完整时间轴
56
+ openclaw-diag tools/oc_session_trace.py <session-uuid> --msg-index 0
145
57
 
146
- ```bash
147
- python3 bin/ocdiag list # 列出 10 个模块
148
- python3 bin/ocdiag run gateway # 跑 04_gateway
149
- python3 bin/ocdiag run all # 全部跑一遍(任一模块崩了不影响其他)
150
- python3 bin/ocdiag run all --skip performance,sessions # 跳过重模块
58
+ # 导出 session 为可读格式(支持 reset / bak / deleted 全状态)
59
+ openclaw-diag tools/oc_session_extract.py <session-uuid> --summary
151
60
  ```
152
61
 
153
- ### npm / npx 入口(同样支持上述全部参数)
62
+ ## 常见配方
154
63
 
155
64
  ```bash
156
- npx openclaw-diag-cli list
157
- npx openclaw-diag-cli run gateway --json
158
- npx openclaw-diag-cli run all --skip performance,sessions
159
- npx openclaw-diag-cli doctor # 检查 Node/Python/ocdiag/OpenClaw
160
- npx openclaw-diag-cli bundle 04_gateway > standalone-gateway.py
161
- ```
65
+ # 找出哪个 cron 任务在连续失败
66
+ openclaw-diag run cron_jobs --json | jq '.data.jobs[] | select(.status!="ok")'
162
67
 
163
- ### JSON 管道(公理 #4 的真正用法)
68
+ # 看哪个模型的 P95 延迟最高
69
+ openclaw-diag run performance | grep -A1 "P95"
164
70
 
165
- ```bash
166
- # 1) 单模块 JSON jq 抽取关键字段
167
- python3 diag/06_cron_jobs.py --json | jq '.data.jobs | length'
168
-
169
- # 2) run all NDJSON → 聚合为单文档
170
- python3 bin/ocdiag run all --json 2>/dev/null | jq -s '.' > report.json
71
+ # 哪些插件今天有 ERROR
72
+ openclaw-diag run plugin_diag --json | jq '.data.plugin_errors | to_entries[] | select(.value.error_count > 0)'
171
73
 
172
- # 3) 找出有错误的模块
173
- python3 bin/ocdiag run all --json 2>/dev/null | jq 'select(.status=="error")'
74
+ # 把所有诊断聚合成单个 JSON 报告
75
+ openclaw-diag run all --json 2>/dev/null | jq -s '.' > report.json
174
76
 
175
- # 4) 提取所有 cron 任务的成功率
176
- python3 bin/ocdiag run all --json 2>/dev/null \
177
- | jq 'select(.module=="cron_jobs") | .data.jobs[] | {name, success_rate}'
77
+ # 找出有 stuck session 的事件
78
+ openclaw-diag run sessions --json | jq '.data.stuck_sessions'
178
79
  ```
179
80
 
180
- ### 工具:单点深挖
81
+ ## 离线机器:bundle 出单文件
181
82
 
182
83
  ```bash
183
- # 跟踪一条 user 消息的处理时间轴
184
- python3 tools/oc_session_trace.py <session-uuid> --msg-index 0
84
+ # 在有网的机器
85
+ openclaw-diag bundle gateway > standalone-gateway.py
185
86
 
186
- # 导出 session 为可读格式
187
- python3 tools/oc_session_extract.py <session-uuid> --summary
188
- python3 tools/oc_session_extract.py <session-uuid> --types message --no-pretty
87
+ # scp 到目标机器(只需要 Python 3.8+,无需安装任何东西)
88
+ scp standalone-gateway.py prod-server:/tmp/
89
+ ssh prod-server "python3 /tmp/standalone-gateway.py --json"
189
90
  ```
190
91
 
191
- ### 环境变量覆盖
92
+ `bundle` 会把脚本和它依赖的共享代码合并成一个 self-contained `.py`,零依赖。
192
93
 
193
- 跑别人机器/容器时不用改代码,覆盖路径即可:
94
+ ## 配置覆盖
194
95
 
195
- | 变量 | 默认值 | 说明 |
96
+ 诊断别人机器或容器时,无需改代码:
97
+
98
+ | 环境变量 | 默认值 | 说明 |
196
99
  |---|---|---|
197
100
  | `OPENCLAW_HOME` | `~/.openclaw` | OpenClaw 主目录 |
198
101
  | `OPENCLAW_CONFIG` | `$OPENCLAW_HOME/openclaw.json` | 配置文件 |
199
102
  | `OPENCLAW_LOG_DIR` | `/tmp/openclaw` | 日志目录 |
200
103
  | `OPENCLAW_SESSIONS` | `$OPENCLAW_HOME/agents` | Session 根 |
201
- | `OPENCLAW_SERVICE_FILE` | `~/.config/systemd/user/openclaw-gateway.service` | systemd 服务单元 |
202
-
203
- 也可以用 `--config /path/to/openclaw.json --log-dir /path/to/logs` 覆盖单个参数。
204
104
 
205
- ---
105
+ 也可以用 `--config /path/to/file --log-dir /path/to/logs` 覆盖单次。
206
106
 
207
- ## 退出码与错误隔离
107
+ ## 退出码
208
108
 
209
109
  | rc | 含义 |
210
110
  |---|---|
211
- | 0 | 模块成功,data 字段已填 |
212
- | 1 | 模块运行成功但报告 `status: "error"`(数据源缺失等业务错误) |
213
- | 2 | 单模块崩溃(dispatcher 已隔离,不影响其他模块) |
111
+ | 0 | 模块成功 |
112
+ | 1 | 模块运行成功但报告 `status: "error"`(数据源缺失等) |
113
+ | 2 | 模块崩溃(已隔离,不影响其他模块) |
214
114
 
215
- `bin/ocdiag run all` 的总 rc 取最大值;任一模块崩溃 stderr 留 traceback,但 stdout 流仍完整。
115
+ ## 设计原则
216
116
 
217
- ---
218
-
219
- ## 扩展:加一个新诊断模块
220
-
221
- 遵循公理即可:
222
-
223
- 1. 新建 `diag/11_my_check.py`,shebang `#!/usr/bin/env python3`
224
- 2. 顶部 docstring 说明:**采集什么 + 数据来源 + 输出含义**
225
- 3. `sys.path.insert(0, str(Path(__file__).resolve().parent.parent))` 接入共享库
226
- 4. `from ocdiag import cli, output, paths` 拿到统一基础设施
227
- 5. `parser = cli.build_common_parser(...); args = parser.parse_args()`
228
- 6. `out = output.init("my_check", json_mode=args.json, ...)`
229
- 7. 业务逻辑——文本输出用 `out.item / out.evidence / out.section`,JSON 数据用 `out.set_data("key", value)`
230
- 8. 流式读 JSONL(`for line in open(...)`),不能 `.read().split('\n')`
231
- 9. 子进程调用必须带 `timeout`
232
- 10. 数据源缺失要明确报告"未找到",不抛异常
233
-
234
- 注册到 `bin/ocdiag` 只需在 `ocdiag/dispatcher.py:MODULES` 列表加一行。
235
-
236
- ---
237
-
238
- ## 不做的事(反模式)
239
-
240
- | 不做 | 原因 |
117
+ | | |
241
118
  |---|---|
242
- | 不写测试框架 | 优先靠 ground truth 对齐验证;测试以后补 |
243
- | 不加 web UI / TUI / Rich | 公理 #2(零依赖)+ 公理 #4(管道友好)冲突 |
244
- | 不需要 `pip install` | 公理 #2 + #3 |
245
- | 不重启 / 不修改 / 不发请求 | 公理 #1 |
246
- | 不强制配置 / 不强制 token | 任何节点 clone 即跑 |
247
- | 不引入 jq 子进程 | Python 自带 json,更可控 |
248
- | 不内嵌 Python 在 bash heredoc 里 | 这就是我们要替代的旧形态 |
249
-
250
- ---
119
+ | **只读** | 永远不修改文件、不重启服务 |
120
+ | **零依赖** | Python 3.8+ 标准库 |
121
+ | **故障隔离** | 单模块崩溃不带崩 `run all` |
122
+ | **数据可靠** | 每个字段都能溯源 |
123
+ | **可组合** | 文本 + JSON 双输出,stderr stdout 分流 |
251
124
 
252
- ## 来历
125
+ 详细设计 → [docs/DESIGN.md](docs/DESIGN.md)(公理推导、目录结构、扩展指南)
253
126
 
254
- 4391 行的 `openclaw-diag.sh`(10 个 bash 模块 + 10 段 heredoc Python)拆分重写。原脚本仍在维护,作为"打包采集 + 远程发送报告"的 all-in-one 用例存在;本仓库面向"模块化、自动化、可推理"的诊断场景。
127
+ ## 反馈
255
128
 
256
- ---
129
+ - Issues: https://github.com/wujiaming88/openclaw-diag-cli/issues
130
+ - 来源:从 4391 行的 `openclaw-diag.sh` 拆分重写
257
131
 
258
132
  ## License
259
133
 
@@ -238,15 +238,19 @@ function main() {
238
238
  const argv = process.argv.slice(2);
239
239
 
240
240
  if (argv.length === 0) {
241
+ const py = findPython();
242
+ if (!py) pythonNotFound();
241
243
  console.log(`openclaw-diag v${PKG.version} — OpenClaw / ArkClaw 诊断 CLI`);
242
244
  console.log('');
243
- console.log(' npx openclaw-diag-cli list 列出所有诊断模块');
244
- console.log(' npx openclaw-diag-cli run <id> 运行单个模块(或 all)');
245
- console.log(' npx openclaw-diag-cli doctor 检查环境是否就绪');
246
- console.log(' npx openclaw-diag-cli --help 查看完整帮助');
245
+ const dispatcher = path.join(REPO_ROOT, 'bin', 'ocdiag');
246
+ spawnSync(py.cmd, [dispatcher, 'list'], { stdio: 'inherit' });
247
247
  console.log('');
248
- runDispatcher(['list']);
249
- return;
248
+ console.log('常用命令:');
249
+ console.log(' openclaw-diag run gateway 跑单个模块');
250
+ console.log(' openclaw-diag run all 全部模块');
251
+ console.log(' openclaw-diag doctor 检查环境');
252
+ console.log(' openclaw-diag --help 完整帮助');
253
+ process.exit(0);
250
254
  }
251
255
 
252
256
  const head = argv[0];
@@ -1,3 +1,3 @@
1
1
  """ocdiag — shared library for openclaw-diag-cli scripts."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.1"
@@ -34,8 +34,6 @@ def cmd_list() -> int:
34
34
  print("Available modules:")
35
35
  for mid, label, _ in MODULES:
36
36
  print(f" [x] {mid:<16s} {label}")
37
- print()
38
- print("Usage: ocdiag run <id> | ocdiag run all [--skip id1,id2] [--json]")
39
37
  return 0
40
38
 
41
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-diag-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenClaw / ArkClaw read-only diagnostic CLI. Zero-dependency Python scripts wrapped in Node for npx-friendly install.",
5
5
  "keywords": [
6
6
  "openclaw",