openclaw-diag-cli 0.1.1 → 0.1.2

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,6 +1,6 @@
1
- # openclaw-diag-cli
1
+ # OpenClaw 诊断工具箱
2
2
 
3
- > OpenClaw / ArkClaw 故障诊断 CLI。零依赖、只读、人和机器都能用。
3
+ > 排查 OpenClaw / ArkClaw 故障的只读 CLI。一组诊断、一个入口、零依赖。
4
4
 
5
5
  ## 安装
6
6
 
@@ -18,25 +18,29 @@ openclaw-diag
18
18
  ## 五分钟上手
19
19
 
20
20
  ```bash
21
- # 1. 看看能做什么(直接列出所有模块 + 常用命令)
21
+ # 1. 看看能做什么
22
22
  openclaw-diag
23
23
 
24
24
  # 2. 检查环境是否就绪
25
25
  openclaw-diag doctor
26
26
 
27
- # 3. 跑单个模块
28
- openclaw-diag run gateway
27
+ # 3. 跑某个诊断
28
+ openclaw-diag gateway
29
29
 
30
- # 4. 全部跑一遍(任一模块崩了不影响其他)
31
- openclaw-diag run all
30
+ # 4. 全部 state collectors 跑一遍(任一崩了不影响其他)
31
+ openclaw-diag all
32
32
 
33
33
  # 5. 输出结构化 JSON
34
- openclaw-diag run gateway --json
34
+ openclaw-diag gateway --json
35
35
  ```
36
36
 
37
- ## 诊断模块(10 个)
37
+ ## 诊断列表
38
38
 
39
- | 模块 | 看什么 |
39
+ 诊断按"是否需要参数"分两类。
40
+
41
+ ### State collectors(无需参数,扫一遍系统当前状态)
42
+
43
+ | 诊断 | 看什么 |
40
44
  |---|---|
41
45
  | `sys_health` | DNS / 网络 / CPU / 内存 / 磁盘 / IO / 进程 / 时间同步 |
42
46
  | `environment` | OpenClaw 版本一致性、Gateway 进程环境变量 |
@@ -49,33 +53,45 @@ openclaw-diag run gateway --json
49
53
  | `plugin_diag` | 插件状态一致性、ERROR/WARN、Hook 异常、Channel、外部依赖 DNS |
50
54
  | `shell_history` | 高危命令、openclaw 命令、最近操作 |
51
55
 
52
- ## 单点工具(2 个)
56
+ ### Object inspectors(需要 session uuid,深挖一个具体对象)
53
57
 
54
- ```bash
55
- # 跟踪一条用户消息从进入到响应的完整时间轴
56
- openclaw-diag tools/oc_session_trace.py <session-uuid> --msg-index 0
58
+ | 诊断 | 看什么 |
59
+ |---|---|
60
+ | `trace <uuid>` | 追踪一条用户消息从进入到响应的完整时间轴 |
61
+ | `extract <uuid>` | 导出 session.jsonl 为可读格式(reset / bak / deleted 全状态) |
57
62
 
58
- # 导出 session 为可读格式(支持 reset / bak / deleted 全状态)
59
- openclaw-diag tools/oc_session_extract.py <session-uuid> --summary
60
- ```
63
+ ### Meta
64
+
65
+ | 命令 | 作用 |
66
+ |---|---|
67
+ | `openclaw-diag all` | 跑全部 state collectors |
68
+ | `openclaw-diag list` | 列出所有诊断 |
69
+ | `openclaw-diag doctor` | 检查 Node / Python / ocdiag / OpenClaw 环境 |
70
+ | `openclaw-diag bundle <id>` | 打成 self-contained 单文件 .py |
61
71
 
62
72
  ## 常见配方
63
73
 
