openclaw-diag-cli 1.11.1 → 1.12.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/CHANGELOG.md CHANGED
@@ -1,5 +1,100 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.12.1 — --help / list / examples output switched to English (2026-06-18)
4
+
5
+ `v1.12.0` made the help comprehensive but in Chinese. This patch translates
6
+ every custom help string surfaced by `--help` (top-level + each subcommand +
7
+ each collector/inspector), `openclaw-diag list`, and `openclaw-diag examples`
8
+ to clear English. argparse framework strings (`usage:`, `options:`, `-h, --help`)
9
+ were already English and are unchanged.
10
+
11
+ ### Changes
12
+
13
+ Only `ocdiag/main.py` + `tests/test_cli_help.py` are touched. Collectors /
14
+ inspectors business logic, collector `title` attributes, and report rendering
15
+ are untouched.
16
+
17
+ 1. `_COMMAND_DESC` values translated to English; `_desc()` unchanged.
18
+ 2. `_common_arguments` group renamed to `"global options"`; every flag
19
+ (`--config` / `--log-dir` / `--sessions-base` / `--openclaw-home` /
20
+ `--format` / `--json` / `--no-color` / `--unmask`) carries an English help
21
+ string. The `channel options` group + `--account` help are also English.
22
+ 3. `trace` / `extract` / `panorama` parsers: per-command group headers
23
+ (`trace options`, `extract options`, `panorama options`), every flag's
24
+ help text, and the `Examples:` epilog blocks are all English. Description
25
+ line uses the English `_desc()` only — the Chinese collector `title` no
26
+ longer leaks into help.
27
+ 4. `_print_help()` rewritten in English (intro / Usage / Health checks / Scan
28
+ diagnostics / Object diagnostics / Helper commands / Global options /
29
+ Exit codes / More).
30
+ 5. `cmd_list` pretty + json outputs are fully English. The json `label` field
31
+ now mirrors the English description (no Chinese title leak).
32
+ 6. `cmd_examples()` translated; example commands themselves are unchanged.
33
+ 7. Per-collector `<id> --help` description switched from
34
+ `f"{coll.title} ({coll.id}) — {desc}"` to `f"{desc} ({coll.id})"` so the
35
+ Chinese title is never printed.
36
+ 8. Error messages: `"Error: 未知 ..."` -> `"Error: unknown ..."` and the
37
+ "run `openclaw-diag list`..." hint translated.
38
+
39
+ ### Tests
40
+
41
+ `tests/test_cli_help.py`: every assertion that pinned a Chinese substring is
42
+ swapped to its English equivalent. Test intent is preserved 1:1.
43
+
44
+ ## v1.12.0 — --help 全面改造:命令/参数清晰说明 + 分组 + 退出码 (2026-06-18)
45
+
46
+ 让 `--help`(顶层 + 每个子命令 + 每个 collector/inspector)对**用户和 agent 都
47
+ 清晰、明确、直接**。每个命令、每个参数都有一句话说明,零猜测。
48
+
49
+ ### 改动
50
+
51
+ 唯一业务改动文件:`ocdiag/main.py`(外加 `tests/test_cli_help.py` 新增测试)。
52
+ 不动 collectors/inspectors 业务逻辑、不动默认路径/参数 default 值。
53
+
54
+ 1. 新增集中式命令描述表 `_COMMAND_DESC`(id → 一句话中文)+ helper `_desc(mid)`,
55
+ 供 `list`、`<id> --help` 的 description、顶层 help 复用,未来新增 collector
56
+ 缺失时回退空串、不报错。
57
+ 2. `_common_arguments` 内全局参数放进 `argument group "全局选项 (global options)"`,
58
+ 每个 flag 都补了清晰中文 help(`--config` / `--log-dir` / `--sessions-base` /
59
+ `--openclaw-home` / `--format` / `--json` / `--no-color` / `--unmask`),路径
60
+ 类参数加 `metavar=PATH` 让 usage 行更整齐。`--account` 同样进 `"channel 选项"`
61
+ group。
62
+ 3. `trace --help` 之前 `--no-trajectory` / `--no-log` / `--show-tool-metas` /
63
+ `--show-plugin-snapshot` / `--mask` 都是裸 flag,本次补齐每个的中文说明,
64
+ 并把 trace/extract/panorama 的命令专属参数各自放进 `"trace/extract/panorama 选项"`
65
+ group。
66
+ 4. 顶层 `_print_help()` 重写:工具简介 → 用法 → 体检命令 → 扫描类(动态从
67
+ registry 渲染,对齐排版)→ 对象诊断 → 辅助命令 → 全局选项 → 退出码
68
+ (0/1/2/3) → 更多。
69
+ 5. `cmd_list` pretty 输出每项追加 `— 描述`;`list --format json` 在每项里加
70
+ `description` 字段,便于 agent / 脚本消费。
71
+ 6. 各 collector parser 的 `description` 从 `f"{title} ({id})"` 改为
72
+ `f"{title} ({id}) — {_desc(id)}"`,`<id> --help` 顶部就能看到一句话简介。
73
+
74
+ ### Tests
75
+
76
+ `tests/test_cli_help.py` 追加 8 个用例:
77
+ - 顶层 help 含工具简介 / 体检命令 / 扫描类 / 对象诊断 / 辅助命令 / 全局选项 /
78
+ 退出码(含 0/1/2/3)等关键 section 标题。
79
+ - 顶层 help 遍历 registry 断言每个 state collector id 出现,未来新增不漏排。
80
+ - 顶层 help 含 trace/extract/panorama 三个 inspector。
81
+ - `trace --help` 五个之前的裸 flag 都带中文 help 片段,且分组标题 `trace 选项`
82
+ + description 一句话简介都在。
83
+ - `gateway --help` 含 `全局选项` 分组,`--config` / `--unmask` / `--no-color` /
84
+ `--log-dir` / `--sessions-base` / `--openclaw-home` 都带中文说明。
85
+ - `gateway --help` description 拼上 `_COMMAND_DESC['gateway']` 一句话片段。
86
+ - `list` pretty 含已知 collector 的描述片段;`list --format json` 每项含
87
+ `description` key(已登记 id 描述非空)。
88
+
89
+ `pytest tests/ -q` → 234 passed, 1 skipped。
90
+
91
+ ### 行为不变
92
+
93
+ - 默认参数值(paths.\*)、各 collector/inspector 业务逻辑、退出码定义、
94
+ argv 解析顺序、扫描结果与 v1.11.x 完全一致。
95
+ - 之前 215+ 用例全绿;本次只增不改测试。
96
+
97
+
3
98
  ## v1.10.4 — plugin_diag windowed scan filters to dated-in-window (2026-06-16)
