@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.1 → 0.1.1-alpha.10

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/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ node_fs = __toESM(node_fs);
30
30
  let node_path = require("node:path");
31
31
  node_path = __toESM(node_path);
32
32
  let node_child_process = require("node:child_process");
33
+ let node_crypto = require("node:crypto");
33
34
  //#region src/rule-engine/base.ts
34
35
  /** Abstract base class for all diagnose rules */
35
36
  var DiagnoseRule = class {
@@ -191,7 +192,7 @@ function fileExists(filePath) {
191
192
  return node_fs.default.existsSync(filePath);
192
193
  }
193
194
  /** Execute a shell command, return stdout. Throws on failure. */
194
- function shell(cmd, timeoutMs = 1e4) {
195
+ function shell(cmd, timeoutMs = 6e4) {
195
196
  return (0, node_child_process.execSync)(cmd, {
196
197
  encoding: "utf-8",
197
198
  timeout: timeoutMs
@@ -685,7 +686,9 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
685
686
  validate(ctx) {
686
687
  const expected = getExpectedOrigins(ctx.vars);
687
688
  if (expected.length === 0) return { pass: true };
688
- const missing = findMissing(getCurrentOrigins(ctx.config), expected);
689
+ const current = getCurrentOrigins(ctx.config);
690
+ if (hasWildcard(current)) return { pass: true };
691
+ const missing = findMissing(current, expected);
689
692
  if (missing.length === 0) return { pass: true };
690
693
  return {
691
694
  pass: false,
@@ -695,6 +698,7 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
695
698
  repair(ctx) {
696
699
  const expected = getExpectedOrigins(ctx.vars);
697
700
  const current = getCurrentOrigins(ctx.config);
701
+ if (hasWildcard(current)) return;
698
702
  const missing = findMissing(current, expected);
699
703
  if (missing.length > 0) {
700
704
  const seen = /* @__PURE__ */ new Set();
@@ -721,13 +725,7 @@ AllowedOriginsRule = __decorate([Rule({
721
725
  repairMode: "standard"
722
726
  })], AllowedOriginsRule);
723
727
  function getExpectedOrigins(vars) {
724
- const origins = [];
725
- if (vars.miaodaDomain) origins.push(vars.miaodaDomain);
726
- if (vars.miaodaOrigin) origins.push(vars.miaodaOrigin);
727
- if (Array.isArray(vars.miaodaOrigins)) {
728
- for (const o of vars.miaodaOrigins) if (o) origins.push(o);
729
- }
730
- return [...new Set(origins)];
728
+ return Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
731
729
  }
732
730
  function getCurrentOrigins(config) {
733
731
  const controlUi = getNestedMap(config, "gateway", "controlUi");
@@ -740,6 +738,10 @@ function findMissing(current, expected) {
740
738
  const set = new Set(current);
741
739
  return expected.filter((o) => !set.has(o));
742
740
  }
741
+ /** Exact "*" entry means allow-all; pattern globs like "https://*.example.com" don't count. */
742
+ function hasWildcard(origins) {
743
+ return origins.includes("*");
744
+ }
743
745
  //#endregion
744
746
  //#region src/rules/jwt-token.ts
745
747
  let JwtTokenRule = class JwtTokenRule extends DiagnoseRule {
@@ -990,19 +992,460 @@ function runRepair(input) {
990
992
  }
991
993
  }
992
994
  //#endregion
995
+ //#region src/backup.ts
996
+ const BACKUP_PATH = "/home/gem/workspace/.force/openclaw/core-backup.json";
997
+ function runBackup(input) {
998
+ try {
999
+ const { configPath } = input;
1000
+ try {
1001
+ const validateOutput = shell("openclaw config validate --json");
1002
+ if (!JSON.parse(validateOutput).valid) return {
1003
+ success: false,
1004
+ error: "config validation failed"
1005
+ };
1006
+ } catch (e) {
1007
+ return {
1008
+ success: false,
1009
+ error: "config validate command failed: " + e.message
1010
+ };
1011
+ }
1012
+ if (!fileExists(configPath)) return {
1013
+ success: false,
1014
+ error: "config file not found: " + configPath
1015
+ };
1016
+ const config = loadJSON5().parse(readFile(configPath));
1017
+ const backup = { _backup_meta: { created_at: (/* @__PURE__ */ new Date()).toISOString() } };
1018
+ if (config.agents) backup.agents = config.agents;
1019
+ if (config.bindings) backup.bindings = config.bindings;
1020
+ const feishu = config.channels?.feishu;
1021
+ if (feishu?.accounts) backup.channels = { feishu: { accounts: feishu.accounts } };
1022
+ const backupDir = node_path.default.dirname(BACKUP_PATH);
1023
+ if (!node_fs.default.existsSync(backupDir)) node_fs.default.mkdirSync(backupDir, { recursive: true });
1024
+ const tmpPath = BACKUP_PATH + ".tmp";
1025
+ node_fs.default.writeFileSync(tmpPath, JSON.stringify(backup, null, 2), "utf-8");
1026
+ node_fs.default.renameSync(tmpPath, BACKUP_PATH);
1027
+ return { success: true };
1028
+ } catch (e) {
1029
+ return {
1030
+ success: false,
1031
+ error: "backup failed: " + e.message
1032
+ };
1033
+ }
1034
+ }
1035
+ //#endregion
1036
+ //#region src/reset-async.ts
1037
+ /**
1038
+ * Start an async reset task: spawn a detached child process and return the taskId.
1039
+ *
1040
+ * The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
1041
+ */
1042
+ function startAsyncReset(ctxBase64) {
1043
+ const taskId = (0, node_crypto.randomUUID)();
1044
+ const resultFile = `/tmp/openclaw-reset-${taskId}.json`;
1045
+ const initial = {
1046
+ status: "running",
1047
+ step: 0,
1048
+ totalSteps: 9,
1049
+ progress: "初始化...",
1050
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1051
+ };
1052
+ const tmpPath = resultFile + ".tmp";
1053
+ const dir = node_path.default.dirname(resultFile);
1054
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1055
+ node_fs.default.writeFileSync(tmpPath, JSON.stringify(initial), "utf-8");
1056
+ node_fs.default.renameSync(tmpPath, resultFile);
1057
+ const child = (0, node_child_process.spawn)(process.execPath, [
1058
+ process.argv[1],
1059
+ "reset",
1060
+ "--worker",
1061
+ `--task-id=${taskId}`,
1062
+ `--ctx=${ctxBase64}`
1063
+ ], {
1064
+ detached: true,
1065
+ stdio: "ignore"
1066
+ });
1067
+ child.on("error", (err) => {
1068
+ const failResult = {
1069
+ status: "failed",
1070
+ step: 0,
1071
+ totalSteps: 9,
1072
+ progress: "Worker process failed to start",
1073
+ error: err.message,
1074
+ startedAt: initial.startedAt,
1075
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1076
+ };
1077
+ const errTmpPath = resultFile + ".tmp";
1078
+ node_fs.default.writeFileSync(errTmpPath, JSON.stringify(failResult));
1079
+ node_fs.default.renameSync(errTmpPath, resultFile);
1080
+ });
1081
+ child.unref();
1082
+ return { taskId };
1083
+ }
1084
+ //#endregion
1085
+ //#region src/reset.ts
1086
+ const STEPS = [
1087
+ "备份当前配置",
1088
+ "生成默认配置",
1089
+ "杀掉 openclaw 进程",
1090
+ "等待沙箱初始化完成",
1091
+ "重装 openclaw",
1092
+ "合并核心备份配置",
1093
+ "复制启动脚本",
1094
+ "重装内置插件",
1095
+ "启动并验证"
1096
+ ];
1097
+ const TOTAL_STEPS = STEPS.length;
1098
+ const CORE_BACKUP_PATH = "/home/gem/workspace/.force/openclaw/core-backup.json";
1099
+ /**
1100
+ * Directory holding the bundled openclaw template (openclaw.json + scripts/).
1101
+ * Synced from git@code.byted.org:apaas/miaoda-openclaw-template.git via
1102
+ * scripts/sync-template.sh and published alongside dist/.
1103
+ *
1104
+ * At runtime, __dirname points to dist/, so the template lives one level up.
1105
+ */
1106
+ const TEMPLATE_DIR = node_path.default.resolve(__dirname, "..", "template");
1107
+ function writeResultFile(resultFile, result) {
1108
+ const dir = node_path.default.dirname(resultFile);
1109
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1110
+ const tmpPath = resultFile + ".tmp";
1111
+ node_fs.default.writeFileSync(tmpPath, JSON.stringify(result), "utf-8");
1112
+ node_fs.default.renameSync(tmpPath, resultFile);
1113
+ }
1114
+ function updateProgress(resultFile, step, startedAt) {
1115
+ writeResultFile(resultFile, {
1116
+ status: "running",
1117
+ step,
1118
+ totalSteps: TOTAL_STEPS,
1119
+ progress: STEPS[step - 1],
1120
+ startedAt
1121
+ });
1122
+ }
1123
+ function markDone(resultFile, startedAt) {
1124
+ writeResultFile(resultFile, {
1125
+ status: "done",
1126
+ step: TOTAL_STEPS,
1127
+ totalSteps: TOTAL_STEPS,
1128
+ progress: "重置完成",
1129
+ startedAt,
1130
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1131
+ });
1132
+ }
1133
+ function markFailed(resultFile, step, error, startedAt) {
1134
+ writeResultFile(resultFile, {
1135
+ status: "failed",
1136
+ step,
1137
+ totalSteps: TOTAL_STEPS,
1138
+ progress: step > 0 ? STEPS[step - 1] : "初始化",
1139
+ error,
1140
+ startedAt,
1141
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1142
+ });
1143
+ }
1144
+ /** Step 1: Backup current config as openclaw.json.bak.N */
1145
+ function backupCurrentConfig(configPath) {
1146
+ if (!fileExists(configPath)) return;
1147
+ const dir = node_path.default.dirname(configPath);
1148
+ let maxN = 0;
1149
+ try {
1150
+ for (const f of node_fs.default.readdirSync(dir)) {
1151
+ const match = f.match(/^openclaw\.json\.bak\.(\d+)$/);
1152
+ if (match) {
1153
+ const n = parseInt(match[1], 10);
1154
+ if (n > maxN) maxN = n;
1155
+ }
1156
+ }
1157
+ } catch {}
1158
+ node_fs.default.copyFileSync(configPath, configPath + ".bak." + (maxN + 1));
1159
+ }
1160
+ /** Step 2: Replace $$__XXX__ placeholders and write default config. */
1161
+ function generateDefaultConfig(srcDir, configPath, templateVars) {
1162
+ const srcConfigPath = node_path.default.join(srcDir, "openclaw.json");
1163
+ if (!fileExists(srcConfigPath)) throw new Error("template openclaw.json not found at " + srcConfigPath);
1164
+ let content = node_fs.default.readFileSync(srcConfigPath, "utf-8");
1165
+ for (const [placeholder, value] of Object.entries(templateVars)) content = content.split(placeholder).join(value);
1166
+ node_fs.default.writeFileSync(configPath, content, "utf-8");
1167
+ }
1168
+ /** Step 3: Kill all openclaw processes. */
1169
+ function killOpenclawProcesses() {
1170
+ try {
1171
+ shell("pkill -f openclaw-gateway || true", 5e3);
1172
+ } catch {}
1173
+ shell("sleep 2", 5e3);
1174
+ }
1175
+ /**
1176
+ * Step 4: Wait for the sandbox's own init (init_sandbox.sh / concurrent npm
1177
+ * install) to finish before we start our own npm i -g. Two npm processes
1178
+ * sharing ~/.npm cache + competing for disk/network just makes everything
1179
+ * crawl; letting init finish first is the cleanest way to get exclusive
1180
+ * access. Polls every 10s up to `maxWaitMs`. If the deadline is hit we fall
1181
+ * through anyway — better to try than to fail the reset outright.
1182
+ */
1183
+ function waitForInitNpm(maxWaitMs) {
1184
+ const deadline = Date.now() + maxWaitMs;
1185
+ const ownPid = String(process.pid);
1186
+ while (Date.now() < deadline) {
1187
+ let running = 0;
1188
+ try {
1189
+ const out = shell(`pgrep -af "init_sandbox.sh|npm install|npm i " | grep -v -- "${ownPid}" | wc -l`, 1e4);
1190
+ running = parseInt(out.trim(), 10) || 0;
1191
+ } catch {
1192
+ return;
1193
+ }
1194
+ if (running === 0) return;
1195
+ try {
1196
+ shell("sleep 10", 12e3);
1197
+ } catch {}
1198
+ }
1199
+ }
1200
+ /**
1201
+ * Step 5: Reinstall openclaw to the version specified in template.
1202
+ *
1203
+ * Simple: if already at target version, skip. Otherwise uninstall+install
1204
+ * with a generous wall-clock timeout and trust npm's exit code (0 = success,
1205
+ * anything else = real failure, bubble up and fail the reset). No retries,
1206
+ * no idle-detection heuristics — waitForInitNpm above removes the main
1207
+ * source of contention so this step should run cleanly.
1208
+ */
1209
+ function reinstallOpenclaw(srcDir) {
1210
+ const targetVersion = loadJSON5().parse(node_fs.default.readFileSync(node_path.default.join(srcDir, "openclaw.json"), "utf-8")).meta?.lastTouchedVersion;
1211
+ if (targetVersion && isOpenclawAtVersion(targetVersion)) {
1212
+ shell("openclaw doctor --fix", 12e4);
1213
+ return;
1214
+ }
1215
+ try {
1216
+ shell("npm uninstall -g openclaw 2>/dev/null || true", 6e4);
1217
+ } catch {}
1218
+ shell(`npm i -g openclaw@${targetVersion || "latest"} --prefer-offline --fetch-timeout=60000 --fetch-retries=2`, 15 * 6e4);
1219
+ shell("openclaw doctor --fix", 12e4);
1220
+ }
1221
+ /** Return true if `openclaw --version` output contains `targetVersion`. */
1222
+ function isOpenclawAtVersion(targetVersion) {
1223
+ try {
1224
+ return shell("openclaw --version 2>&1 || true", 1e4).includes(targetVersion);
1225
+ } catch {
1226
+ return false;
1227
+ }
1228
+ }
1229
+ /** Step 6: Merge core-backup.json into config + ensure allowedOrigins. */
1230
+ function mergeCoreBackupAndOrigins(configPath, vars) {
1231
+ const JSON5 = loadJSON5();
1232
+ if (fileExists(CORE_BACKUP_PATH)) {
1233
+ const backup = JSON.parse(node_fs.default.readFileSync(CORE_BACKUP_PATH, "utf-8"));
1234
+ const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
1235
+ if (backup.agents) config.agents = backup.agents;
1236
+ if (backup.bindings) config.bindings = backup.bindings;
1237
+ const backupAccounts = backup.channels?.feishu;
1238
+ if (backupAccounts?.accounts) {
1239
+ if (!config.channels) config.channels = {};
1240
+ const ch = config.channels;
1241
+ if (!ch.feishu) ch.feishu = {};
1242
+ ch.feishu.accounts = backupAccounts.accounts;
1243
+ }
1244
+ node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
1245
+ }
1246
+ const expectedOrigins = Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
1247
+ if (expectedOrigins.length > 0) {
1248
+ const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
1249
+ if (!config.gateway) config.gateway = {};
1250
+ const gw = config.gateway;
1251
+ if (!gw.controlUi) gw.controlUi = {};
1252
+ const cui = gw.controlUi;
1253
+ const current = Array.isArray(cui.allowedOrigins) ? cui.allowedOrigins.filter((o) => typeof o === "string") : [];
1254
+ if (current.includes("*")) return;
1255
+ const seen = new Set(current);
1256
+ const merged = [...current];
1257
+ for (const o of expectedOrigins) if (!seen.has(o)) {
1258
+ merged.push(o);
1259
+ seen.add(o);
1260
+ }
1261
+ cui.allowedOrigins = merged;
1262
+ node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
1263
+ }
1264
+ }
1265
+ /** Step 7: Copy startup scripts from template to agent dir. */
1266
+ function copyStartupScripts(srcDir, configDir) {
1267
+ const srcScriptsDir = node_path.default.join(srcDir, "scripts");
1268
+ const targetScriptsDir = node_path.default.join(configDir, "scripts");
1269
+ if (!node_fs.default.existsSync(srcScriptsDir)) return;
1270
+ if (!node_fs.default.existsSync(targetScriptsDir)) node_fs.default.mkdirSync(targetScriptsDir, { recursive: true });
1271
+ shell(`cp -r '${srcScriptsDir}'/* '${targetScriptsDir}/'`, 1e4);
1272
+ }
1273
+ /**
1274
+ * Step 8: Reinstall all plugins via openclaw CLI.
1275
+ *
1276
+ * Simple execSync with a generous wall-clock timeout (15min). By now
1277
+ * init_sandbox's npm should have finished (Step 4 waited for it), so we
1278
+ * have exclusive npm access. Non-fatal — a plugin update failure shouldn't
1279
+ * stop the reset from restarting the gateway.
1280
+ */
1281
+ function reinstallPlugins() {
1282
+ try {
1283
+ shell("openclaw plugins update --all", 15 * 6e4);
1284
+ } catch {}
1285
+ }
1286
+ /** Step 9: Write secrets/provider key files and restart openclaw. */
1287
+ function writeSecretsAndRestart(vars, resetData, configDir) {
1288
+ if (resetData.secretsContent && vars.secretsFilePath) writeFile(vars.secretsFilePath, resetData.secretsContent);
1289
+ if (resetData.providerKeyContent && vars.providerFilePath) writeFile(vars.providerFilePath, resetData.providerKeyContent);
1290
+ const restartScript = node_path.default.join(configDir, "scripts", "restart.sh");
1291
+ if (fileExists(restartScript)) shell(`bash '${restartScript}'`, 3e4);
1292
+ }
1293
+ /**
1294
+ * Run the 9-step reset process. Called from the worker entry point.
1295
+ *
1296
+ * Each step is an independent function. The orchestrator handles progress
1297
+ * reporting, error handling, and process-level exception guards.
1298
+ *
1299
+ * The openclaw.json / scripts/*.sh template files are bundled with this CLI
1300
+ * (see TEMPLATE_DIR) and synced from the miaoda-openclaw-template repo via
1301
+ * scripts/sync-template.sh, so no runtime download is required.
1302
+ */
1303
+ function runReset(input, taskId, resultFile) {
1304
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1305
+ const { configPath, vars, resetData } = input;
1306
+ const configDir = node_path.default.dirname(configPath);
1307
+ const srcDir = TEMPLATE_DIR;
1308
+ let currentStep = 0;
1309
+ if (!node_fs.default.existsSync(node_path.default.join(srcDir, "openclaw.json"))) {
1310
+ markFailed(resultFile, 0, `bundled template not found at ${srcDir}`, startedAt);
1311
+ process.exit(1);
1312
+ }
1313
+ process.on("uncaughtException", (err) => {
1314
+ markFailed(resultFile, currentStep, `uncaught exception: ${err.message}`, startedAt);
1315
+ process.exit(1);
1316
+ });
1317
+ process.on("unhandledRejection", (reason) => {
1318
+ markFailed(resultFile, currentStep, `unhandled rejection: ${reason}`, startedAt);
1319
+ process.exit(1);
1320
+ });
1321
+ /** Advance to the next step, updating the progress file. */
1322
+ const step = (n) => {
1323
+ currentStep = n;
1324
+ updateProgress(resultFile, n, startedAt);
1325
+ };
1326
+ try {
1327
+ step(1);
1328
+ backupCurrentConfig(configPath);
1329
+ step(2);
1330
+ generateDefaultConfig(srcDir, configPath, resetData.templateVars);
1331
+ step(3);
1332
+ killOpenclawProcesses();
1333
+ step(4);
1334
+ waitForInitNpm(10 * 6e4);
1335
+ step(5);
1336
+ reinstallOpenclaw(srcDir);
1337
+ step(6);
1338
+ mergeCoreBackupAndOrigins(configPath, vars);
1339
+ step(7);
1340
+ copyStartupScripts(srcDir, configDir);
1341
+ step(8);
1342
+ reinstallPlugins();
1343
+ step(9);
1344
+ writeSecretsAndRestart(vars, resetData, configDir);
1345
+ markDone(resultFile, startedAt);
1346
+ } catch (e) {
1347
+ markFailed(resultFile, currentStep, e.message, startedAt);
1348
+ process.exit(1);
1349
+ }
1350
+ }
1351
+ //#endregion
1352
+ //#region src/get-reset-task.ts
1353
+ /**
1354
+ * Long-poll for a reset task result.
1355
+ * Reads the result file every 1s for up to 30s.
1356
+ * Returns immediately on terminal states (done/failed).
1357
+ */
1358
+ function getResetTask(taskId) {
1359
+ const resultFile = `/tmp/openclaw-reset-${taskId}.json`;
1360
+ const deadline = Date.now() + 3e4;
1361
+ while (Date.now() < deadline) {
1362
+ if (!node_fs.default.existsSync(resultFile)) {
1363
+ sleepSync(1e3);
1364
+ continue;
1365
+ }
1366
+ try {
1367
+ const content = node_fs.default.readFileSync(resultFile, "utf-8");
1368
+ const result = JSON.parse(content);
1369
+ if (result.status === "done" || result.status === "failed") return result;
1370
+ } catch {}
1371
+ sleepSync(1e3);
1372
+ }
1373
+ if (node_fs.default.existsSync(resultFile)) try {
1374
+ return JSON.parse(node_fs.default.readFileSync(resultFile, "utf-8"));
1375
+ } catch {}
1376
+ return {
1377
+ status: "running",
1378
+ progress: "等待中..."
1379
+ };
1380
+ }
1381
+ /**
1382
+ * Synchronous sleep using Atomics.wait on a shared buffer.
1383
+ */
1384
+ function sleepSync(ms) {
1385
+ const buf = new SharedArrayBuffer(4);
1386
+ const arr = new Int32Array(buf);
1387
+ Atomics.wait(arr, 0, 0, ms);
1388
+ }
1389
+ //#endregion
993
1390
  //#region src/index.ts
994
1391
  const args = node_process.default.argv.slice(2);
995
1392
  const mode = args.find((a) => !a.startsWith("--"));
996
- const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
997
- if (!mode || !["check", "repair"].includes(mode)) {
998
- console.error("Usage: mclaw-diagnose <check|repair> --ctx=<base64>");
999
- node_process.default.exit(1);
1000
- }
1001
- if (!ctx) {
1002
- console.error("Error: --ctx=<base64> is required");
1003
- node_process.default.exit(1);
1004
- }
1005
- const input = JSON.parse(Buffer.from(ctx, "base64").toString("utf-8"));
1006
- if (mode === "check") console.log(JSON.stringify(runCheck(input)));
1007
- else console.log(JSON.stringify(runRepair(input)));
1393
+ switch (mode) {
1394
+ case "check":
1395
+ case "repair": {
1396
+ const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
1397
+ if (!ctx) {
1398
+ console.error("Error: --ctx=<base64> is required");
1399
+ node_process.default.exit(1);
1400
+ }
1401
+ const input = JSON.parse(Buffer.from(ctx, "base64").toString("utf-8"));
1402
+ if (mode === "check") console.log(JSON.stringify(runCheck(input)));
1403
+ else console.log(JSON.stringify(runRepair(input)));
1404
+ break;
1405
+ }
1406
+ case "backup": {
1407
+ const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
1408
+ if (!ctx) {
1409
+ console.error("Error: --ctx=<base64> is required");
1410
+ node_process.default.exit(1);
1411
+ }
1412
+ const input = JSON.parse(Buffer.from(ctx, "base64").toString("utf-8"));
1413
+ console.log(JSON.stringify(runBackup(input)));
1414
+ break;
1415
+ }
1416
+ case "reset":
1417
+ if (args.includes("--async")) {
1418
+ const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
1419
+ if (!ctx) {
1420
+ console.error("Error: --ctx=<base64> is required");
1421
+ node_process.default.exit(1);
1422
+ }
1423
+ console.log(JSON.stringify(startAsyncReset(ctx)));
1424
+ } else if (args.includes("--worker")) {
1425
+ const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
1426
+ const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
1427
+ if (!ctx || !taskId) {
1428
+ console.error("Error: --ctx=<base64> and --task-id=<id> are required for worker");
1429
+ node_process.default.exit(1);
1430
+ }
1431
+ const resultFile = `/tmp/openclaw-reset-${taskId}.json`;
1432
+ runReset(JSON.parse(Buffer.from(ctx, "base64").toString("utf-8")), taskId, resultFile);
1433
+ } else {
1434
+ console.error("Usage: reset --async --ctx=<base64> | reset --worker --task-id=<id> --ctx=<base64>");
1435
+ node_process.default.exit(1);
1436
+ }
1437
+ break;
1438
+ case "get_reset_task": {
1439
+ const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
1440
+ if (!taskId) {
1441
+ console.error("Error: --task-id=<id> is required");
1442
+ node_process.default.exit(1);
1443
+ }
1444
+ console.log(JSON.stringify(getResetTask(taskId)));
1445
+ break;
1446
+ }
1447
+ default:
1448
+ console.error("Usage: mclaw-diagnose <check|repair|backup|reset|get_reset_task> [options]");
1449
+ node_process.default.exit(1);
1450
+ }
1008
1451
  //#endregion
package/package.json CHANGED
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.1-alpha.1",
3
+ "version": "0.1.1-alpha.10",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {
7
7
  "mclaw-diagnose": "./dist/index.cjs"
8
8
  },
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "template"
11
12
  ],
12
13
  "scripts": {
13
14
  "build": "tsdown",
14
15
  "test": "vitest run",
15
16
  "test:watch": "vitest",
16
17
  "test:integration": "vitest run --config vitest.integration.config.ts",
18
+ "sync:template": "bash scripts/sync-template.sh",
17
19
  "prepublishOnly": "npm run build"
18
20
  },
19
21
  "keywords": [
@@ -0,0 +1,522 @@
1
+ {
2
+ "meta": {
3
+ "lastTouchedVersion": "2026.4.9",
4
+ "lastTouchedAt": "2026-04-11T09:30:21.703Z"
5
+ },
6
+ "wizard": {
7
+ "lastRunAt": "2026-03-30T14:54:56.160Z",
8
+ "lastRunVersion": "2026.3.24",
9
+ "lastRunCommand": "doctor",
10
+ "lastRunMode": "local"
11
+ },
12
+ "update": {
13
+ "checkOnStart": false
14
+ },
15
+ "browser": {
16
+ "enabled": true,
17
+ "color": "#00AA00",
18
+ "executablePath": "/usr/bin/chromium-browser",
19
+ "headless": true,
20
+ "noSandbox": true,
21
+ "defaultProfile": "openclaw",
22
+ "extraArgs": [
23
+ "--window-size=1920,1080",
24
+ "--test-type",
25
+ "--disable-gpu",
26
+ "--no-sandbox"
27
+ ]
28
+ },
29
+ "secrets": {
30
+ "providers": {
31
+ "miaoda-provider": {
32
+ "source": "file",
33
+ "path": "$$__MIAODA_PROVIDER_FILEPATH__",
34
+ "mode": "singleValue"
35
+ },
36
+ "miaoda-secret-provider": {
37
+ "source": "file",
38
+ "path": "$$__MIAODA_OPENCLAW_SECRETS_FILEPATH__",
39
+ "mode": "json"
40
+ }
41
+ }
42
+ },
43
+ "models": {
44
+ "providers": {
45
+ "miaoda": {
46
+ "baseUrl": "$$__MIAODA_PROVIDER_BASE_URL__",
47
+ "apiKey": {
48
+ "source": "file",
49
+ "provider": "miaoda-provider",
50
+ "id": "value"
51
+ },
52
+ "api": "openai-completions",
53
+ "headers": {
54
+ "x-api-key": {
55
+ "source": "file",
56
+ "provider": "miaoda-secret-provider",
57
+ "id": "/models_providers_miaoda_headers_x_api_key"
58
+ }
59
+ },
60
+ "models": [
61
+ {
62
+ "id": "miaoda-model-auto",
63
+ "name": "妙搭",
64
+ "reasoning": false,
65
+ "input": [
66
+ "text"
67
+ ],
68
+ "cost": {
69
+ "input": 0,
70
+ "output": 0,
71
+ "cacheRead": 0,
72
+ "cacheWrite": 0
73
+ },
74
+ "contextWindow": 200000,
75
+ "maxTokens": 8192
76
+ },
77
+ {
78
+ "id": "miaoda-auto-multimodal",
79
+ "name": "妙搭多模态",
80
+ "reasoning": false,
81
+ "input": [
82
+ "text",
83
+ "image"
84
+ ],
85
+ "cost": {
86
+ "input": 0,
87
+ "output": 0,
88
+ "cacheRead": 0,
89
+ "cacheWrite": 0
90
+ },
91
+ "contextWindow": 200000,
92
+ "maxTokens": 8192
93
+ },
94
+ {
95
+ "id": "miaoda-model-flash",
96
+ "name": "妙搭 Flash",
97
+ "reasoning": false,
98
+ "input": [
99
+ "text"
100
+ ],
101
+ "cost": {
102
+ "input": 0,
103
+ "output": 0,
104
+ "cacheRead": 0,
105
+ "cacheWrite": 0
106
+ },
107
+ "contextWindow": 200000,
108
+ "maxTokens": 8192
109
+ }
110
+ ]
111
+ }
112
+ }
113
+ },
114
+ "agents": {
115
+ "defaults": {
116
+ "model": {
117
+ "primary": "miaoda/miaoda-model-auto"
118
+ },
119
+ "imageModel": "miaoda/miaoda-auto-multimodal",
120
+ "imageGenerationModel": {
121
+ "primary": "miaoda/miaoda-image-gen"
122
+ },
123
+ "models": {
124
+ "miaoda/miaoda-model-auto": {
125
+ "alias": "Miaoda Auto"
126
+ },
127
+ "miaoda/miaoda-auto-multimodal": {
128
+ "alias": "Miaoda Multimodal"
129
+ },
130
+ "miaoda/miaoda-model-flash": {
131
+ "alias": "Miaoda Flash"
132
+ }
133
+ },
134
+ "workspace": "$$__MIAODA_WORKSPACE_DIR__/workspace",
135
+ "compaction": {
136
+ "mode": "safeguard",
137
+ "reserveTokensFloor": 50000
138
+ },
139
+ "verboseDefault": "off",
140
+ "blockStreamingCoalesce": {
141
+ "minChars": 20,
142
+ "maxChars": 800,
143
+ "idleMs": 300
144
+ },
145
+ "humanDelay": {
146
+ "mode": "off"
147
+ },
148
+ "heartbeat": {
149
+ "every": "4h",
150
+ "activeHours": {
151
+ "start": "08:00",
152
+ "end": "22:00"
153
+ },
154
+ "model": "miaoda/miaoda-model-flash"
155
+ },
156
+ "maxConcurrent": 4,
157
+ "subagents": {
158
+ "maxConcurrent": 8
159
+ }
160
+ }
161
+ },
162
+ "tools": {
163
+ "profile": "full",
164
+ "alsoAllow": [
165
+ "feishu_bitable_app",
166
+ "feishu_bitable_app_table",
167
+ "feishu_bitable_app_table_field",
168
+ "feishu_bitable_app_table_record",
169
+ "feishu_bitable_app_table_view",
170
+ "feishu_calendar_calendar",
171
+ "feishu_calendar_event",
172
+ "feishu_calendar_event_attendee",
173
+ "feishu_calendar_freebusy",
174
+ "feishu_chat",
175
+ "feishu_chat_members",
176
+ "feishu_create_doc",
177
+ "feishu_doc_comments",
178
+ "feishu_doc_media",
179
+ "feishu_drive_file",
180
+ "feishu_fetch_doc",
181
+ "feishu_get_user",
182
+ "feishu_im_bot_image",
183
+ "feishu_im_user_fetch_resource",
184
+ "feishu_im_user_get_messages",
185
+ "feishu_im_user_get_thread_messages",
186
+ "feishu_im_user_message",
187
+ "feishu_im_user_search_messages",
188
+ "feishu_oauth",
189
+ "feishu_oauth_batch_auth",
190
+ "feishu_search_doc_wiki",
191
+ "feishu_search_user",
192
+ "feishu_sheet",
193
+ "feishu_task_comment",
194
+ "feishu_task_subtask",
195
+ "feishu_task_task",
196
+ "feishu_task_tasklist",
197
+ "feishu_update_doc",
198
+ "feishu_wiki_space",
199
+ "feishu_wiki_space_node"
200
+ ],
201
+ "deny": [
202
+ "web_fetch",
203
+ "tts",
204
+ "agents_list",
205
+ "feishu_task_task",
206
+ "feishu_task_tasklist",
207
+ "feishu_task_comment",
208
+ "feishu_task_subtask",
209
+ "feishu_bitable_app_table_view",
210
+ "feishu_doc_comments",
211
+ "feishu_doc_media",
212
+ "feishu_drive_file",
213
+ "feishu_wiki_space",
214
+ "feishu_wiki_space_node",
215
+ "feishu_sheet"
216
+ ],
217
+ "web": {
218
+ "search": {
219
+ "provider": "miaoda"
220
+ }
221
+ },
222
+ "media": {
223
+ "image": {
224
+ "models": [
225
+ {
226
+ "provider": "miaoda"
227
+ }
228
+ ]
229
+ },
230
+ "audio": {
231
+ "models": [
232
+ {
233
+ "provider": "miaoda"
234
+ }
235
+ ]
236
+ }
237
+ },
238
+ "sessions": {
239
+ "visibility": "all"
240
+ }
241
+ },
242
+ "messages": {
243
+ "ackReactionScope": "group-mentions"
244
+ },
245
+ "commands": {
246
+ "native": "auto",
247
+ "nativeSkills": "auto",
248
+ "restart": true,
249
+ "ownerDisplay": "raw"
250
+ },
251
+ "session": {
252
+ "dmScope": "per-channel-peer"
253
+ },
254
+ "channels": {
255
+ "feishu": {
256
+ "enabled": true,
257
+ "appId": "$$__FEISHU_APP_ID__",
258
+ "appSecret": {
259
+ "source": "file",
260
+ "provider": "miaoda-secret-provider",
261
+ "id": "/channels_feishu_app_secret"
262
+ },
263
+ "domain": "feishu",
264
+ "requireMention": true,
265
+ "dmPolicy": "allowlist",
266
+ "allowFrom": [
267
+ "$$__FEISHU_OPEN_ID__"
268
+ ],
269
+ "groupPolicy": "allowlist",
270
+ "groupAllowFrom": [
271
+ "$$__FEISHU_OPEN_ID__"
272
+ ],
273
+ "groups": {
274
+ "*": {
275
+ "enabled": true
276
+ }
277
+ },
278
+ "streaming": true,
279
+ "threadSession": true,
280
+ "footer": {
281
+ "elapsed": false,
282
+ "status": false
283
+ }
284
+ }
285
+ },
286
+ "discovery": {
287
+ "mdns": {
288
+ "mode": "off"
289
+ }
290
+ },
291
+ "gateway": {
292
+ "port": 18789,
293
+ "mode": "local",
294
+ "bind": "loopback",
295
+ "controlUi": {
296
+ "allowedOrigins": [
297
+ "$$__MIAODA_DOMAIN__",
298
+ "$$__MIAODA_ORIGIN__"
299
+ ],
300
+ "dangerouslyDisableDeviceAuth": true
301
+ },
302
+ "auth": {
303
+ "mode": "token",
304
+ "token": "$$__CLAW_TOKEN__"
305
+ },
306
+ "trustedProxies": [
307
+ "::1",
308
+ "127.0.0.1"
309
+ ],
310
+ "tailscale": {
311
+ "mode": "off",
312
+ "resetOnExit": false
313
+ }
314
+ },
315
+ "skills": {
316
+ "install": {
317
+ "nodeManager": "npm"
318
+ },
319
+ "entries": {
320
+ "1password": {
321
+ "enabled": false
322
+ },
323
+ "apple-notes": {
324
+ "enabled": false
325
+ },
326
+ "apple-reminders": {
327
+ "enabled": false
328
+ },
329
+ "bear-notes": {
330
+ "enabled": false
331
+ },
332
+ "bluebubbles": {
333
+ "enabled": false
334
+ },
335
+ "blucli": {
336
+ "enabled": false
337
+ },
338
+ "camsnap": {
339
+ "enabled": false
340
+ },
341
+ "coding-agent": {
342
+ "enabled": false
343
+ },
344
+ "discord": {
345
+ "enabled": false
346
+ },
347
+ "eightctl": {
348
+ "enabled": false
349
+ },
350
+ "gemini": {
351
+ "enabled": false
352
+ },
353
+ "gifgrep": {
354
+ "enabled": false
355
+ },
356
+ "gog": {
357
+ "enabled": false
358
+ },
359
+ "goplaces": {
360
+ "enabled": false
361
+ },
362
+ "imsg": {
363
+ "enabled": false
364
+ },
365
+ "model-usage": {
366
+ "enabled": false
367
+ },
368
+ "notion": {
369
+ "enabled": false
370
+ },
371
+ "openai-image-gen": {
372
+ "enabled": false
373
+ },
374
+ "openai-whisper": {
375
+ "enabled": false
376
+ },
377
+ "openai-whisper-api": {
378
+ "enabled": false
379
+ },
380
+ "openhue": {
381
+ "enabled": false
382
+ },
383
+ "oracle": {
384
+ "enabled": false
385
+ },
386
+ "ordercli": {
387
+ "enabled": false
388
+ },
389
+ "peekaboo": {
390
+ "enabled": false
391
+ },
392
+ "sag": {
393
+ "enabled": false
394
+ },
395
+ "slack": {
396
+ "enabled": false
397
+ },
398
+ "sonoscli": {
399
+ "enabled": false
400
+ },
401
+ "spotify-player": {
402
+ "enabled": false
403
+ },
404
+ "summarize": {
405
+ "enabled": false
406
+ },
407
+ "things-mac": {
408
+ "enabled": false
409
+ },
410
+ "trello": {
411
+ "enabled": false
412
+ },
413
+ "voice-call": {
414
+ "enabled": false
415
+ },
416
+ "wacli": {
417
+ "enabled": false
418
+ },
419
+ "xurl": {
420
+ "enabled": false
421
+ },
422
+ "feishu-task": {
423
+ "enabled": false
424
+ },
425
+ "gh-issues": {
426
+ "enabled": false
427
+ },
428
+ "github": {
429
+ "enabled": false
430
+ },
431
+ "tmux": {
432
+ "enabled": false
433
+ },
434
+ "blogwatcher": {
435
+ "enabled": false
436
+ },
437
+ "himalaya": {
438
+ "enabled": false
439
+ },
440
+ "video-frames": {
441
+ "enabled": false
442
+ },
443
+ "obsidian": {
444
+ "enabled": false
445
+ }
446
+ }
447
+ },
448
+ "plugins": {
449
+ "allow": [
450
+ "openclaw-lark",
451
+ "openclaw-extension-miaoda",
452
+ "openclaw-extension-miaoda-coding",
453
+ "browser"
454
+ ],
455
+ "entries": {
456
+ "feishu": {
457
+ "enabled": false
458
+ },
459
+ "openclaw-extension-miaoda": {
460
+ "enabled": true,
461
+ "config": {
462
+ "greeting": {
463
+ "message": "👋 Hi,我是**$$__MIAODA_CLAW_NAME__**,一只生活在飞书里的 🦞 小龙虾,打个招呼,我们开始认识一下?",
464
+ "targets": [
465
+ "$$__FEISHU_OPEN_ID__"
466
+ ]
467
+ }
468
+ }
469
+ },
470
+ "openclaw-lark": {
471
+ "enabled": true
472
+ },
473
+ "openclaw-extension-miaoda-coding": {
474
+ "enabled": true
475
+ },
476
+ "browser": {
477
+ "enabled": true
478
+ }
479
+ },
480
+ "installs": {
481
+ "openclaw-lark": {
482
+ "source": "npm",
483
+ "spec": "@lark-apaas/openclaw-lark",
484
+ "installPath": "./extensions/openclaw-lark",
485
+ "version": "2026.4.3",
486
+ "resolvedName": "@lark-apaas/openclaw-lark",
487
+ "resolvedVersion": "2026.4.3",
488
+ "resolvedSpec": "@lark-apaas/openclaw-lark@2026.4.3",
489
+ "integrity": "sha512-1eJ2WCvAYsnM9TPwPbEoD3nJtqNSOP2zOBV/3Vb9YQRg1kJJspEly5yCjiZt1wew4UhyN8Tcrp/XV78Qn747GA==",
490
+ "shasum": "e8d8cf05f8cb96cf5de4bca097c527eb9116e655",
491
+ "resolvedAt": "2026-04-03T06:27:44.706Z",
492
+ "installedAt": "2026-04-03T06:28:10.547Z"
493
+ },
494
+ "openclaw-extension-miaoda": {
495
+ "source": "npm",
496
+ "installPath": "./extensions/openclaw-extension-miaoda",
497
+ "version": "1.0.9",
498
+ "installedAt": "2026-04-11T09:27:06.639Z",
499
+ "spec": "@lark-apaas/openclaw-extension-miaoda@1.0.9",
500
+ "resolvedVersion": "1.0.9",
501
+ "resolvedName": "@lark-apaas/openclaw-extension-miaoda",
502
+ "resolvedSpec": "@lark-apaas/openclaw-extension-miaoda@1.0.9",
503
+ "resolvedAt": "2026-04-11T09:27:06.639Z",
504
+ "integrity": "sha512-McNeuPAUDLrMhT3yZuwk9A7pI262r2CK1N1KNQP6VuzymkDUjx2sTcJPCEBB3bFkdXd0yUU983/OhJiaJo+JWg==",
505
+ "shasum": "a3c75886e40b63f39a33a2660932f8afdae6a514"
506
+ },
507
+ "openclaw-extension-miaoda-coding": {
508
+ "source": "npm",
509
+ "spec": "@lark-apaas/openclaw-extension-miaoda-coding",
510
+ "installPath": "./extensions/openclaw-extension-miaoda-coding",
511
+ "version": "1.0.8",
512
+ "resolvedName": "@lark-apaas/openclaw-extension-miaoda-coding",
513
+ "resolvedVersion": "1.0.8",
514
+ "resolvedSpec": "@lark-apaas/openclaw-extension-miaoda-coding@1.0.8",
515
+ "integrity": "sha512-uxlLtgH2CTwz56UTaZD+n/x1p2a3Q01o3Og7oLUJCm6izWHXFEI1SQhNnCPggrfSam49KFir8xB64tY4T9dt2Q==",
516
+ "shasum": "058eadf5bc71ae87f79b5096b9d96f4afb89a9db",
517
+ "resolvedAt": "2026-04-09T11:33:36.208Z",
518
+ "installedAt": "2026-04-09T11:33:37.171Z"
519
+ }
520
+ }
521
+ }
522
+ }
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+
3
+ log() { echo "[restart.sh] $(date '+%Y-%m-%d %H:%M:%S') $*"; }
4
+
5
+ gw_alive() {
6
+ pgrep -f "openclaw-gateway|openclaw gateway" | grep -vw "$$" >/dev/null 2>&1
7
+ }
8
+
9
+ if [ -d "/run/systemd/system/" ]; then
10
+ log "systemd detected, restarting via systemctl..."
11
+ sudo systemctl restart openclaw
12
+ log "systemctl restart exit code: $?"
13
+ else
14
+ log "killing openclaw-gateway processes..."
15
+ PIDS_GW=$(pgrep -f "openclaw-gateway|openclaw gateway" | grep -vw "$$" || true)
16
+ if [ -n "$PIDS_GW" ]; then
17
+ log "killing gateway pids: $(echo $PIDS_GW | tr '\n' ' ')"
18
+ kill -9 $PIDS_GW 2>&1
19
+ else
20
+ log "no openclaw-gateway processes found"
21
+ fi
22
+
23
+ for i in $(seq 1 50); do
24
+ gw_alive || break
25
+ sleep 0.1
26
+ done
27
+
28
+ if gw_alive; then
29
+ log "ERROR: openclaw-gateway still alive after 5s, abort"
30
+ exit 1
31
+ fi
32
+ log "openclaw-gateway stopped"
33
+
34
+ log "starting openclaw gateway..."
35
+ nohup openclaw gateway run --port 18789 > /tmp/openclaw-gateway.log 2>&1 &
36
+ log "gateway started (pid=$!)"
37
+ fi
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ if [ -d "/run/systemd/system/" ]; then
3
+ sudo systemctl start openclaw
4
+ else
5
+ nohup openclaw gateway run --port 18789 > /tmp/openclaw-gateway.log 2>&1 &
6
+ fi
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bash
2
+ lsof -nP -iTCP:18789 -sTCP:LISTEN -t | xargs -r kill -9