64
74
  ```bash
65
75
  # 找出哪个 cron 任务在连续失败
66
- openclaw-diag run cron_jobs --json | jq '.data.jobs[] | select(.status!="ok")'
76
+ openclaw-diag cron_jobs --json | jq '.data.jobs[] | select(.status!="ok")'
67
77
 
68
78
  # 看哪个模型的 P95 延迟最高
69
- openclaw-diag run performance | grep -A1 "P95"
79
+ openclaw-diag performance | grep -A1 "P95"
70
80
 
71
81
  # 哪些插件今天有 ERROR
72
- openclaw-diag run plugin_diag --json | jq '.data.plugin_errors | to_entries[] | select(.value.error_count > 0)'
82
+ openclaw-diag plugin_diag --json | jq '.data.plugin_errors | to_entries[] | select(.value.error_count > 0)'
73
83
 
74
84
  # 把所有诊断聚合成单个 JSON 报告
75
- openclaw-diag run all --json 2>/dev/null | jq -s '.' > report.json
85
+ openclaw-diag all --json 2>/dev/null | jq -s '.' > report.json
76
86
 
77
87
  # 找出有 stuck session 的事件
78
- openclaw-diag run sessions --json | jq '.data.stuck_sessions'
88
+ openclaw-diag sessions --json | jq '.data.stuck_sessions'
89
+
90
+ # 追踪用户消息时间轴
91
+ openclaw-diag trace <session-uuid> --msg-index 0
92
+
93
+ # 导出 session 为可读格式
94
+ openclaw-diag extract <session-uuid> --summary
79
95
  ```
80
96
 
81
97
  ## 离线机器:bundle 出单文件
@@ -108,9 +124,9 @@ ssh prod-server "python3 /tmp/standalone-gateway.py --json"
108
124
 
109
125
  | rc | 含义 |
110
126
  |---|---|
111
- | 0 | 模块成功 |
112
- | 1 | 模块运行成功但报告 `status: "error"`(数据源缺失等) |
113
- | 2 | 模块崩溃(已隔离,不影响其他模块) |
127
+ | 0 | 诊断成功 |
128
+ | 1 | 诊断运行成功但报告 `status: "error"`(数据源缺失等) |
129
+ | 2 | 诊断崩溃(已隔离,不影响 `all`) |
114
130
 
115
131
  ## 设计原则
116
132
 
@@ -118,7 +134,7 @@ ssh prod-server "python3 /tmp/standalone-gateway.py --json"
118
134
  |---|---|
119
135
  | **只读** | 永远不修改文件、不重启服务 |
120
136
  | **零依赖** | 仅 Python 3.8+ 标准库 |
121
- | **故障隔离** | 单模块崩溃不带崩 `run all` |
137
+ | **故障隔离** | 单诊断崩溃不带崩 `all` |
122
138
  | **数据可靠** | 每个字段都能溯源 |
123
139
  | **可组合** | 文本 + JSON 双输出,stderr 与 stdout 分流 |
124
140
 
@@ -17,6 +17,22 @@ const PYTHON_CANDIDATES = process.platform === 'win32'
17
17
  ? ['python3', 'python', 'py']
18
18
  : ['python3', 'python'];
19
19
 