4
99
 
5
100
  Minimal correctness hardening of the v1.10.2 full-cache reuse. The 7d/30d
@@ -1,3 +1,3 @@
1
1
  """ocdiag — shared library for openclaw-diag-cli scripts."""
2
2
 
3
- __version__ = "1.11.1"
3
+ __version__ = "1.12.1"
package/ocdiag/main.py CHANGED
@@ -36,6 +36,37 @@ from .render.ndjson import NdjsonRenderer
36
36
  _FORMAT_CHOICES = ("pretty", "json", "ndjson")
37
37
 
38
38
 
39
+ # Centralized command description table (id -> one-line English summary).
40
+ # Reused by `list`, `<id> --help`, and the top-level help. When adding a new
41
+ # collector/inspector, add a row here; missing ids fall back to "" via _desc().
42
+ _COMMAND_DESC = {
43
+ # Scan-type collectors (state)
44
+ "channel": "Scan channel (Feishu/Telegram/etc.) connect + message logs; flag disconnects, expired-discard, auth failures",
45
+ "configuration": "Parse key openclaw.json settings (agents/models/plugins/channels) and flag missing or risky values",
46
+ "cron_jobs": "List every cron job's full config (schedule/payload/sessionTarget/delivery/enabled); detect no-fire / delivery failures",
47
+ "doctor": "Check Node / Python / openclaw-diag / OpenClaw install and versions are ready",
48
+ "environment": "Collect host environment, Gateway process, and OpenClaw version basics",
49
+ "gateway": "Analyze Gateway process lifecycle logs (start/restart/WS connect/crash)",
50
+ "performance": "Measure model-call latency (E2E/TTFT), tool durations, and availability",
51
+ "plugin_diag": "Check plugin load status, hook subscriptions, trust gate, and plugin errors",
52
+ "recent_errors": "Extract and categorize recent errors/exceptions from logs",
53
+ "run_health": "Assess agent run completion rate, stalls, and interruptions",
54
+ "sessions_diag": "Scan session.jsonl; count toolCall/toolResult pairing, orphans, anomalies",
55
+ "shell_history": "Summarize shell commands the agent has executed",
56
+ "sys_health": "Check system-level health: CPU/memory/disk/OOM/processes",
57
+ "task_health": "Assess background task (openclaw tasks) status and health",
58
+ # Object-type inspectors
59
+ "extract": "Export a session file to readable form (incl. active/reset/deleted/backup versions)",
60
+ "panorama": "360° diagnosis: correlate trajectory + app logs + subtasks + cron",
61
+ "trace": "Trace one user message's full lifecycle (prompt->toolCall->toolResult->reply)",
62
+ }
63
+
64
+
65
+ def _desc(mid: str) -> str:
66
+ """Return the one-line English description for a command id, or ""."""
67
+ return _COMMAND_DESC.get(mid, "")
68
+
69
+
39
70
  def _paged_print(text: str) -> None:
40
71
  """Print text through a pager when stdout is a TTY and output is long.
41
72
 
@@ -99,28 +130,53 @@ def _build_context(args) -> DiagContext:
99
130
 
100
131
 
101
132
  def _common_arguments(p: argparse.ArgumentParser) -> None:
102
- p.add_argument("--config", default=paths.CONFIG)
103
- p.add_argument("--log-dir", default=paths.LOG_DIR)
104
- p.add_argument("--sessions-base", default=paths.SESSIONS_BASE)
105
- p.add_argument("--openclaw-home", default=paths.OPENCLAW_HOME)
106
- p.add_argument(
133
+ """Register the global options shared by every subcommand, in a dedicated
134
+ argument group so --help output stays aligned.
135
+ """
136
+ g = p.add_argument_group("global options")
137
+ g.add_argument(
138
+ "--config", metavar="PATH", default=paths.CONFIG,
139
+ help="Path to openclaw.json (default: ~/.openclaw/openclaw.json)",
140
+ )
141
+ g.add_argument(
142
+ "--log-dir", metavar="PATH", default=paths.LOG_DIR,
143
+ help="OpenClaw log directory (default: /tmp/openclaw)",
144
+ )
145
+ g.add_argument(
146
+ "--sessions-base", metavar="PATH", default=paths.SESSIONS_BASE,
147
+ help="Sessions root directory (default: ~/.openclaw/agents)",
148
+ )
149
+ g.add_argument(
150
+ "--openclaw-home", metavar="PATH", default=paths.OPENCLAW_HOME,
151
+ help="OpenClaw home directory (default: ~/.openclaw)",
152
+ )
153
+ g.add_argument(
107
154
  "--format",
108
155
  choices=list(_FORMAT_CHOICES),
109
156
  default=None,
110
- help="Output format (pretty|json|ndjson). Default: pretty.",
157
+ help="Output format pretty|json|ndjson (default: pretty; json/ndjson suit agents/scripts)",
158
+ )
159
+ g.add_argument(
160
+ "--json", action="store_true",
161
+ help="Alias for --format json",
162
+ )
163
+ g.add_argument(
164
+ "--no-color", action="store_true",
165
+ help="Disable ANSI color (use when writing to a file or pipe)",
166
+ )
167
+ g.add_argument(
168
+ "--unmask", action="store_true",
169
+ help="Do not mask; show raw sensitive content (tokens/message bodies). Masked by default",
111
170
  )
112
- p.add_argument("--json", action="store_true", help="Alias for --format json.")
113
- p.add_argument("--no-color", action="store_true")
114
- p.add_argument("--unmask", action="store_true")
115
171
 
116
172
 
117
173
  def _channel_arguments(p: argparse.ArgumentParser) -> None:
118
- p.add_argument(
174
+ g = p.add_argument_group("channel options")
175
+ g.add_argument(
119
176
  "--account", default=None,
120
177
  help="Filter channel signals by account substring "
121
178
  "(matched against the channel-prefix portion of the message body, "
122
- "e.g. ``--account default`` to keep only ``feishu[default]:`` lines). "
123
- "Default: no filter.",
179
+ "e.g. --account default keeps only feishu[default]: lines). Default: no filter",
124
180
  )
125
181
 
126
182
 
@@ -159,63 +215,67 @@ def cmd_list(args) -> int:
159
215
  if fmt != "pretty":
160
216
  payload = {
161
217
  "state_collectors": [
162
- {"id": c.id, "label": c.title} for c in state
218
+ {"id": c.id, "label": _desc(c.id), "description": _desc(c.id)}
219
+ for c in state
163
220
  ],
164
221
  "object_inspectors": [
165
- {"id": c.id, "label": c.title} for c in inspectors
222
+ {"id": c.id, "label": _desc(c.id), "description": _desc(c.id)}
223
+ for c in inspectors
166
224
  ],
167
225
  }
168
226
  print(json.dumps(payload, ensure_ascii=False))
169
227
  return 0
170
- print("openclaw-diag — 可用诊断 (v2)")
228
+ print("openclaw-diag — available diagnostics (v2)")
171
229
  print()
172
- print(" 扫描类(无需参数):")
230
+ print(" Scan type (no args required):")
173
231
  for c in state:
174
- print(f" {c.id:<16s} {c.title}")
232
+ d = _desc(c.id)
233
+ print(f" {c.id:<16s} {d}")
175
234
  print()
176
235
  if inspectors:
177
- print(" 对象类(需要 session uuid):")
236
+ print(" Object type (require session uuid):")
178
237
  for c in inspectors:
179
- print(f" {c.id:<16s} {c.title}")
238
+ d = _desc(c.id)
239
+ print(f" {c.id:<16s} {d}")
180
240
  print()
181
- print(" 其它命令:")
182
- print(" all 一次跑完所有扫描类")
183
- print(" doctor 检查 Node / Python / openclaw-diag / OpenClaw 环境")
184
- print(" examples 打印常用使用示例")
241
+ print(" Other commands:")
242
+ print(" all Run all scan-type diagnostics at once")
243
+ print(" doctor Check Node / Python / openclaw-diag / OpenClaw environment")
244
+ print(" examples Print common usage examples")
185
245
  return 0
186
246
 
187
247
 
188
248
  def cmd_examples() -> int:
189
- print("""openclaw-diag — 常用场景
249
+ print("""openclaw-diag — common scenarios
190
250
 
191
- # 全面体检
251
+ # Full health check
192
252
  openclaw-diag all
193
253
 
194
- # JSON 输出(Agent / 脚本)
254
+ # JSON output (for agents / scripts)
195
255
  openclaw-diag all --format json
196
256
 
197
- # Gateway 状态
257
+ # Check Gateway status
198
258
  openclaw-diag gateway
199
259
 
200
- # 追踪一条消息的完整生命周期
260
+ # Trace one message's full lifecycle
201
261
  openclaw-diag trace <uuid>
202
262
  openclaw-diag trace abc12345 --msg-index 0
203
263
 
204
- # 导出 session 对话内容
264
+ # Export session conversation content
205
265
  openclaw-diag extract <uuid>
206
266
  openclaw-diag extract abc12345 --summary
207
267
 
208
- # session 全景诊断(关联到 trajectory + 日志 + 子任务 + cron
268
+ # Session panorama diagnosis (correlates trajectory + logs + subtasks + cron)
209
269
  openclaw-diag panorama <uuid>
210
270
  openclaw-diag panorama abc12345 --strict-correlation --format json
211
271
 
212
- # 模型性能
272
+ # Model performance
213
273
  openclaw-diag performance
214
274
 
215
- # 定时任务状态
275
+ # Cron job status
216
276
  openclaw-diag cron_jobs
217
277
 
218
- # jq 快速看 verdict
278
+ # Quick verdict via jq
219
279
  openclaw-diag all --format json | jq '.data.verdict'
220
280
  """)
