@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.2 → 0.1.1-alpha.21

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
@@ -244,11 +245,15 @@ function findBackupFiles(configPath) {
244
245
  }
245
246
  /**
246
247
  * Among backup files, find the one with the highest numeric suffix.
247
- * `.bak` (no number) is treated as 0, `.bak1` as 1, `.bak2` as 2, etc.
248
+ * Supports all three naming styles used by the current backup code and its
249
+ * older variants:
250
+ * `.bak` → n = 0 (legacy single-slot backup)
251
+ * `.bakN` → n = N (older style, dot-less)
252
+ * `.bak.N` → n = N (current style written by reset Step 1)
248
253
  */
249
254
  function findHighestBackup(backupFiles) {
250
255
  if (backupFiles.length === 0) return null;
251
- const bakRegex = /\.bak(\d*)$/;
256
+ const bakRegex = /\.bak\.?(\d*)$/;
252
257
  let best = null;
253
258
  for (const f of backupFiles) {
254
259
  const match = bakRegex.exec(f);
@@ -685,7 +690,9 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
685
690
  validate(ctx) {
686
691
  const expected = getExpectedOrigins(ctx.vars);
687
692
  if (expected.length === 0) return { pass: true };
688
- const missing = findMissing(getCurrentOrigins(ctx.config), expected);
693
+ const current = getCurrentOrigins(ctx.config);
694
+ if (hasWildcard(current)) return { pass: true };
695
+ const missing = findMissing(current, expected);
689
696
  if (missing.length === 0) return { pass: true };
690
697
  return {
691
698
  pass: false,
@@ -695,6 +702,7 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
695
702
  repair(ctx) {
696
703
  const expected = getExpectedOrigins(ctx.vars);
697
704
  const current = getCurrentOrigins(ctx.config);
705
+ if (hasWildcard(current)) return;
698
706
  const missing = findMissing(current, expected);
699
707
  if (missing.length > 0) {
700
708
  const seen = /* @__PURE__ */ new Set();
@@ -734,6 +742,10 @@ function findMissing(current, expected) {
734
742
  const set = new Set(current);
735
743
  return expected.filter((o) => !set.has(o));
736
744
  }
745
+ /** Exact "*" entry means allow-all; pattern globs like "https://*.example.com" don't count. */
746
+ function hasWildcard(origins) {
747
+ return origins.includes("*");
748
+ }
737
749
  //#endregion
738
750
  //#region src/rules/jwt-token.ts
739
751
  let JwtTokenRule = class JwtTokenRule extends DiagnoseRule {
@@ -984,19 +996,640 @@ function runRepair(input) {
984
996
  }
985
997
  }
986
998
  //#endregion
999
+ //#region src/paths.ts
1000
+ /**
1001
+ * Central directory for all ephemeral diagnose/reset artifacts: task status
1002
+ * files (`reset-<taskId>.json`) and human-readable step logs
1003
+ * (`reset-<taskId>.log`). Having everything under one dir makes debugging a
1004
+ * stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
1005
+ * run, and each run's log is right next to its state.
1006
+ */
1007
+ const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
1008
+ function resetResultFile(taskId) {
1009
+ return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
1010
+ }
1011
+ function resetLogFile(taskId) {
1012
+ return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
1013
+ }
1014
+ //#endregion
1015
+ //#region src/logger.ts
1016
+ function makeLogger(logFile) {
1017
+ try {
1018
+ const dir = node_path.default.dirname(logFile);
1019
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1020
+ } catch {}
1021
+ return (msg) => {
1022
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}\n`;
1023
+ try {
1024
+ node_fs.default.appendFileSync(logFile, line);
1025
+ } catch {}
1026
+ };
1027
+ }
1028
+ //#endregion
1029
+ //#region src/reset-async.ts
1030
+ /**
1031
+ * Start an async reset task: spawn a detached child process and return the taskId.
1032
+ *
1033
+ * The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
1034
+ */
1035
+ function startAsyncReset(ctxBase64) {
1036
+ const taskId = (0, node_crypto.randomUUID)();
1037
+ const resultFile = resetResultFile(taskId);
1038
+ const log = makeLogger(resetLogFile(taskId));
1039
+ log(`=== startAsyncReset spawning worker for taskId=${taskId} ===`);
1040
+ const initial = {
1041
+ status: "running",
1042
+ step: 0,
1043
+ totalSteps: 9,
1044
+ progress: "初始化...",
1045
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1046
+ };
1047
+ const tmpPath = resultFile + ".tmp";
1048
+ const dir = node_path.default.dirname(resultFile);
1049
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1050
+ node_fs.default.writeFileSync(tmpPath, JSON.stringify(initial), "utf-8");
1051
+ node_fs.default.renameSync(tmpPath, resultFile);
1052
+ const child = (0, node_child_process.spawn)(process.execPath, [
1053
+ process.argv[1],
1054
+ "reset",
1055
+ "--worker",
1056
+ `--task-id=${taskId}`,
1057
+ `--ctx=${ctxBase64}`
1058
+ ], {
1059
+ detached: true,
1060
+ stdio: "ignore"
1061
+ });
1062
+ child.on("error", (err) => {
1063
+ log(`FATAL worker failed to start: ${err.message}`);
1064
+ const failResult = {
1065
+ status: "failed",
1066
+ step: 0,
1067
+ totalSteps: 9,
1068
+ progress: "Worker process failed to start",
1069
+ error: err.message,
1070
+ startedAt: initial.startedAt,
1071
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1072
+ };
1073
+ const errTmpPath = resultFile + ".tmp";
1074
+ node_fs.default.writeFileSync(errTmpPath, JSON.stringify(failResult));
1075
+ node_fs.default.renameSync(errTmpPath, resultFile);
1076
+ });
1077
+ child.unref();
1078
+ log(`spawned worker pid=${child.pid}`);
1079
+ return { taskId };
1080
+ }
1081
+ //#endregion
1082
+ //#region src/reset.ts
1083
+ const STEPS = [
1084
+ "备份当前配置",
1085
+ "生成默认配置",
1086
+ "杀掉 openclaw 进程",
1087
+ "等待沙箱初始化完成",
1088
+ "确认 openclaw 版本",
1089
+ "合并核心备份配置",
1090
+ "复制启动脚本",
1091
+ "安装扩展",
1092
+ "启动并验证"
1093
+ ];
1094
+ const TOTAL_STEPS = STEPS.length;
1095
+ /** Pre-packed extensions archive on OSS. Update this URL when releasing a new version. */
1096
+ const EXTENSIONS_OSS_URL = "https://miaoda-template-online.oss-cn-beijing.aliyuncs.com/builtin/tool/pkg/openclaw-extensions-2026.4.9.tar.gz";
1097
+ /**
1098
+ * Directory holding the bundled openclaw template (openclaw.json + scripts/).
1099
+ * Synced from git@code.byted.org:apaas/miaoda-openclaw-template.git via
1100
+ * scripts/sync-template.sh and published alongside dist/.
1101
+ *
1102
+ * At runtime, __dirname points to dist/, so the template lives one level up.
1103
+ */
1104
+ const TEMPLATE_DIR = node_path.default.resolve(__dirname, "..", "template");
1105
+ function writeResultFile(resultFile, result) {
1106
+ const dir = node_path.default.dirname(resultFile);
1107
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1108
+ const tmpPath = resultFile + ".tmp";
1109
+ node_fs.default.writeFileSync(tmpPath, JSON.stringify(result), "utf-8");
1110
+ node_fs.default.renameSync(tmpPath, resultFile);
1111
+ }
1112
+ function updateProgress(resultFile, step, startedAt) {
1113
+ writeResultFile(resultFile, {
1114
+ status: "running",
1115
+ step,
1116
+ totalSteps: TOTAL_STEPS,
1117
+ progress: STEPS[step - 1],
1118
+ startedAt
1119
+ });
1120
+ }
1121
+ function markDone(resultFile, startedAt) {
1122
+ writeResultFile(resultFile, {
1123
+ status: "done",
1124
+ step: TOTAL_STEPS,
1125
+ totalSteps: TOTAL_STEPS,
1126
+ progress: "重置完成",
1127
+ startedAt,
1128
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1129
+ });
1130
+ }
1131
+ function markFailed(resultFile, step, error, startedAt) {
1132
+ writeResultFile(resultFile, {
1133
+ status: "failed",
1134
+ step,
1135
+ totalSteps: TOTAL_STEPS,
1136
+ progress: step > 0 ? STEPS[step - 1] : "初始化",
1137
+ error,
1138
+ startedAt,
1139
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1140
+ });
1141
+ }
1142
+ /** Step 1: Backup current config as openclaw.json.bak.N */
1143
+ function backupCurrentConfig(configPath, log) {
1144
+ if (!fileExists(configPath)) {
1145
+ log("no existing config, skip backup");
1146
+ return;
1147
+ }
1148
+ const dir = node_path.default.dirname(configPath);
1149
+ let maxN = 0;
1150
+ try {
1151
+ for (const f of node_fs.default.readdirSync(dir)) {
1152
+ const match = f.match(/^openclaw\.json\.bak\.(\d+)$/);
1153
+ if (match) {
1154
+ const n = parseInt(match[1], 10);
1155
+ if (n > maxN) maxN = n;
1156
+ }
1157
+ }
1158
+ } catch {}
1159
+ const bakPath = configPath + ".bak." + (maxN + 1);
1160
+ node_fs.default.copyFileSync(configPath, bakPath);
1161
+ log(`backed up to ${bakPath}`);
1162
+ }
1163
+ /** Step 2: Replace $$__XXX__ placeholders and write default config. */
1164
+ function generateDefaultConfig(srcDir, configPath, templateVars, log) {
1165
+ const srcConfigPath = node_path.default.join(srcDir, "openclaw.json");
1166
+ if (!fileExists(srcConfigPath)) throw new Error("template openclaw.json not found at " + srcConfigPath);
1167
+ let content = node_fs.default.readFileSync(srcConfigPath, "utf-8");
1168
+ let replaced = 0;
1169
+ for (const [placeholder, value] of Object.entries(templateVars)) {
1170
+ const parts = content.split(placeholder);
1171
+ if (parts.length > 1) replaced += parts.length - 1;
1172
+ content = parts.join(value);
1173
+ }
1174
+ node_fs.default.writeFileSync(configPath, content, "utf-8");
1175
+ log(`wrote ${configPath} (${replaced} placeholder(s) replaced, ${Object.keys(templateVars).length} provided)`);
1176
+ }
1177
+ /** Step 3: Kill all openclaw processes. */
1178
+ function killOpenclawProcesses(log) {
1179
+ try {
1180
+ shell("pkill -f openclaw-gateway || true", 5e3);
1181
+ } catch {}
1182
+ shell("sleep 2", 5e3);
1183
+ log("killed openclaw-gateway processes");
1184
+ }
1185
+ /**
1186
+ * Step 4: Wait for the sandbox's own init (init_sandbox.sh / concurrent npm
1187
+ * install) to finish before we start our own npm i -g. Two npm processes
1188
+ * sharing ~/.npm cache + competing for disk/network just makes everything
1189
+ * crawl; letting init finish first is the cleanest way to get exclusive
1190
+ * access. Polls every 10s up to `maxWaitMs`. If the deadline is hit we fall
1191
+ * through anyway — better to try than to fail the reset outright.
1192
+ */
1193
+ function waitForInitNpm(maxWaitMs, log) {
1194
+ const deadline = Date.now() + maxWaitMs;
1195
+ const ownPid = String(process.pid);
1196
+ let polls = 0;
1197
+ while (Date.now() < deadline) {
1198
+ polls++;
1199
+ let running = 0;
1200
+ try {
1201
+ const out = shell(`pgrep -af "init_sandbox.sh|npm install|npm i " | grep -v -- "${ownPid}" | wc -l`, 1e4);
1202
+ running = parseInt(out.trim(), 10) || 0;
1203
+ } catch {
1204
+ log(`poll ${polls}: no concurrent npm, proceeding`);
1205
+ return;
1206
+ }
1207
+ if (running === 0) {
1208
+ log(`poll ${polls}: no concurrent npm, proceeding`);
1209
+ return;
1210
+ }
1211
+ log(`poll ${polls}: ${running} concurrent npm/init process(es) still running, waiting 10s`);
1212
+ try {
1213
+ shell("sleep 10", 12e3);
1214
+ } catch {}
1215
+ }
1216
+ log(`deadline (${maxWaitMs}ms) hit after ${polls} poll(s), proceeding anyway`);
1217
+ }
1218
+ /**
1219
+ * Step 5: Ensure openclaw binary is at the template's recommended version.
1220
+ *
1221
+ * Only checks/installs the binary — does NOT run `doctor --fix` any more.
1222
+ * Extensions are installed separately via OSS tar.gz (Step 8), which means
1223
+ * the openclaw-lark extension schema is already in place when openclaw
1224
+ * starts, avoiding the schema-priority mismatch that caused doctor to
1225
+ * reject valid config fields like threadSession / footer.
1226
+ */
1227
+ function ensureOpenclawBinary(srcDir, configPath, log) {
1228
+ const targetVersion = loadJSON5().parse(node_fs.default.readFileSync(node_path.default.join(srcDir, "openclaw.json"), "utf-8")).meta?.lastTouchedVersion;
1229
+ log(`target openclaw version: ${targetVersion ?? "<unset>"}`);
1230
+ if (targetVersion && isOpenclawAtVersion(targetVersion)) {
1231
+ log("openclaw already at target version, nothing to do");
1232
+ return;
1233
+ }
1234
+ if (isOpenclawInstalled()) {
1235
+ const updateCmd = `openclaw update${targetVersion ? " --tag " + targetVersion : ""} --yes`;
1236
+ log(`openclaw installed but version mismatched, running: ${updateCmd}`);
1237
+ const timeout = 12 * 6e4;
1238
+ const t = Date.now();
1239
+ const tmpConfig = configPath + ".update-tmp";
1240
+ const hadConfig = node_fs.default.existsSync(configPath);
1241
+ if (hadConfig) {
1242
+ node_fs.default.renameSync(configPath, tmpConfig);
1243
+ log("temporarily hid config to bypass config guard");
1244
+ }
1245
+ try {
1246
+ shell(updateCmd, timeout);
1247
+ log(`openclaw update done in ${Date.now() - t}ms`);
1248
+ } catch (e) {
1249
+ const elapsed = Date.now() - t;
1250
+ if (elapsed >= timeout - 1e3) log(`openclaw update timed out after ${elapsed}ms (non-fatal, continuing)`);
1251
+ else throw e;
1252
+ } finally {
1253
+ if (hadConfig && node_fs.default.existsSync(tmpConfig)) {
1254
+ node_fs.default.renameSync(tmpConfig, configPath);
1255
+ log("restored config after update");
1256
+ }
1257
+ }
1258
+ } else {
1259
+ log("openclaw binary not found, running full reinstall");
1260
+ fullReinstall(targetVersion, log);
1261
+ }
1262
+ }
1263
+ /** Check if openclaw command exists (regardless of version). */
1264
+ function isOpenclawInstalled() {
1265
+ try {
1266
+ shell("which openclaw 2>/dev/null", 5e3);
1267
+ return true;
1268
+ } catch {
1269
+ return false;
1270
+ }
1271
+ }
1272
+ /** Full uninstall + reinstall from npm (slow path, triggers postinstall). */
1273
+ function fullReinstall(targetVersion, log) {
1274
+ try {
1275
+ shell("npm uninstall -g openclaw 2>/dev/null || true", 6e4);
1276
+ } catch {}
1277
+ try {
1278
+ shell("rm -rf /home/gem/.npm-global/lib/node_modules/openclaw /home/gem/.npm-global/bin/openclaw 2>/dev/null || true", 1e4);
1279
+ log("force-cleaned residual openclaw global dir");
1280
+ } catch {}
1281
+ const installCmd = `npm i -g openclaw@${targetVersion || "latest"}`;
1282
+ log(`running: ${installCmd}`);
1283
+ const t = Date.now();
1284
+ shell(installCmd, 30 * 6e4);
1285
+ log(`npm install done in ${Date.now() - t}ms`);
1286
+ }
1287
+ /** Return true if `openclaw --version` output contains `targetVersion`. */
1288
+ function isOpenclawAtVersion(targetVersion) {
1289
+ try {
1290
+ return shell("openclaw --version 2>&1 || true", 1e4).includes(targetVersion);
1291
+ } catch {
1292
+ return false;
1293
+ }
1294
+ }
1295
+ /** Step 6: Merge coreBackup from resetData + ensure allowedOrigins. */
1296
+ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
1297
+ const JSON5 = loadJSON5();
1298
+ const backup = resetData.coreBackup;
1299
+ if (backup) {
1300
+ const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
1301
+ const merged = [];
1302
+ if (backup.agents && backup.agents.length > 0) {
1303
+ if (!config.agents) config.agents = {};
1304
+ const agents = config.agents;
1305
+ if (!Array.isArray(agents.list)) agents.list = [];
1306
+ const configDir = node_path.default.dirname(configPath);
1307
+ for (const agent of backup.agents) {
1308
+ const enriched = {
1309
+ id: agent.id,
1310
+ name: agent.id,
1311
+ workspace: agent.workspace,
1312
+ agentDir: configDir + "/agents/" + agent.id + "/agent"
1313
+ };
1314
+ agents.list.push(enriched);
1315
+ }
1316
+ merged.push(`agents(+${backup.agents.length})`);
1317
+ const list = agents.list;
1318
+ let mainIdx = list.findIndex((a) => a.id === "main");
1319
+ if (mainIdx < 0) {
1320
+ list.unshift({ id: "main" });
1321
+ mainIdx = 0;
1322
+ }
1323
+ list[mainIdx].subagents = { allowAgents: ["*"] };
1324
+ list[mainIdx].default = true;
1325
+ merged.push("main-team-mode");
1326
+ const feishu = config.channels?.feishu;
1327
+ if (feishu) {
1328
+ if (!feishu.accounts) feishu.accounts = {};
1329
+ const accounts = feishu.accounts;
1330
+ const defaultAccount = {};
1331
+ for (const key of [
1332
+ "dmPolicy",
1333
+ "allowFrom",
1334
+ "groupPolicy",
1335
+ "groupAllowFrom"
1336
+ ]) if (feishu[key] !== void 0) defaultAccount[key] = feishu[key];
1337
+ if (Object.keys(defaultAccount).length > 0) {
1338
+ accounts.default = defaultAccount;
1339
+ merged.push("accounts.default");
1340
+ }
1341
+ }
1342
+ }
1343
+ if (backup.bindings && backup.bindings.length > 0) {
1344
+ config.bindings = backup.bindings;
1345
+ merged.push("bindings");
1346
+ }
1347
+ const backupAccounts = backup.channels?.feishu?.accounts;
1348
+ if (backupAccounts && Object.keys(backupAccounts).length > 0) {
1349
+ if (!config.channels) config.channels = {};
1350
+ const ch = config.channels;
1351
+ if (!ch.feishu) ch.feishu = {};
1352
+ const feishu = ch.feishu;
1353
+ if (!feishu.accounts) feishu.accounts = {};
1354
+ Object.assign(feishu.accounts, backupAccounts);
1355
+ merged.push("channels.feishu.accounts");
1356
+ }
1357
+ node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
1358
+ log(`merged from coreBackup: [${merged.join(", ") || "nothing"}]`);
1359
+ } else log("no coreBackup in resetData, skip multi-agent merge");
1360
+ const expectedOrigins = Array.isArray(vars.expectedOrigins) ? vars.expectedOrigins : [];
1361
+ if (expectedOrigins.length === 0) {
1362
+ log("no expectedOrigins provided");
1363
+ return;
1364
+ }
1365
+ const config = JSON5.parse(node_fs.default.readFileSync(configPath, "utf-8"));
1366
+ if (!config.gateway) config.gateway = {};
1367
+ const gw = config.gateway;
1368
+ if (!gw.controlUi) gw.controlUi = {};
1369
+ const cui = gw.controlUi;
1370
+ const current = Array.isArray(cui.allowedOrigins) ? cui.allowedOrigins.filter((o) => typeof o === "string") : [];
1371
+ if (current.includes("*")) {
1372
+ log("allowedOrigins already contains \"*\", skip origin merge");
1373
+ return;
1374
+ }
1375
+ const seen = new Set(current);
1376
+ const added = [];
1377
+ const mergedOrigins = [...current];
1378
+ for (const o of expectedOrigins) if (!seen.has(o)) {
1379
+ mergedOrigins.push(o);
1380
+ seen.add(o);
1381
+ added.push(o);
1382
+ }
1383
+ cui.allowedOrigins = mergedOrigins;
1384
+ node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
1385
+ log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
1386
+ }
1387
+ /** Step 7: Copy startup scripts from template to agent dir. */
1388
+ function copyStartupScripts(srcDir, configDir, log) {
1389
+ const srcScriptsDir = node_path.default.join(srcDir, "scripts");
1390
+ const targetScriptsDir = node_path.default.join(configDir, "scripts");
1391
+ if (!node_fs.default.existsSync(srcScriptsDir)) {
1392
+ log(`no scripts/ in template, skip`);
1393
+ return;
1394
+ }
1395
+ if (!node_fs.default.existsSync(targetScriptsDir)) node_fs.default.mkdirSync(targetScriptsDir, { recursive: true });
1396
+ shell(`cp -r '${srcScriptsDir}'/* '${targetScriptsDir}/'`, 1e4);
1397
+ log(`copied scripts/* -> ${targetScriptsDir}`);
1398
+ }
1399
+ /**
1400
+ * Step 8: Install extensions from OSS tar.gz or fall back to `openclaw plugins update`.
1401
+ *
1402
+ * 1. Derive the OSS URL from the openclaw version in the template config
1403
+ * 2. Download tar.gz → extract to a staging dir
1404
+ * 3. For each included extension, backup (mv) the user's existing copy
1405
+ * to a temp dir — extensions NOT in the archive are left untouched
1406
+ * 4. Move the fresh extensions from staging to the target dir
1407
+ *
1408
+ * This bypasses npm entirely and avoids the schema-priority mismatch where
1409
+ * `openclaw doctor --fix` would validate config against the bundled feishu
1410
+ * plugin's strict schema before openclaw-lark is installed.
1411
+ *
1412
+ * Falls back to `openclaw plugins update --all` if the OSS download fails.
1413
+ */
1414
+ function installExtensions(configDir, log) {
1415
+ const ossUrl = EXTENSIONS_OSS_URL;
1416
+ const targetExtDir = node_path.default.join(configDir, "extensions");
1417
+ const tmpDir = node_path.default.join(configDir, `.ext-reset-tmp-${Date.now()}`);
1418
+ const tarPath = node_path.default.join(tmpDir, "extensions.tar.gz");
1419
+ node_fs.default.mkdirSync(tmpDir, { recursive: true });
1420
+ try {
1421
+ log(`downloading extensions from ${ossUrl}`);
1422
+ const dlStart = Date.now();
1423
+ shell(`curl -fsSL '${ossUrl}' -o '${tarPath}'`, 5 * 6e4);
1424
+ const size = node_fs.default.statSync(tarPath).size;
1425
+ log(`download done in ${Date.now() - dlStart}ms (${(size / 1024 / 1024).toFixed(1)}MB)`);
1426
+ shell(`tar -xzf '${tarPath}' -C '${tmpDir}'`, 6e4);
1427
+ const stagedExtDir = node_path.default.join(tmpDir, "extensions");
1428
+ if (!node_fs.default.existsSync(stagedExtDir)) throw new Error("tar.gz does not contain an extensions/ directory");
1429
+ const extNames = node_fs.default.readdirSync(stagedExtDir).filter((name) => node_fs.default.statSync(node_path.default.join(stagedExtDir, name)).isDirectory());
1430
+ log(`archive contains ${extNames.length} extension(s): ${extNames.join(", ")}`);
1431
+ for (const name of extNames) {
1432
+ const target = node_path.default.join(targetExtDir, name);
1433
+ if (node_fs.default.existsSync(target)) node_fs.default.rmSync(target, {
1434
+ recursive: true,
1435
+ force: true
1436
+ });
1437
+ node_fs.default.renameSync(node_path.default.join(stagedExtDir, name), target);
1438
+ log(` ${name}: installed`);
1439
+ }
1440
+ const packinfo = node_path.default.join(stagedExtDir, ".packinfo.json");
1441
+ if (node_fs.default.existsSync(packinfo)) {
1442
+ node_fs.default.copyFileSync(packinfo, node_path.default.join(targetExtDir, ".packinfo.json"));
1443
+ log(`packinfo: ${node_fs.default.readFileSync(packinfo, "utf-8").trim()}`);
1444
+ }
1445
+ log("extensions installed from OSS successfully");
1446
+ } finally {
1447
+ try {
1448
+ node_fs.default.rmSync(tmpDir, {
1449
+ recursive: true,
1450
+ force: true
1451
+ });
1452
+ } catch {}
1453
+ }
1454
+ }
1455
+ /** Step 9: Write secrets/provider key files and restart openclaw. */
1456
+ function writeSecretsAndRestart(vars, resetData, configDir, log) {
1457
+ if (resetData.secretsContent && vars.secretsFilePath) {
1458
+ writeFile(vars.secretsFilePath, resetData.secretsContent);
1459
+ log(`wrote secrets to ${vars.secretsFilePath}`);
1460
+ }
1461
+ if (resetData.providerKeyContent && vars.providerFilePath) {
1462
+ writeFile(vars.providerFilePath, resetData.providerKeyContent);
1463
+ log(`wrote provider key to ${vars.providerFilePath}`);
1464
+ }
1465
+ const restartScript = node_path.default.join(configDir, "scripts", "restart.sh");
1466
+ if (fileExists(restartScript)) {
1467
+ const t = Date.now();
1468
+ shell(`bash '${restartScript}'`, 3e4);
1469
+ log(`restart.sh done in ${Date.now() - t}ms`);
1470
+ } else log(`no restart.sh at ${restartScript}, skip`);
1471
+ }
1472
+ /**
1473
+ * Run the 9-step reset process. Called from the worker entry point.
1474
+ *
1475
+ * Each step is an independent function. The orchestrator handles progress
1476
+ * reporting, error handling, and process-level exception guards.
1477
+ *
1478
+ * The openclaw.json / scripts/*.sh template files are bundled with this CLI
1479
+ * (see TEMPLATE_DIR) and synced from the miaoda-openclaw-template repo via
1480
+ * scripts/sync-template.sh, so no runtime download is required.
1481
+ */
1482
+ function runReset(input, taskId, resultFile) {
1483
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1484
+ const { configPath, vars, resetData } = input;
1485
+ const configDir = node_path.default.dirname(configPath);
1486
+ const srcDir = TEMPLATE_DIR;
1487
+ let currentStep = 0;
1488
+ let stepStartedAt = Date.now();
1489
+ const log = makeLogger(resetLogFile(taskId));
1490
+ log(`=== reset started, taskId=${taskId}, pid=${process.pid} ===`);
1491
+ log(`configPath=${configPath}, configDir=${configDir}, templateDir=${srcDir}`);
1492
+ if (!node_fs.default.existsSync(node_path.default.join(srcDir, "openclaw.json"))) {
1493
+ const err = `bundled template not found at ${srcDir}`;
1494
+ log(`ERROR: ${err}`);
1495
+ markFailed(resultFile, 0, err, startedAt);
1496
+ process.exit(1);
1497
+ }
1498
+ process.on("uncaughtException", (err) => {
1499
+ log(`FATAL uncaughtException: ${err.message}\n${err.stack ?? ""}`);
1500
+ markFailed(resultFile, currentStep, `uncaught exception: ${err.message}`, startedAt);
1501
+ process.exit(1);
1502
+ });
1503
+ process.on("unhandledRejection", (reason) => {
1504
+ log(`FATAL unhandledRejection: ${String(reason)}`);
1505
+ markFailed(resultFile, currentStep, `unhandled rejection: ${reason}`, startedAt);
1506
+ process.exit(1);
1507
+ });
1508
+ /** Advance to the next step, updating the progress file and logging a boundary. */
1509
+ const step = (n) => {
1510
+ if (currentStep > 0) log(`step ${currentStep} "${STEPS[currentStep - 1]}" done in ${Date.now() - stepStartedAt}ms`);
1511
+ currentStep = n;
1512
+ stepStartedAt = Date.now();
1513
+ log(`--- step ${n}/${TOTAL_STEPS}: ${STEPS[n - 1]} ---`);
1514
+ updateProgress(resultFile, n, startedAt);
1515
+ };
1516
+ try {
1517
+ step(1);
1518
+ backupCurrentConfig(configPath, log);
1519
+ step(2);
1520
+ generateDefaultConfig(srcDir, configPath, resetData.templateVars, log);
1521
+ step(3);
1522
+ killOpenclawProcesses(log);
1523
+ step(4);
1524
+ waitForInitNpm(10 * 6e4, log);
1525
+ step(5);
1526
+ ensureOpenclawBinary(srcDir, configPath, log);
1527
+ step(6);
1528
+ mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
1529
+ step(7);
1530
+ copyStartupScripts(srcDir, configDir, log);
1531
+ step(8);
1532
+ installExtensions(configDir, log);
1533
+ step(9);
1534
+ writeSecretsAndRestart(vars, resetData, configDir, log);
1535
+ log(`step 9 "${STEPS[8]}" done in ${Date.now() - stepStartedAt}ms`);
1536
+ log("=== reset completed successfully ===");
1537
+ markDone(resultFile, startedAt);
1538
+ } catch (e) {
1539
+ const err = e.message;
1540
+ log(`ERROR in step ${currentStep} "${STEPS[currentStep - 1] ?? "init"}" after ${Date.now() - stepStartedAt}ms: ${err}\n${e.stack ?? ""}`);
1541
+ markFailed(resultFile, currentStep, err, startedAt);
1542
+ process.exit(1);
1543
+ }
1544
+ }
1545
+ //#endregion
1546
+ //#region src/get-reset-task.ts
1547
+ /**
1548
+ * Long-poll for a reset task result.
1549
+ * Reads the result file every 1s for up to 30s.
1550
+ * Returns immediately on terminal states (done/failed).
1551
+ */
1552
+ function getResetTask(taskId) {
1553
+ const resultFile = resetResultFile(taskId);
1554
+ const deadline = Date.now() + 3e4;
1555
+ while (Date.now() < deadline) {
1556
+ if (!node_fs.default.existsSync(resultFile)) {
1557
+ sleepSync(1e3);
1558
+ continue;
1559
+ }
1560
+ try {
1561
+ const content = node_fs.default.readFileSync(resultFile, "utf-8");
1562
+ const result = JSON.parse(content);
1563
+ if (result.status === "done" || result.status === "failed") return result;
1564
+ } catch {}
1565
+ sleepSync(1e3);
1566
+ }
1567
+ if (node_fs.default.existsSync(resultFile)) try {
1568
+ return JSON.parse(node_fs.default.readFileSync(resultFile, "utf-8"));
1569
+ } catch {}
1570
+ return {
1571
+ status: "running",
1572
+ progress: "等待中..."
1573
+ };
1574
+ }
1575
+ /**
1576
+ * Synchronous sleep using Atomics.wait on a shared buffer.
1577
+ */
1578
+ function sleepSync(ms) {
1579
+ const buf = new SharedArrayBuffer(4);
1580
+ const arr = new Int32Array(buf);
1581
+ Atomics.wait(arr, 0, 0, ms);
1582
+ }
1583
+ //#endregion
987
1584
  //#region src/index.ts
988
1585
  const args = node_process.default.argv.slice(2);
989
1586
  const mode = args.find((a) => !a.startsWith("--"));
990
- const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
991
- if (!mode || !["check", "repair"].includes(mode)) {
992
- console.error("Usage: mclaw-diagnose <check|repair> --ctx=<base64>");
993
- node_process.default.exit(1);
994
- }
995
- if (!ctx) {
996
- console.error("Error: --ctx=<base64> is required");
997
- node_process.default.exit(1);
998
- }
999
- const input = JSON.parse(Buffer.from(ctx, "base64").toString("utf-8"));
1000
- if (mode === "check") console.log(JSON.stringify(runCheck(input)));
1001
- else console.log(JSON.stringify(runRepair(input)));
1587
+ switch (mode) {
1588
+ case "check":
1589
+ case "repair": {
1590
+ const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
1591
+ if (!ctx) {
1592
+ console.error("Error: --ctx=<base64> is required");
1593
+ node_process.default.exit(1);
1594
+ }
1595
+ const input = JSON.parse(Buffer.from(ctx, "base64").toString("utf-8"));
1596
+ if (mode === "check") console.log(JSON.stringify(runCheck(input)));
1597
+ else console.log(JSON.stringify(runRepair(input)));
1598
+ break;
1599
+ }
1600
+ case "reset":
1601
+ if (args.includes("--async")) {
1602
+ const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
1603
+ if (!ctx) {
1604
+ console.error("Error: --ctx=<base64> is required");
1605
+ node_process.default.exit(1);
1606
+ }
1607
+ console.log(JSON.stringify(startAsyncReset(ctx)));
1608
+ } else if (args.includes("--worker")) {
1609
+ const ctx = args.find((a) => a.startsWith("--ctx="))?.slice(6);
1610
+ const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
1611
+ if (!ctx || !taskId) {
1612
+ console.error("Error: --ctx=<base64> and --task-id=<id> are required for worker");
1613
+ node_process.default.exit(1);
1614
+ }
1615
+ const resultFile = resetResultFile(taskId);
1616
+ runReset(JSON.parse(Buffer.from(ctx, "base64").toString("utf-8")), taskId, resultFile);
1617
+ } else {
1618
+ console.error("Usage: reset --async --ctx=<base64> | reset --worker --task-id=<id> --ctx=<base64>");
1619
+ node_process.default.exit(1);
1620
+ }
1621
+ break;
1622
+ case "get_reset_task": {
1623
+ const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
1624
+ if (!taskId) {
1625
+ console.error("Error: --task-id=<id> is required");
1626
+ node_process.default.exit(1);
1627
+ }
1628
+ console.log(JSON.stringify(getResetTask(taskId)));
1629
+ break;
1630
+ }
1631
+ default:
1632
+ console.error("Usage: mclaw-diagnose <check|repair|reset|get_reset_task> [options]");
1633
+ node_process.default.exit(1);
1634
+ }
1002
1635
  //#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.2",
3
+ "version": "0.1.1-alpha.21",
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,539 @@
1
+ {
2
+ "meta": {
3
+ "lastTouchedVersion": "2026.4.9",
4
+ "lastTouchedAt": "2026-04-13T03:14:33.386Z"
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-coding",
452
+ "browser",
453
+ "openclaw-mem0-plugin",
454
+ "openclaw-extension-miaoda"
455
+ ],
456
+ "entries": {
457
+ "feishu": {
458
+ "enabled": false
459
+ },
460
+ "openclaw-lark": {
461
+ "enabled": true
462
+ },
463
+ "openclaw-extension-miaoda-coding": {
464
+ "enabled": true
465
+ },
466
+ "browser": {
467
+ "enabled": true
468
+ },
469
+ "openclaw-mem0-plugin": {
470
+ "enabled": false
471
+ },
472
+ "openclaw-extension-miaoda": {
473
+ "enabled": true,
474
+ "config": {
475
+ "greeting": {
476
+ "message": "👋 Hi,我是**$$__MIAODA_CLAW_NAME__**,一只生活在飞书里的 🦞 小龙虾,打个招呼,我们开始认识一下?",
477
+ "targets": [
478
+ "$$__FEISHU_OPEN_ID__"
479
+ ]
480
+ }
481
+ }
482
+ }
483
+ },
484
+ "installs": {
485
+ "openclaw-lark": {
486
+ "source": "npm",
487
+ "spec": "@lark-apaas/openclaw-lark",
488
+ "installPath": "./extensions/openclaw-lark",
489
+ "version": "2026.4.3",
490
+ "resolvedName": "@lark-apaas/openclaw-lark",
491
+ "resolvedVersion": "2026.4.3",
492
+ "resolvedSpec": "@lark-apaas/openclaw-lark@2026.4.3",
493
+ "integrity": "sha512-1eJ2WCvAYsnM9TPwPbEoD3nJtqNSOP2zOBV/3Vb9YQRg1kJJspEly5yCjiZt1wew4UhyN8Tcrp/XV78Qn747GA==",
494
+ "shasum": "e8d8cf05f8cb96cf5de4bca097c527eb9116e655",
495
+ "resolvedAt": "2026-04-03T06:27:44.706Z",
496
+ "installedAt": "2026-04-03T06:28:10.547Z"
497
+ },
498
+ "openclaw-extension-miaoda-coding": {
499
+ "source": "npm",
500
+ "spec": "@lark-apaas/openclaw-extension-miaoda-coding",
501
+ "installPath": "./extensions/openclaw-extension-miaoda-coding",
502
+ "version": "1.0.11",
503
+ "resolvedName": "@lark-apaas/openclaw-extension-miaoda-coding",
504
+ "resolvedVersion": "1.0.11",
505
+ "resolvedSpec": "@lark-apaas/openclaw-extension-miaoda-coding@1.0.11",
506
+ "integrity": "sha512-B2i3n367cR37UVJrDCTZSBljxUYzDZ4gWf3dw79XBdaZcq0G88XnkgyFmJoZijQd/HQP13xNBlfcLk4Lz2c6Bg==",
507
+ "shasum": "a6bf5efcec70bbd139ee7a75ea183e4f9751fd39",
508
+ "resolvedAt": "2026-04-13T14:40:18.926Z",
509
+ "installedAt": "2026-04-13T14:40:18.926Z"
510
+ },
511
+ "openclaw-mem0-plugin": {
512
+ "source": "npm",
513
+ "spec": "@shareclz/openclaw-mem0-plugin@1.1.2",
514
+ "installPath": "./extensions/openclaw-mem0-plugin",
515
+ "installedAt": "2026-04-13T03:13:10.564Z",
516
+ "version": "1.1.2",
517
+ "resolvedVersion": "1.1.2",
518
+ "resolvedName": "@shareclz/openclaw-mem0-plugin",
519
+ "resolvedSpec": "@shareclz/openclaw-mem0-plugin@1.1.2",
520
+ "resolvedAt": "2026-04-13T03:13:10.564Z",
521
+ "integrity": "sha512-j6m8oN6ykVc3a1/Bw0o7LmXmNApiGk6wxafrT0zW9m4UiqZe8LM4Y2LzvGEAnKwFdkUIkaSzpl/8Qp7MPy/zcQ==",
522
+ "shasum": "b1bdaa44917ce9535ba1acddb145ea0ed5d26bae"
523
+ },
524
+ "openclaw-extension-miaoda": {
525
+ "source": "npm",
526
+ "installPath": "./extensions/openclaw-extension-miaoda",
527
+ "version": "1.0.10",
528
+ "installedAt": "2026-04-14T12:21:23.090Z",
529
+ "spec": "@lark-apaas/openclaw-extension-miaoda@1.0.10",
530
+ "resolvedVersion": "1.0.10",
531
+ "resolvedName": "@lark-apaas/openclaw-extension-miaoda",
532
+ "resolvedSpec": "@lark-apaas/openclaw-extension-miaoda@1.0.10",
533
+ "resolvedAt": "2026-04-14T12:21:23.090Z",
534
+ "integrity": "sha512-aWXIBdzcjUQAdthiUjLhgGp8G0TMVB3wpAzR3wjscDRX7XAo1zhpmcWkBM0JNdrle1i3e5eB4hxVcLfn1yooNw==",
535
+ "shasum": "5ef5f6ef972d90b43091c79e8a1038a3052cc1f5"
536
+ }
537
+ }
538
+ }
539
+ }
@@ -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