20
+ // Keep these in sync with ocdiag/dispatcher.py.
21
+ const STATE_COLLECTORS = [
22
+ 'sys_health', 'environment', 'configuration', 'gateway', 'recent_errors',
23
+ 'cron_jobs', 'performance', 'sessions', 'plugin_diag', 'shell_history',
24
+ ];
25
+ const OBJECT_INSPECTORS = ['trace', 'extract'];
26
+ const MODULE_IDS = new Set([...STATE_COLLECTORS, ...OBJECT_INSPECTORS]);
27
+
28
+ const STATE_SCRIPTS = [
29
+ '01_sys_health.py', '02_environment.py', '03_configuration.py',
30
+ '04_gateway.py', '05_recent_errors.py', '06_cron_jobs.py',
31
+ '07_performance.py', '08_sessions.py', '09_plugin_diag.py',
32
+ '10_shell_history.py',
33
+ ];
34
+ const OBJECT_SCRIPTS = ['oc_session_trace.py', 'oc_session_extract.py'];
35
+
20
36
  function findPython() {
21
37
  for (const cmd of PYTHON_CANDIDATES) {
22
38
  try {
@@ -51,23 +67,26 @@ function printVersion() {
51
67
 
52
68
  function printHelp() {
53
69
  const lines = [
54
- 'openclaw-diag — OpenClaw / ArkClaw read-only diagnostic CLI',
70
+ 'openclaw-diag — OpenClaw / ArkClaw 诊断工具箱',
55
71
  '',
56
72
  'Usage:',
57
- ' openclaw-diag Show banner + module list',
58
- ' openclaw-diag list List all diagnostic modules',
59
- ' openclaw-diag run <id> Run a single module (or "all")',
60
- ' openclaw-diag run all [--skip a,b] Run all modules (skip optional)',
61
- ' openclaw-diag run <id> --json Emit JSON (NDJSON for "all")',
62
- ' openclaw-diag bundle <id> Print self-contained single-file .py to stdout',
63
- ' openclaw-diag doctor [--json] Check Node / Python / ocdiag / OpenClaw env',
64
- ' openclaw-diag --version Print package version',
65
- ' openclaw-diag --help Print this help',
73
+ ' openclaw-diag 打印 banner + 诊断目录',
74
+ ' openclaw-diag list 列出全部诊断(按类型分组)',
75
+ ' openclaw-diag <id> [args...] 跑单个诊断',
76
+ ' openclaw-diag all [--skip a,b] 跑全部 state collectors',
77
+ ' openclaw-diag all [--json] NDJSON 聚合输出',
78
+ ' openclaw-diag bundle <id> 打成 self-contained 单文件 .py',
79
+ ' openclaw-diag doctor [--json] 检查 Node / Python / ocdiag / OpenClaw env',
80
+ ' openclaw-diag --version 打印版本号',
81
+ ' openclaw-diag --help 本帮助',
82
+ '',
83
+ 'State collectors (无需参数):',
84
+ ' ' + STATE_COLLECTORS.join(' '),
66
85
  '',
67
- 'Module ids: sys_health environment configuration gateway recent_errors',
68
- ' cron_jobs performance sessions plugin_diag shell_history',
86
+ 'Object inspectors (需要 session uuid):',
87
+ ' ' + OBJECT_INSPECTORS.join(' '),
69
88
  '',
70
- 'Pass-through flags (forwarded to Python): --config --log-dir --json --no-color',
89
+ '透传给诊断脚本: --config --log-dir --json --no-color',
71
90
  ];
72
91
  console.log(lines.join('\n'));
73
92
  }
@@ -90,15 +109,10 @@ function runDispatcher(args) {
90
109
  });
91
110
  }
92
111
 
93
- function runBundle(args) {
94
- if (args.length === 0) {
95
- console.error('Error: bundle requires a module id (e.g. `openclaw-diag bundle gateway`)');
96
- process.exit(2);
97
- }
112
+ function runScript(scriptPath, args) {
98
113
  const py = findPython();
99
114
  if (!py) pythonNotFound();
100
- const bundleScript = path.join(REPO_ROOT, 'lib', 'bundle.py');
101
- const child = spawn(py.cmd, [bundleScript, ...args], { stdio: 'inherit' });
115
+ const child = spawn(py.cmd, [scriptPath, ...args], { stdio: 'inherit' });
102
116
  child.on('error', (err) => {
103
117
  console.error(`Error: failed to spawn ${py.cmd}: ${err.message}`);
104
118
  process.exit(1);
@@ -112,14 +126,15 @@ function runBundle(args) {
112
126
  });
113
127
  }
114
128
 
115
- // ── doctor ──
129
+ function runBundle(args) {
130
+ if (args.length === 0) {
131
+ console.error('Error: bundle requires a module id (e.g. `openclaw-diag bundle gateway`)');
132
+ process.exit(2);
133
+ }
134
+ runScript(path.join(REPO_ROOT, 'lib', 'bundle.py'), args);
135
+ }
116
136
 
117
- const DIAG_SCRIPTS = [
118
- '01_sys_health.py', '02_environment.py', '03_configuration.py',
119
- '04_gateway.py', '05_recent_errors.py', '06_cron_jobs.py',
120
- '07_performance.py', '08_sessions.py', '09_plugin_diag.py',
121
- '10_shell_history.py',
122
- ];
137
+ // ── doctor ──
123
138
 
124
139
  function nodeVersionOk() {
125
140
  const m = process.versions.node.match(/^(\d+)\./);
@@ -143,17 +158,20 @@ function checkOcdiagImport(pyCmd) {
143
158
 
144
159
  function checkDiagScripts(pyCmd) {
145
160
  const failed = [];
146
- for (const name of DIAG_SCRIPTS) {
147
- const p = path.join(REPO_ROOT, 'diag', name);
148
- const r = spawnSync(pyCmd, [p, '--help'], {
161
+ const all = [
162
+ ...STATE_SCRIPTS.map((n) => ({ name: n, path: path.join(REPO_ROOT, 'diag', n) })),
163
+ ...OBJECT_SCRIPTS.map((n) => ({ name: n, path: path.join(REPO_ROOT, 'tools', n) })),
164
+ ];
165
+ for (const item of all) {
166
+ const r = spawnSync(pyCmd, [item.path, '--help'], {
149
167
  stdio: ['ignore', 'pipe', 'pipe'],
150
168
  timeout: 10000,
151
169
  });
152
170
  if (r.status !== 0) {
153
- failed.push({ script: name, status: r.status, stderr: ((r.stderr || '').toString().trim()).slice(0, 200) });
171
+ failed.push({ script: item.name, status: r.status, stderr: ((r.stderr || '').toString().trim()).slice(0, 200) });
154
172
  }
155
173
  }
156
- return failed;
174
+ return { failed, total: all.length };
157
175
  }
158
176
 
159
177
  function checkOpenclawConfig() {
@@ -190,10 +208,10 @@ function runDoctor(args) {
190
208
  const ocdiag = checkOcdiagImport(py.cmd);
191
209
  result.ocdiag = ocdiag;
192
210
 
193
- const failed = checkDiagScripts(py.cmd);
211
+ const { failed, total } = checkDiagScripts(py.cmd);
194
212
  result.diag_scripts = {
195
213
  ok: failed.length === 0,
196
- total: DIAG_SCRIPTS.length,
214
+ total,
197
215
  failed,
198
216
  };
199
217
 
@@ -214,9 +232,9 @@ function runDoctor(args) {
214
232
  }
215
233
  }
216
234
  if (failed.length === 0) {
217
- console.log(`✓ All ${DIAG_SCRIPTS.length} diag modules respond to --help`);
235
+ console.log(`✓ All ${total} diagnostics respond to --help`);
218
236
  } else {
219
- console.log(`✗ ${failed.length}/${DIAG_SCRIPTS.length} diag modules failed --help:`);
237
+ console.log(`✗ ${failed.length}/${total} diagnostics failed --help:`);
220
238
  for (const f of failed) {
221
239
  console.log(` ${f.script} (rc=${f.status})`);
222
240
  }
@@ -240,14 +258,15 @@ function main() {
240
258
  if (argv.length === 0) {
241
259
  const py = findPython();
242
260
  if (!py) pythonNotFound();
243
- console.log(`openclaw-diag v${PKG.version} — OpenClaw / ArkClaw 诊断 CLI`);
261
+ console.log(`openclaw-diag v${PKG.version} — OpenClaw / ArkClaw 诊断工具箱`);
244
262
  console.log('');
245
263
  const dispatcher = path.join(REPO_ROOT, 'bin', 'ocdiag');
246
264
  spawnSync(py.cmd, [dispatcher, 'list'], { stdio: 'inherit' });
247
265
  console.log('');
248
266
  console.log('常用命令:');
249
- console.log(' openclaw-diag run gateway 跑单个模块');
250
- console.log(' openclaw-diag run all 全部模块');
267
+ console.log(' openclaw-diag gateway 跑单个 state collector');
268
+ console.log(' openclaw-diag all 全部 state collectors');
269
+ console.log(' openclaw-diag trace <uuid> 追踪一条用户消息');
251
270
  console.log(' openclaw-diag doctor 检查环境');
252
271
  console.log(' openclaw-diag --help 完整帮助');
253
272
  process.exit(0);
@@ -272,7 +291,7 @@ function main() {
272
291
  return;
273
292
  }
274
293
 
275
- // Pass through everything else to the Python dispatcher.
294
+ // Pass through everything else (flat ids, `all`, `list`, `run` alias, unknown) to dispatcher.
276
295
  runDispatcher(argv);
277
296
  }
278
297
 
@@ -1,3 +1,3 @@
1
1
  """ocdiag — shared library for openclaw-diag-cli scripts."""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.1.2"
@@ -1,8 +1,15 @@
1
- """Dispatcher: list / run <name> / run all."""
1
+ """Dispatcher: every diagnostic is a top-level subcommand.
2
+
3
+ Layout:
4
+ ocdiag <state-collector> runs that collector (e.g. `ocdiag gateway`)
5
+ ocdiag <object-inspector> ARG runs that inspector (e.g. `ocdiag trace UUID`)
6
+ ocdiag all [--skip a,b] runs every state collector
7
+ ocdiag list prints the catalogue grouped by parameter mode
8
+ ocdiag run <id> [args...] legacy alias retained for 0.1.x users
9
+ """
2
10
 
3
11
  from __future__ import annotations
4
12
 
5
- import argparse
6
13
  import os
7
14
  import runpy
8
15
  import sys
@@ -13,27 +20,47 @@ from typing import List
13
20
 
14
21
  REPO_ROOT = Path(__file__).resolve().parent.parent
15
22
 
16
- # Module ID -> (label, script filename relative to REPO_ROOT)
17
- MODULES = [
18
- ("sys_health", "系统健康检查", "diag/01_sys_health.py"),
19
- ("environment", "采集基础环境", "diag/02_environment.py"),
20
- ("configuration", "采集配置", "diag/03_configuration.py"),
21
- ("gateway", "采集 Gateway 状态", "diag/04_gateway.py"),
22
- ("recent_errors", "采集近期日志", "diag/05_recent_errors.py"),
23
- ("cron_jobs", "采集定时任务", "diag/06_cron_jobs.py"),
24
- ("performance", "采集模型与性能数据", "diag/07_performance.py"),
25
- ("sessions", "采集 Session 数据", "diag/08_sessions.py"),
26
- ("plugin_diag", "采集插件诊断", "diag/09_plugin_diag.py"),
27
- ("shell_history", "采集命令执行历史", "diag/10_shell_history.py"),
23
+ # State collectors: zero required args, parameter-free observation of system state.
24
+ STATE_COLLECTORS = [
25
+ ("sys_health", "系统健康检查", "diag/01_sys_health.py"),
26
+ ("environment", "OpenClaw 基础环境", "diag/02_environment.py"),
27
+ ("configuration", "配置展平(脱敏)", "diag/03_configuration.py"),
28
+ ("gateway", "Gateway 状态", "diag/04_gateway.py"),
29
+ ("recent_errors", "近期错误聚合", "diag/05_recent_errors.py"),
30
+ ("cron_jobs", "定时任务状态", "diag/06_cron_jobs.py"),
31
+ ("performance", "模型/工具性能", "diag/07_performance.py"),
32
+ ("sessions", "Session 数据", "diag/08_sessions.py"),
33
+ ("plugin_diag", "插件诊断", "diag/09_plugin_diag.py"),
34
+ ("shell_history", "Shell 历史", "diag/10_shell_history.py"),
35
+ ]
36
+
37
+ # Object inspectors: take a session uuid (or other identifier) and inspect it.
38
+ OBJECT_INSPECTORS = [
39
+ ("trace", "追踪用户消息时间轴", "tools/oc_session_trace.py"),
40
+ ("extract", "导出 session 为可读格式", "tools/oc_session_extract.py"),
28
41
  ]
29
42
 
30
- MODULE_BY_ID = {mid: (label, script) for mid, label, script in MODULES}
43
+ STATE_BY_ID = {mid: (label, script) for mid, label, script in STATE_COLLECTORS}
44
+ OBJECT_BY_ID = {mid: (label, script) for mid, label, script in OBJECT_INSPECTORS}
45
+ MODULE_BY_ID = {**STATE_BY_ID, **OBJECT_BY_ID}
46
+ MODULE_IDS = set(MODULE_BY_ID.keys())
31
47
 
32
48
 
33
49
  def cmd_list() -> int:
34
- print("Available modules:")
35
- for mid, label, _ in MODULES:
36
- print(f" [x] {mid:<16s} {label}")
50
+ print("Available diagnostics:")
51
+ print()
52
+ print(" State collectors (no args needed):")
53
+ for mid, label, _ in STATE_COLLECTORS:
54
+ print(f" {mid:<16s} {label}")
55
+ print()
56
+ print(" Object inspectors (require session uuid):")
57
+ for mid, label, _ in OBJECT_INSPECTORS:
58
+ print(f" {mid:<16s} {label}")
59
+ print()
60
+ print(" Meta:")
61
+ print(" all 跑全部 state collectors")
62
+ print(" doctor 检查 Node/Python/OpenClaw 环境")
63
+ print(" bundle <id> 打包成 self-contained 单文件")
37
64
  return 0
38
65
 
39
66
 
@@ -62,72 +89,98 @@ def run_script(script_rel: str, extra_args: List[str]) -> int:
62
89
  sys.argv = saved_argv
63
90
 
64
91
 
65
- def cmd_run(target: str, extra_args: List[str], skip_ids: List[str]) -> int:
92
+ def cmd_all(extra_args: List[str], skip_ids: List[str]) -> int:
66
93
  json_mode = "--json" in extra_args
67
94
  progress_stream = sys.stderr if json_mode else sys.stdout
68
- if target == "all":
69
- rc_overall = 0
70
- total = sum(1 for mid, _, _ in MODULES if mid not in skip_ids)
71
- n = 0
72
- for mid, label, script in MODULES:
73
- if mid in skip_ids:
74
- continue
75
- n += 1
76
- print(f"\n[{n}/{total}] {label} ({mid})...", flush=True, file=progress_stream)
77
- t0 = time.time()
78
- rc = run_script(script, extra_args)
79
- elapsed = time.time() - t0
80
- print(f"[{n}/{total}] {label} ({mid}) ... done ({elapsed:.1f}s)", flush=True, file=progress_stream)
81
- if rc != 0:
82
- rc_overall = rc
83
- return rc_overall
84
- if target not in MODULE_BY_ID:
85
- print(f"Error: unknown module '{target}'. Use `ocdiag list`.", file=sys.stderr)
86
- return 2
87
- _, script = MODULE_BY_ID[target]
88
- return run_script(script, extra_args)
95
+ rc_overall = 0
96
+ total = sum(1 for mid, _, _ in STATE_COLLECTORS if mid not in skip_ids)
97
+ n = 0
98
+ for mid, label, script in STATE_COLLECTORS:
99
+ if mid in skip_ids:
100
+ continue
101
+ n += 1
102
+ print(f"\n[{n}/{total}] {label} ({mid})...", flush=True, file=progress_stream)
103
+ t0 = time.time()
104
+ rc = run_script(script, extra_args)
105
+ elapsed = time.time() - t0
106
+ print(f"[{n}/{total}] {label} ({mid}) ... done ({elapsed:.1f}s)",
107
+ flush=True, file=progress_stream)
108
+ if rc != 0:
109
+ rc_overall = rc
110
+ return rc_overall
111
+
112
+
113
+ def _split_skip(rest: List[str]) -> (List[str], List[str]):
114
+ """Pull out --skip a,b out of an argv tail; return (skip_ids, passthrough)."""
115
+ skip_ids: List[str] = []
116
+ passthrough: List[str] = []
117
+ i = 0
118
+ while i < len(rest):
119
+ a = rest[i]
120
+ if a == "--skip" and i + 1 < len(rest):
121
+ skip_ids.extend(s.strip() for s in rest[i + 1].split(",") if s.strip())
122
+ i += 2
123
+ continue
124
+ passthrough.append(a)
125
+ i += 1
126
+ return skip_ids, passthrough
127
+
128
+
129
+ def print_help() -> None:
130
+ print("ocdiag — OpenClaw 诊断工具箱")
131
+ print()
132
+ print("Usage:")
133
+ print(" ocdiag <id> [args...] 跑单个诊断(state collector 或 object inspector)")
134
+ print(" ocdiag all [--skip a,b] 跑全部 state collectors")
135
+ print(" ocdiag list 列出所有诊断")
136
+ print(" ocdiag run <id> [args...] 旧用法别名(0.1.x 兼容)")
137
+ print()
138
+ print("State collectors:")
139
+ print(" " + " ".join(mid for mid, _, _ in STATE_COLLECTORS))
140
+ print("Object inspectors:")
141
+ print(" " + " ".join(mid for mid, _, _ in OBJECT_INSPECTORS))
142
+ print()
143
+ print("--skip 后接逗号分隔 id 列表(仅对 all 有意义)。")
144
+ print("其它参数(--config / --log-dir / --json / --no-color)原样传递给脚本。")
89
145
 
90
146
 
91
147
  def main(argv=None) -> int:
92
148
  argv = list(sys.argv[1:] if argv is None else argv)
93
149
 
94
150
  if not argv or argv[0] in ("-h", "--help"):
95
- print("ocdiag — OpenClaw 诊断 CLI dispatcher")
96
- print()
97
- print("Usage:")
98
- print(" ocdiag list 列出所有诊断模块")
99
- print(" ocdiag run <id> 运行单个模块(id 或 all)")
100
- print(" ocdiag run all [--skip ids] 运行全部模块,可跳过若干")
101
- print()
102
- print("--skip 后接逗号分隔的 module id 列表(如 performance,sessions)。")
103
- print("其它参数(--config / --log-dir / --json / --no-color)原样传递。")
151
+ print_help()
104
152
  return 0
105
153
 
106
- cmd, rest = argv[0], argv[1:]
154
+ head, rest = argv[0], argv[1:]
107
155
 
108
- if cmd == "list":
156
+ if head == "list":
109
157
  return cmd_list()
110
158
 
111
- if cmd == "run":
159
+ if head == "all":
160
+ skip_ids, passthrough = _split_skip(rest)
161
+ return cmd_all(passthrough, skip_ids)
162
+
163
+ # Backward-compat alias: `ocdiag run <id> [args...]` still works.
164
+ if head == "run":
112
165
  if not rest:
113
166
  print("Error: run requires a target (module id or 'all').", file=sys.stderr)
114
167
  return 2
115
- target = rest[0]
116
- sub = rest[1:]
117
- skip_ids: List[str] = []
118
- passthrough: List[str] = []
119
- i = 0
120
- while i < len(sub):
121
- a = sub[i]
122
- if a == "--skip" and i + 1 < len(sub):
123
- skip_ids.extend(s.strip() for s in sub[i + 1].split(",") if s.strip())
124
- i += 2
125
- continue
126
- passthrough.append(a)
127
- i += 1
128
- return cmd_run(target, passthrough, skip_ids)
129
-
130
- print(f"Error: unknown command '{cmd}'", file=sys.stderr)
168
+ target, sub = rest[0], rest[1:]
169
+ if target == "all":
170
+ skip_ids, passthrough = _split_skip(sub)
171
+ return cmd_all(passthrough, skip_ids)
172
+ if target in MODULE_BY_ID:
173
+ _, script = MODULE_BY_ID[target]
174
+ return run_script(script, sub)
175
+ print(f"Error: unknown diagnostic '{target}'. Use `ocdiag list`.", file=sys.stderr)
176
+ return 2
177
+
178
+ if head in MODULE_BY_ID:
179
+ _, script = MODULE_BY_ID[head]
180
+ return run_script(script, rest)
181
+
182
+ print(f"Error: unknown command '{head}'. Use `ocdiag list` to see available diagnostics.",
183
+ file=sys.stderr)
131
184
  return 2
132
185
 
133
186
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-diag-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",