221
281
  return 0
@@ -300,7 +360,7 @@ def cmd_all(args, skip_ids: List[str]) -> int:
300
360
  def cmd_run_collector(args, mid: str) -> int:
301
361
  c = registry.get(mid)
302
362
  if c is None:
303
- print(f"Error: 未知 collector '{mid}'", file=sys.stderr)
363
+ print(f"Error: unknown collector '{mid}'", file=sys.stderr)
304
364
  return EXIT_INPUT_ERROR
305
365
  ctx = _build_context(args)
306
366
  t0 = time.time()
@@ -319,22 +379,22 @@ def cmd_run_collector(args, mid: str) -> int:
319
379
  return _exit_code(report)
320
380
 
321
381
 
322
- _TRACE_EPILOG = """示例:
323
- openclaw-diag trace 7e9f3b31 # session 最后一条用户消息
324
- openclaw-diag trace 7e9f3b31 --msg-index 0 # 第一条
325
- openclaw-diag trace 7e9f3b31 --msg-match deploy # 按内容匹配
326
- openclaw-diag trace 7e9f3b31 --all-messages # 一次跑完全部用户消息(每轮一段)
327
- openclaw-diag trace 7e9f3b31 -A --format json # 全部用户消息,JSON 输出
382
+ _TRACE_EPILOG = """Examples:
383
+ openclaw-diag trace 7e9f3b31 # last user message in the session
384
+ openclaw-diag trace 7e9f3b31 --msg-index 0 # the first one
385
+ openclaw-diag trace 7e9f3b31 --msg-match deploy # match by content
386
+ openclaw-diag trace 7e9f3b31 --all-messages # trace every user message in one run (one block per turn)
387
+ openclaw-diag trace 7e9f3b31 -A --format json # all user messages, JSON output
328
388
  """
329
389
 
330
- _EXTRACT_EPILOG = """示例:
331
- openclaw-diag extract 7e9f3b31 # 默认导出 active 文件
332
- openclaw-diag extract 7e9f3b31 --summary # 只看统计
333
- openclaw-diag extract 7e9f3b31 --all # reset / deleted / backup
390
+ _EXTRACT_EPILOG = """Examples:
391
+ openclaw-diag extract 7e9f3b31 # export the active file by default
392
+ openclaw-diag extract 7e9f3b31 --summary # stats only
393
+ openclaw-diag extract 7e9f3b31 --all # include reset / deleted / backup
334
394
  openclaw-diag extract 7e9f3b31 --format json
335
395
  """
