openclaw-diag-cli 0.1.1 → 0.1.3
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 +42 -26
- package/bin/openclaw-diag.js +59 -40
- package/diag/02_environment.py +2 -2
- package/diag/10_shell_history.py +2 -2
- package/ocdiag/__init__.py +1 -1
- package/ocdiag/dispatcher.py +121 -68
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenClaw 诊断工具箱
|
|
2
2
|
|
|
3
|
-
> OpenClaw
|
|
3
|
+
> 排查 OpenClaw 故障的只读 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
|
|
27
|
+
# 3. 跑某个诊断
|
|
28
|
+
openclaw-diag gateway
|
|
29
29
|
|
|
30
|
-
# 4.
|
|
31
|
-
openclaw-diag
|
|
30
|
+
# 4. 全部 state collectors 跑一遍(任一崩了不影响其他)
|
|
31
|
+
openclaw-diag all
|
|
32
32
|
|
|
33
33
|
# 5. 输出结构化 JSON
|
|
34
|
-
openclaw-diag
|
|
34
|
+
openclaw-diag gateway --json
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
##
|
|
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
|
-
|
|
56
|
+
### Object inspectors(需要 session uuid,深挖一个具体对象)
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
| 诊断 | 看什么 |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `trace <uuid>` | 追踪一条用户消息从进入到响应的完整时间轴 |
|
|
61
|
+
| `extract <uuid>` | 导出 session.jsonl 为可读格式(reset / bak / deleted 全状态) |
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
76
|
+
openclaw-diag cron_jobs --json | jq '.data.jobs[] | select(.status!="ok")'
|
|
67
77
|
|
|
68
78
|
# 看哪个模型的 P95 延迟最高
|
|
69
|
-
openclaw-diag
|
|
79
|
+
openclaw-diag performance | grep -A1 "P95"
|
|
70
80
|
|
|
71
81
|
# 哪些插件今天有 ERROR
|
|
72
|
-
openclaw-diag
|
|
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
|
|
85
|
+
openclaw-diag all --json 2>/dev/null | jq -s '.' > report.json
|
|
76
86
|
|
|
77
87
|
# 找出有 stuck session 的事件
|
|
78
|
-
openclaw-diag
|
|
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 |
|
|
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
|
-
| **故障隔离** |
|
|
137
|
+
| **故障隔离** | 单诊断崩溃不带崩 `all` |
|
|
122
138
|
| **数据可靠** | 每个字段都能溯源 |
|
|
123
139
|
| **可组合** | 文本 + JSON 双输出,stderr 与 stdout 分流 |
|
|
124
140
|
|
package/bin/openclaw-diag.js
CHANGED
|
@@ -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
|
|
70
|
+
'openclaw-diag — OpenClaw 诊断工具箱',
|
|
55
71
|
'',
|
|
56
72
|
'Usage:',
|
|
57
|
-
' openclaw-diag
|
|
58
|
-
' openclaw-diag list
|
|
59
|
-
' openclaw-diag
|
|
60
|
-
' openclaw-diag
|
|
61
|
-
' openclaw-diag
|
|
62
|
-
' openclaw-diag bundle <id>
|
|
63
|
-
' openclaw-diag doctor [--json]
|
|
64
|
-
' openclaw-diag --version
|
|
65
|
-
' openclaw-diag --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
|
-
'
|
|
68
|
-
'
|
|
86
|
+
'Object inspectors (需要 session uuid):',
|
|
87
|
+
' ' + OBJECT_INSPECTORS.join(' '),
|
|
69
88
|
'',
|
|
70
|
-
'
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
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 ${
|
|
235
|
+
console.log(`✓ All ${total} diagnostics respond to --help`);
|
|
218
236
|
} else {
|
|
219
|
-
console.log(`✗ ${failed.length}/${
|
|
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
|
|
261
|
+
console.log(`openclaw-diag v${PKG.version} — OpenClaw 诊断工具箱`);
|
|
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
|
|
250
|
-
console.log(' openclaw-diag
|
|
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
|
|
294
|
+
// Pass through everything else (flat ids, `all`, `list`, `run` alias, unknown) to dispatcher.
|
|
276
295
|
runDispatcher(argv);
|
|
277
296
|
}
|
|
278
297
|
|
package/diag/02_environment.py
CHANGED
|
@@ -118,9 +118,9 @@ def main() -> int:
|
|
|
118
118
|
|
|
119
119
|
oc_version = detect_oc_version()
|
|
120
120
|
if oc_version:
|
|
121
|
-
out.item(f"
|
|
121
|
+
out.item(f"OpenClaw 版本: {oc_version}")
|
|
122
122
|
else:
|
|
123
|
-
out.item("
|
|
123
|
+
out.item("OpenClaw 版本: 无法确定")
|
|
124
124
|
out.evidence("openclaw --version", "命令未找到或无输出")
|
|
125
125
|
out.set_data("oc_version", oc_version)
|
|
126
126
|
|
package/diag/10_shell_history.py
CHANGED
|
@@ -89,13 +89,13 @@ def main() -> int:
|
|
|
89
89
|
oc_cmds = oc_all[-30:]
|
|
90
90
|
if oc_total:
|
|
91
91
|
out.item(
|
|
92
|
-
f"
|
|
92
|
+
f" OpenClaw 相关命令: 全文 {oc_total} 条,最近 30 条采样 {len(oc_cmds)} 条 — "
|
|
93
93
|
"用户手动执行的 openclaw 命令"
|
|
94
94
|
)
|
|
95
95
|
ev = "\n".join(f"{n}: {ln}" for n, ln in oc_cmds)
|
|
96
96
|
out.evidence(f"{hfile} (openclaw)", ev)
|
|
97
97
|
else:
|
|
98
|
-
out.item("
|
|
98
|
+
out.item(" OpenClaw 相关命令: 0 条")
|
|
99
99
|
|
|
100
100
|
recent = lines[-20:]
|
|
101
101
|
if recent:
|
package/ocdiag/__init__.py
CHANGED
package/ocdiag/dispatcher.py
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
"""Dispatcher:
|
|
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
|
-
#
|
|
17
|
-
|
|
18
|
-
("sys_health", "系统健康检查",
|
|
19
|
-
("environment", "
|
|
20
|
-
("configuration", "
|
|
21
|
-
("gateway", "
|
|
22
|
-
("recent_errors", "
|
|
23
|
-
("cron_jobs", "
|
|
24
|
-
("performance", "
|
|
25
|
-
("sessions", "
|
|
26
|
-
("plugin_diag", "
|
|
27
|
-
("shell_history", "
|
|
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
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
+
head, rest = argv[0], argv[1:]
|
|
107
155
|
|
|
108
|
-
if
|
|
156
|
+
if head == "list":
|
|
109
157
|
return cmd_list()
|
|
110
158
|
|
|
111
|
-
if
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-diag-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "OpenClaw
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "OpenClaw read-only diagnostic CLI. Zero-dependency Python scripts wrapped in Node for npx-friendly install.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
7
|
-
"arkclaw",
|
|
8
7
|
"diagnostic",
|
|
9
8
|
"cli",
|
|
10
9
|
"ops",
|