336
396
 
337
- _PANORAMA_EPILOG = """示例:
397
+ _PANORAMA_EPILOG = """Examples:
338
398
  openclaw-diag panorama 7e9f3b31 # latest run
339
399
  openclaw-diag panorama 7e9f3b31 --all-runs # every run
340
400
  openclaw-diag panorama 7e9f3b31 --run-index 0 # first run
@@ -344,71 +404,139 @@ _PANORAMA_EPILOG = """示例:
344
404
 
345
405
 
346
406
  def _build_trace_parser() -> argparse.ArgumentParser:
407
+ desc_text = _desc("trace")
408
+ description = f"{desc_text} (trace)" if desc_text else "(trace)"
347
409
  p = argparse.ArgumentParser(
348
410
  prog="openclaw-diag trace",
411
+ description=description,
349
412
  add_help=True,
350
413
  epilog=_TRACE_EPILOG,
351
414
  formatter_class=argparse.RawDescriptionHelpFormatter,
352
415
  )
353
- p.add_argument("session_id", help="Session UUID (full or 8+ char prefix)")
354
- p.add_argument("--msg-index", type=int, default=None,
355
- help="Nth user message (0-based)")
356
- p.add_argument("--msg-id", default=None, help="Message by id field")
357
- p.add_argument("--msg-match", default=None,
358
- help="First user message containing TEXT")
359
- p.add_argument("-A", "--all-messages", action="store_true",
360
- dest="all_messages",
361
- help="Trace every user message in the session "
362
- "(mutually exclusive with --msg-index/--msg-id/--msg-match)")
363
- p.add_argument("--no-trajectory", action="store_true")
364
- p.add_argument("--no-log", action="store_true")
365
- p.add_argument("--show-tool-metas", action="store_true")
366
- p.add_argument("--show-plugin-snapshot", action="store_true")
367
- p.add_argument("--mask", action="store_true")
368
- p.add_argument("--agent", default=None, help="Limit to specific agent")
416
+ g = p.add_argument_group("trace options")
417
+ g.add_argument(
418
+ "session_id",
419
+ help="Target session UUID (full or 8+ char prefix)",
420
+ )
421
+ g.add_argument(
422
+ "--msg-index", type=int, default=None,
423
+ help="Nth user message (0-based)",
424
+ )
425
+ g.add_argument(
426
+ "--msg-id", default=None,
427
+ help="User message by id field",
428
+ )
429
+ g.add_argument(
430
+ "--msg-match", default=None,
431
+ help="First user message containing TEXT",
432
+ )
433
+ g.add_argument(
434
+ "-A", "--all-messages", action="store_true", dest="all_messages",
435
+ help="Trace every user message in the session "
436
+ "(mutually exclusive with --msg-index/--msg-id/--msg-match)",
437
+ )
438
+ g.add_argument(
439
+ "--no-trajectory", action="store_true",
440
+ help="Do not read trajectory.jsonl; analyze from session.jsonl only",
441
+ )
442
+ g.add_argument(
443
+ "--no-log", action="store_true",
444
+ help="Do not correlate openclaw application logs",
445
+ )
446
+ g.add_argument(
447
+ "--show-tool-metas", action="store_true",
448
+ help="Show full meta for each toolCall",
449
+ )
450
+ g.add_argument(
451
+ "--show-plugin-snapshot", action="store_true",
452
+ help="Show plugin snapshot (hooks/status)",
453
+ )
454
+ g.add_argument(
455
+ "--mask", action="store_true",
456
+ help="Force masking (trace does not mask by default)",
457
+ )
458
+ g.add_argument(
459
+ "--agent", default=None,
460
+ help="Limit to a specific agent",
461
+ )
369
462
  _common_arguments(p)
370
463
  return p
371
464
 
372
465
 
373
466
  def _build_extract_parser() -> argparse.ArgumentParser:
467
+ desc_text = _desc("extract")
468
+ description = f"{desc_text} (extract)" if desc_text else "(extract)"
374
469
  p = argparse.ArgumentParser(
375
470
  prog="openclaw-diag extract",
471
+ description=description,
376
472
  add_help=True,
377
473
  epilog=_EXTRACT_EPILOG,
378
474
  formatter_class=argparse.RawDescriptionHelpFormatter,
379
475
  )
380
- p.add_argument("session_id", help="Session UUID (full or 8+ char prefix)")
381
- p.add_argument("--summary", action="store_true",
382
- help="Per-file record-count summary, no record dump")
383
- p.add_argument("-a", "--all", action="store_true", dest="all_versions",
384
- help="Extract all versions (active + reset + deleted + backup)")
385
- p.add_argument("--list", action="store_true", dest="list_only",
386
- help="List matching files; do not extract")
387
- p.add_argument("--types", default=None,
388
- help="Filter by record type (comma-separated)")
389
- p.add_argument("--agent", default=None, help="Limit to specific agent")
476
+ g = p.add_argument_group("extract options")
477
+ g.add_argument(
478
+ "session_id",
479
+ help="Target session UUID (full or 8+ char prefix)",
480
+ )
481
+ g.add_argument(
482
+ "--summary", action="store_true",
483
+ help="Per-file record-count summary; do not dump record bodies",
484
+ )
485
+ g.add_argument(
486
+ "-a", "--all", action="store_true", dest="all_versions",
487
+ help="Export all versions (active + reset + deleted + backup)",
488
+ )
489
+ g.add_argument(
490
+ "--list", action="store_true", dest="list_only",
491
+ help="List matching files only; do not extract content",
492
+ )
493
+ g.add_argument(
494
+ "--types", default=None,
495
+ help="Filter by record type (comma-separated, e.g. user,assistant,toolCall)",
496
+ )
497
+ g.add_argument(
498
+ "--agent", default=None,
499
+ help="Limit to a specific agent",
500
+ )
390
501
  _common_arguments(p)
391
502
  return p
392
503
 
393
504
 
394
505
  def _build_panorama_parser() -> argparse.ArgumentParser:
506
+ desc_text = _desc("panorama")
507
+ description = f"{desc_text} (panorama)" if desc_text else "(panorama)"
395
508
  p = argparse.ArgumentParser(
396
509
  prog="openclaw-diag panorama",
510
+ description=description,
397
511
  add_help=True,
398
512
  epilog=_PANORAMA_EPILOG,
399
513
  formatter_class=argparse.RawDescriptionHelpFormatter,
400
514
  )
401
- p.add_argument("session_id", help="Session UUID (full or 8+ char prefix)")
402
- p.add_argument("--mask", action="store_true",
403
- help="Sanitize tool args / message content / api keys")
404
- p.add_argument("--run-index", type=int, default=None,
405
- help="Pick the Nth run (default: -1 = latest)")
406
- p.add_argument("--all-runs", action="store_true",
407
- help="Include every run in the session")
408
- p.add_argument("--strict-correlation", action="store_true",
409
- help="Match only on sessionId / runIds (drops sessionKey "
410
- "and toolCallId hits)")
411
- p.add_argument("--agent", default=None, help="Limit to specific agent")
515
+ g = p.add_argument_group("panorama options")
516
+ g.add_argument(
517
+ "session_id",
518
+ help="Target session UUID (full or 8+ char prefix)",
519
+ )
520
+ g.add_argument(
521
+ "--mask", action="store_true",
522
+ help="Sanitize tool args / message content / api keys",
523
+ )
524
+ g.add_argument(
525
+ "--run-index", type=int, default=None,
526
+ help="Pick the Nth run (default: -1 = latest)",
527
+ )
528
+ g.add_argument(
529
+ "--all-runs", action="store_true",
530
+ help="Include every run in the session",
531
+ )
532
+ g.add_argument(
533
+ "--strict-correlation", action="store_true",
534
+ help="Match only on sessionId / runIds (drop sessionKey and toolCallId hits)",
535
+ )
536
+ g.add_argument(
537
+ "--agent", default=None,
538
+ help="Limit to a specific agent",
539
+ )
412
540
  _common_arguments(p)
413
541
  return p
414
542
 
@@ -416,7 +544,7 @@ def _build_panorama_parser() -> argparse.ArgumentParser:
416
544
  def cmd_inspector(head: str, rest: List[str]) -> int:
417
545
  inspector = registry.get(head)
418
546
  if inspector is None or inspector.kind != "inspector":
419
- print(f"Error: 未知 inspector '{head}'", file=sys.stderr)
547
+ print(f"Error: unknown inspector '{head}'", file=sys.stderr)
420
548
  return EXIT_INPUT_ERROR
421
549
  if head == "trace":
422
550
  parser = _build_trace_parser()
@@ -545,19 +673,61 @@ def _split_skip(rest: List[str]):
545
673
 
546
674
 
547
675
  def _print_help() -> None:
548
- print(f"openclaw-diag v{__version__} (v2)")
549
- print()
550
- print("用法:")
551
- print(" openclaw-diag <id> [args...] 跑单个诊断")
552
- print(" openclaw-diag all [--skip a,b] 跑全部 state collectors")
553
- print(" openclaw-diag list [--format X] 列出所有诊断")
554
- print(" openclaw-diag doctor 检查环境")
555
- print(" openclaw-diag trace <uuid> 追踪一条用户消息")
556
- print(" openclaw-diag extract <uuid> 导出 session 为可读格式")
557
- print(" openclaw-diag panorama <uuid> 360° session 全景诊断")
558
- print(" openclaw-diag examples 打印常用示例")
559
- print()
560
- print("通用 flag:--format pretty|json|ndjson --json (alias) --no-color --unmask --version")
676
+ """Render the top-level --help.
677
+
678
+ Structure: intro -> usage -> health checks -> scan diagnostics (rendered
679
+ dynamically from the registry) -> object diagnostics -> helper commands
680
+ -> global options -> exit codes -> more. Scan-list ids/descriptions are
681
+ pulled from registry + _COMMAND_DESC so newly registered collectors are
682
+ never missed.
683
+ """
684
+ state_ids = [c.id for c in registry.all_state()]
685
+ width = 16
686
+ lines: List[str] = []
687
+ lines.append(f"openclaw-diag v{__version__} — OpenClaw operations diagnostics CLI")
688
+ lines.append("")
689
+ lines.append("Scans config / logs / sessions to pinpoint connection, performance, cron,")
690
+ lines.append("plugin, and run issues in an OpenClaw deployment.")
691
+ lines.append("")
692
+ lines.append("Usage:")
693
+ lines.append(" openclaw-diag <command> [args...]")
694
+ lines.append(" openclaw-diag <command> --help Show detailed args for a command")
695
+ lines.append("")
696
+ lines.append("Health checks:")
697
+ lines.append(f" {'all':<{width}s}Run every scan-type diagnostic at once (recommended first step)")
698
+ lines.append(f" {'doctor':<{width}s}Check the runtime (Node / Python / OpenClaw readiness)")
699
+ lines.append("")
700
+ lines.append("Scan diagnostics (no args required):")
701
+ for sid in state_ids:
702
+ if sid == "doctor":
703
+ # doctor is already listed under Health checks; skip the duplicate.
704
+ continue
705
+ lines.append(f" {sid:<{width}s}{_desc(sid)}")
706
+ lines.append("")
707
+ lines.append("Object diagnostics (require a session uuid):")
708
+ lines.append(f" {'trace <uuid>':<{width}s}{_desc('trace')}")
709
+ lines.append(f" {'extract <uuid>':<{width}s}{_desc('extract')}")
710
+ lines.append(f" {'panorama <uuid>':<{width}s}{_desc('panorama')}")
711
+ lines.append("")
712
+ lines.append("Helper commands:")
713
+ lines.append(f" {'list':<{width}s}List all available diagnostics (supports --format json)")
714
+ lines.append(f" {'examples':<{width}s}Print common usage examples")
715
+ lines.append("")
716
+ lines.append("Global options (all commands):")
717
+ lines.append(" --format pretty|json|ndjson Output format (default: pretty)")
718
+ lines.append(" --json Alias for --format json")
719
+ lines.append(" --no-color Disable colored output")
720
+ lines.append(" --unmask Show raw (unmasked) sensitive content")
721
+ lines.append(" --config / --log-dir / --sessions-base / --openclaw-home Override default paths")
722
+ lines.append("")
723
+ lines.append("Exit codes:")
724
+ lines.append(" 0 OK (no warn/fail)")
725
+ lines.append(" 1 Warn or fail present")
726
+ lines.append(" 2 Input error (bad args/uuid)")
727
+ lines.append(" 3 Runtime error")
728
+ lines.append("")
729
+ lines.append("More: `openclaw-diag list` for all diagnostics, `openclaw-diag <command> --help` for details.")
730
+ print("\n".join(lines))
561
731
 
562
732
 
563
733
  def main(argv: Optional[List[str]] = None) -> int:
@@ -618,9 +788,11 @@ def main(argv: Optional[List[str]] = None) -> int:
618
788
  # parse_known_args (which would otherwise execute the diagnostic).
619
789
  # parse_known_args is preserved to keep the existing lenient handling
620
790
  # of unrecognized flags from external callers.
791
+ d = _desc(coll.id)
792
+ desc = f"{d} ({coll.id})" if d else f"({coll.id})"
621
793
  cparser = argparse.ArgumentParser(
622
794
  prog=f"openclaw-diag {head}",
623
- description=f"{coll.title} ({coll.id})",
795
+ description=desc,
624
796
  add_help=True,
625
797
  )
626
798
  _common_arguments(cparser)
@@ -629,8 +801,8 @@ def main(argv: Optional[List[str]] = None) -> int:
629
801
  args, _ = cparser.parse_known_args(rest)
630
802
  return cmd_run_collector(args, head)
631
803
 
632
- print(f"Error: 未知命令 '{head}'", file=sys.stderr)
633
- print("运行 `openclaw-diag list` 查看全部诊断。", file=sys.stderr)
804
+ print(f"Error: unknown command '{head}'", file=sys.stderr)
805
+ print("Run `openclaw-diag list` to see all diagnostics.", file=sys.stderr)
634
806
  return EXIT_INPUT_ERROR
635
807
 
636
808
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-diag-cli",
3
- "version": "1.11.1",
3
+ "version": "1.12.1",
4
4
  "description": "OpenClaw observer-only diagnostic CLI. Zero-dependency Python scripts wrapped in Node for npx-friendly install.",
5
5
  "keywords": [
6
6
  "openclaw",