@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.25 → 0.1.1-alpha.27

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.
Files changed (2) hide show
  1. package/dist/index.cjs +225 -86
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -365,6 +365,74 @@ ConfigSyntaxRule = __decorate([Rule({
365
365
  repairMode: "ai"
366
366
  })], ConfigSyntaxRule);
367
367
  //#endregion
368
+ //#region src/rules/template-vars-unreplaced.ts
369
+ /**
370
+ * Placeholder format used by miaoda-openclaw-template and Go-side templateVars,
371
+ * e.g. `$$__FEISHU_APP_ID__`. Double underscores on both sides act as a natural
372
+ * boundary so split-join replacement can't accidentally overlap between keys.
373
+ */
374
+ const PLACEHOLDER_RE = /\$\$__[A-Z0-9_]+__/g;
375
+ let TemplateVarsUnreplacedRule = class TemplateVarsUnreplacedRule extends DiagnoseRule {
376
+ validate(ctx) {
377
+ const found = /* @__PURE__ */ new Set();
378
+ collectPlaceholders(ctx.config, found);
379
+ if (found.size === 0) return { pass: true };
380
+ return {
381
+ pass: false,
382
+ message: "存在未替换的模板占位符: " + [...found].sort().join(", ")
383
+ };
384
+ }
385
+ repair(ctx) {
386
+ const map = ctx.templateVars;
387
+ if (!map || Object.keys(map).length === 0) return;
388
+ replaceInPlace(ctx.config, Object.entries(map));
389
+ }
390
+ };
391
+ TemplateVarsUnreplacedRule = __decorate([Rule({
392
+ key: "template_vars_unreplaced",
393
+ dependsOn: ["config_syntax_check"],
394
+ repairMode: "standard"
395
+ })], TemplateVarsUnreplacedRule);
396
+ function collectPlaceholders(value, found) {
397
+ if (typeof value === "string") {
398
+ const matches = value.match(PLACEHOLDER_RE);
399
+ if (matches) for (const m of matches) found.add(m);
400
+ return;
401
+ }
402
+ if (Array.isArray(value)) {
403
+ for (const v of value) collectPlaceholders(v, found);
404
+ return;
405
+ }
406
+ if (value && typeof value === "object") for (const v of Object.values(value)) collectPlaceholders(v, found);
407
+ }
408
+ function replaceInPlace(value, entries) {
409
+ if (Array.isArray(value)) {
410
+ for (let i = 0; i < value.length; i++) {
411
+ const el = value[i];
412
+ if (typeof el === "string") value[i] = applyVars(el, entries);
413
+ else replaceInPlace(el, entries);
414
+ }
415
+ return;
416
+ }
417
+ if (value && typeof value === "object") {
418
+ const obj = value;
419
+ for (const key of Object.keys(obj)) {
420
+ const v = obj[key];
421
+ if (typeof v === "string") obj[key] = applyVars(v, entries);
422
+ else replaceInPlace(v, entries);
423
+ }
424
+ }
425
+ }
426
+ /** Split-join replacement — matches the algorithm in reset.ts:120 and avoids regex-escaping `$$`. */
427
+ function applyVars(str, entries) {
428
+ let out = str;
429
+ for (const [placeholder, value] of entries) {
430
+ if (!value) continue;
431
+ if (out.includes(placeholder)) out = out.split(placeholder).join(value);
432
+ }
433
+ return out;
434
+ }
435
+ //#endregion
368
436
  //#region src/rules/model-provider.ts
369
437
  var _ModelProviderRule;
370
438
  let ModelProviderRule = class ModelProviderRule extends DiagnoseRule {
@@ -614,6 +682,27 @@ FeishuChannelRule = _FeishuChannelRule = __decorate([Rule({
614
682
  repairMode: "standard"
615
683
  })], FeishuChannelRule);
616
684
  //#endregion
685
+ //#region src/rules/feishu-default-account.ts
686
+ let FeishuDefaultAccountRule = class FeishuDefaultAccountRule extends DiagnoseRule {
687
+ validate(ctx) {
688
+ const feishu = getNestedMap(ctx.config, "channels", "feishu");
689
+ if (feishu && "defaultAccount" in feishu) return {
690
+ pass: false,
691
+ message: "channels.feishu.defaultAccount should be removed"
692
+ };
693
+ return { pass: true };
694
+ }
695
+ repair(ctx) {
696
+ const feishu = getNestedMap(ctx.config, "channels", "feishu");
697
+ if (feishu && "defaultAccount" in feishu) delete feishu.defaultAccount;
698
+ }
699
+ };
700
+ FeishuDefaultAccountRule = __decorate([Rule({
701
+ key: "feishu_default_account",
702
+ dependsOn: ["config_syntax_check"],
703
+ repairMode: "standard"
704
+ })], FeishuDefaultAccountRule);
705
+ //#endregion
617
706
  //#region src/rules/gateway.ts
618
707
  var _GatewayRule;
619
708
  let GatewayRule = class GatewayRule extends DiagnoseRule {
@@ -877,74 +966,6 @@ SecretsRule = __decorate([Rule({
877
966
  skipWhen: ({ hasMiaoda, deps }) => !hasMiaoda || !deps.usesMiaodaSecretProvider
878
967
  })], SecretsRule);
879
968
  //#endregion
880
- //#region src/rules/template-vars-unreplaced.ts
881
- /**
882
- * Placeholder format used by miaoda-openclaw-template and Go-side templateVars,
883
- * e.g. `$$__FEISHU_APP_ID__`. Double underscores on both sides act as a natural
884
- * boundary so split-join replacement can't accidentally overlap between keys.
885
- */
886
- const PLACEHOLDER_RE = /\$\$__[A-Z0-9_]+__/g;
887
- let TemplateVarsUnreplacedRule = class TemplateVarsUnreplacedRule extends DiagnoseRule {
888
- validate(ctx) {
889
- const found = /* @__PURE__ */ new Set();
890
- collectPlaceholders(ctx.config, found);
891
- if (found.size === 0) return { pass: true };
892
- return {
893
- pass: false,
894
- message: "存在未替换的模板占位符: " + [...found].sort().join(", ")
895
- };
896
- }
897
- repair(ctx) {
898
- const map = ctx.templateVars;
899
- if (!map || Object.keys(map).length === 0) return;
900
- replaceInPlace(ctx.config, Object.entries(map));
901
- }
902
- };
903
- TemplateVarsUnreplacedRule = __decorate([Rule({
904
- key: "template_vars_unreplaced",
905
- dependsOn: ["config_syntax_check"],
906
- repairMode: "standard"
907
- })], TemplateVarsUnreplacedRule);
908
- function collectPlaceholders(value, found) {
909
- if (typeof value === "string") {
910
- const matches = value.match(PLACEHOLDER_RE);
911
- if (matches) for (const m of matches) found.add(m);
912
- return;
913
- }
914
- if (Array.isArray(value)) {
915
- for (const v of value) collectPlaceholders(v, found);
916
- return;
917
- }
918
- if (value && typeof value === "object") for (const v of Object.values(value)) collectPlaceholders(v, found);
919
- }
920
- function replaceInPlace(value, entries) {
921
- if (Array.isArray(value)) {
922
- for (let i = 0; i < value.length; i++) {
923
- const el = value[i];
924
- if (typeof el === "string") value[i] = applyVars(el, entries);
925
- else replaceInPlace(el, entries);
926
- }
927
- return;
928
- }
929
- if (value && typeof value === "object") {
930
- const obj = value;
931
- for (const key of Object.keys(obj)) {
932
- const v = obj[key];
933
- if (typeof v === "string") obj[key] = applyVars(v, entries);
934
- else replaceInPlace(v, entries);
935
- }
936
- }
937
- }
938
- /** Split-join replacement — matches the algorithm in reset.ts:120 and avoids regex-escaping `$$`. */
939
- function applyVars(str, entries) {
940
- let out = str;
941
- for (const [placeholder, value] of entries) {
942
- if (!value) continue;
943
- if (out.includes(placeholder)) out = out.split(placeholder).join(value);
944
- }
945
- return out;
946
- }
947
- //#endregion
948
969
  //#region src/rules/cleanup-install-backup-dirs.ts
949
970
  const DIR_PREFIX = ".openclaw-install-";
950
971
  function resolveExtensionsDir(configPath) {
@@ -962,7 +983,7 @@ function findLeftoverDirs(extensionsDir) {
962
983
  }
963
984
  let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends DiagnoseRule {
964
985
  validate(ctx) {
965
- const configPath = ctx.config.__configPath;
986
+ const { configPath } = ctx;
966
987
  if (!configPath) return { pass: true };
967
988
  const dirs = findLeftoverDirs(resolveExtensionsDir(configPath));
968
989
  if (dirs.length === 0) return { pass: true };
@@ -972,7 +993,7 @@ let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends Di
972
993
  };
973
994
  }
974
995
  repair(ctx) {
975
- const configPath = ctx.config.__configPath;
996
+ const { configPath } = ctx;
976
997
  if (!configPath) return;
977
998
  const dirs = findLeftoverDirs(resolveExtensionsDir(configPath));
978
999
  const failures = [];
@@ -1322,6 +1343,54 @@ const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-s
1322
1343
  const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
1323
1344
  //#endregion
1324
1345
  //#region src/logger.ts
1346
+ /**
1347
+ * Shared CLI log file. Every log line the CLI emits — whether through
1348
+ * `console.error` (rules, helpers, errors) or through the per-task
1349
+ * `makeLogger` (reset worker) — is tee'd here so operators have a single
1350
+ * file to tail when diagnosing a sandbox.
1351
+ *
1352
+ * `/tmp` is ephemeral on sandbox restart; we rely on that for rotation
1353
+ * (no size-based rotation implemented).
1354
+ */
1355
+ const CLI_LOG_FILE = "/tmp/openclaw-diagnose/cli.log";
1356
+ /** Append one line to the shared cli.log. Swallows any fs error —
1357
+ * logging must never break the business flow. */
1358
+ function appendCliLog(line) {
1359
+ try {
1360
+ const dir = node_path.default.dirname(CLI_LOG_FILE);
1361
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1362
+ node_fs.default.appendFileSync(CLI_LOG_FILE, line);
1363
+ } catch {}
1364
+ }
1365
+ let stderrMirrorInstalled = false;
1366
+ /**
1367
+ * Install a process-wide `console.error` interceptor that mirrors each
1368
+ * line to BOTH the original stderr AND cli.log. Call once at CLI entry
1369
+ * before any subcommand dispatch; idempotent.
1370
+ *
1371
+ * Why console.error and not console.log: the CLI's stdout carries the
1372
+ * structured JSON result protocol consumed by sandbox_console and other
1373
+ * callers — any log line on stdout would corrupt JSON parsing. Rules,
1374
+ * helpers, and error paths therefore must route debug output through
1375
+ * console.error (stderr).
1376
+ */
1377
+ function installStderrMirror() {
1378
+ if (stderrMirrorInstalled) return;
1379
+ stderrMirrorInstalled = true;
1380
+ const original = console.error.bind(console);
1381
+ console.error = (...args) => {
1382
+ original(...args);
1383
+ const body = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
1384
+ appendCliLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${body}\n`);
1385
+ };
1386
+ }
1387
+ function safeStringify(v) {
1388
+ try {
1389
+ return JSON.stringify(v);
1390
+ } catch {
1391
+ return String(v);
1392
+ }
1393
+ }
1325
1394
  function makeLogger(logFile) {
1326
1395
  try {
1327
1396
  const dir = node_path.default.dirname(logFile);
@@ -1332,9 +1401,70 @@ function makeLogger(logFile) {
1332
1401
  try {
1333
1402
  node_fs.default.appendFileSync(logFile, line);
1334
1403
  } catch {}
1404
+ appendCliLog(line);
1335
1405
  };
1336
1406
  }
1337
1407
  //#endregion
1408
+ //#region src/fs-utils.ts
1409
+ /**
1410
+ * Rename src → dst, falling back to `mv` (which handles cross-device copy)
1411
+ * when the kernel returns EXDEV.
1412
+ *
1413
+ * Sandbox filesystems can put sibling paths on different "devices" from
1414
+ * rename(2)'s point of view: bind mounts, overlayfs copy-up, and
1415
+ * mount-point children inside a single directory all trip EXDEV. Seen in
1416
+ * production when reset's atomic swap did
1417
+ * /home/gem/.npm-global/lib/node_modules/openclaw → openclaw.bak
1418
+ * and the openclaw subdir was a bind-mounted volume.
1419
+ *
1420
+ * Behavior:
1421
+ * - Happy path hits rename(2) — atomic, single syscall, microseconds.
1422
+ * - EXDEV path shells out to `mv`, which does rename() then copy+unlink
1423
+ * on failure. Non-atomic but correct; callers already have rollback
1424
+ * logic (install-openclaw restores from .bak) so loss of atomicity
1425
+ * only matters if the process dies mid-copy, and that's survivable.
1426
+ * - Any other error (ENOENT, EACCES, EBUSY...) rethrows as-is so callers
1427
+ * see the real problem instead of a misleading `mv` fallback failure.
1428
+ */
1429
+ function moveSafe(src, dst) {
1430
+ try {
1431
+ node_fs.default.renameSync(src, dst);
1432
+ } catch (e) {
1433
+ if (e?.code !== "EXDEV") throw e;
1434
+ execCaptureErr(`mv ${shellQuote(src)} ${shellQuote(dst)}`);
1435
+ }
1436
+ }
1437
+ /**
1438
+ * Run a shell command, re-throwing with stderr attached on failure.
1439
+ *
1440
+ * Node's `execSync(..., { stdio: 'ignore' })` swallows stderr entirely —
1441
+ * callers only see "Command failed: <cmd>" with no hint of the real error
1442
+ * (ENOSPC, EROFS, "unrecognized option", etc.). Production debugging on
1443
+ * sandboxed boxes is painful without the underlying message, so we pipe
1444
+ * stderr, capture it, and embed it in the thrown Error. stdout stays
1445
+ * suppressed because the commands we run here (tar/mv) are silent on
1446
+ * success.
1447
+ */
1448
+ function execCaptureErr(cmd) {
1449
+ try {
1450
+ (0, node_child_process.execSync)(cmd, { stdio: [
1451
+ "ignore",
1452
+ "ignore",
1453
+ "pipe"
1454
+ ] });
1455
+ } catch (e) {
1456
+ const stderr = e?.stderr;
1457
+ const stderrStr = (typeof stderr === "string" ? stderr : stderr?.toString("utf8") ?? "").trim();
1458
+ const base = e?.message ?? "command failed";
1459
+ throw new Error(stderrStr ? `${base}\nstderr: ${stderrStr}` : base);
1460
+ }
1461
+ }
1462
+ /** POSIX single-quote shell escape. Paths with embedded quotes are rare but
1463
+ * the token-file path conventions in sandboxes don't guarantee cleanliness. */
1464
+ function shellQuote(s) {
1465
+ return `'${s.replace(/'/g, `'\\''`)}'`;
1466
+ }
1467
+ //#endregion
1338
1468
  //#region src/reset-async.ts
1339
1469
  /**
1340
1470
  * Start an async reset task: spawn a detached child process and return the taskId.
@@ -1357,7 +1487,7 @@ function startAsyncReset(ctxBase64) {
1357
1487
  const dir = node_path.default.dirname(resultFile);
1358
1488
  if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1359
1489
  node_fs.default.writeFileSync(tmpPath, JSON.stringify(initial), "utf-8");
1360
- node_fs.default.renameSync(tmpPath, resultFile);
1490
+ moveSafe(tmpPath, resultFile);
1361
1491
  const child = (0, node_child_process.spawn)(process.execPath, [
1362
1492
  process.argv[1],
1363
1493
  "reset",
@@ -1381,7 +1511,7 @@ function startAsyncReset(ctxBase64) {
1381
1511
  };
1382
1512
  const errTmpPath = resultFile + ".tmp";
1383
1513
  node_fs.default.writeFileSync(errTmpPath, JSON.stringify(failResult));
1384
- node_fs.default.renameSync(errTmpPath, resultFile);
1514
+ moveSafe(errTmpPath, resultFile);
1385
1515
  });
1386
1516
  child.unref();
1387
1517
  log(`spawned worker pid=${child.pid}`);
@@ -1389,10 +1519,16 @@ function startAsyncReset(ctxBase64) {
1389
1519
  }
1390
1520
  //#endregion
1391
1521
  //#region src/oss/fetchManifest.ts
1522
+ const MANIFEST_PREFIX = "builtin/manifests/openclaw/recommended/";
1523
+ const MANIFEST_SUFFIX = ".json";
1392
1524
  async function fetchManifest(ossFileMap, tag) {
1393
- const key = `builtin/manifests/openclaw/recommended/${tag}.json`;
1525
+ const key = `${MANIFEST_PREFIX}${tag}${MANIFEST_SUFFIX}`;
1394
1526
  const url = ossFileMap[key];
1395
- if (!url) throw new Error(`manifest signed URL missing for ${key}`);
1527
+ if (!url) {
1528
+ const available = Object.keys(ossFileMap).filter((k) => k.startsWith(MANIFEST_PREFIX) && k.endsWith(MANIFEST_SUFFIX)).map((k) => k.slice(39, -5));
1529
+ const availStr = available.length ? available.join(", ") : "(none)";
1530
+ throw new Error(`manifest signed URL missing for tag "${tag}" (key ${key}). Available tags in ossFileMap: ${availStr}. Either pass an available tag or update the studio_server TCC openclaw_upgrade_config supported_versions.`);
1531
+ }
1396
1532
  const res = await fetch(url);
1397
1533
  if (!res.ok) throw new Error(`fetch manifest failed: HTTP ${res.status} ${res.statusText}`);
1398
1534
  return await res.json();
@@ -1430,7 +1566,7 @@ async function downloadWithCache(pkg, ossFileMap, opts = {}) {
1430
1566
  console.error(`⚠ [downloadWithCache] INTEGRITY BYPASS for ${pkg.ossKey}: expected ${expected.slice(0, 12)}… got ${actual.slice(0, 12)}… — ${sourceLabel}. DO NOT use this flag in production.`);
1431
1567
  } else throw new Error(`integrity mismatch for ${pkg.ossKey}: expected ${expected} got ${actual}`);
1432
1568
  }
1433
- node_fs.default.renameSync(tmpFile, destFile);
1569
+ moveSafe(tmpFile, destFile);
1434
1570
  return destFile;
1435
1571
  } catch (e) {
1436
1572
  try {
@@ -1453,10 +1589,10 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
1453
1589
  force: true
1454
1590
  });
1455
1591
  const hadExisting = node_fs.default.existsSync(targetDir);
1456
- if (hadExisting) node_fs.default.renameSync(targetDir, bakDir);
1592
+ if (hadExisting) moveSafe(targetDir, bakDir);
1457
1593
  try {
1458
1594
  node_fs.default.mkdirSync(targetDir, { recursive: true });
1459
- (0, node_child_process.execSync)(`tar -xzf '${tarball}' -C '${targetDir}' --strip-components=1`, { stdio: "ignore" });
1595
+ execCaptureErr(`tar -xzf '${tarball}' -C '${targetDir}' --strip-components=1`);
1460
1596
  if (!node_fs.default.existsSync(node_path.default.join(targetDir, "package.json"))) throw new Error("extracted tarball missing package.json");
1461
1597
  } catch (e) {
1462
1598
  try {
@@ -1465,7 +1601,7 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
1465
1601
  force: true
1466
1602
  });
1467
1603
  } catch {}
1468
- if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.renameSync(bakDir, targetDir);
1604
+ if (hadExisting && node_fs.default.existsSync(bakDir)) moveSafe(bakDir, targetDir);
1469
1605
  throw e;
1470
1606
  }
1471
1607
  if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
@@ -1540,7 +1676,7 @@ function updatePluginInstalls(configPath, installedPkgs) {
1540
1676
  } else skipped++;
1541
1677
  const tmpPath = configPath + ".installs-tmp";
1542
1678
  node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
1543
- node_fs.default.renameSync(tmpPath, configPath);
1679
+ moveSafe(tmpPath, configPath);
1544
1680
  console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
1545
1681
  }
1546
1682
  function installOne(pkg, tarball, homeBase) {
@@ -1554,7 +1690,7 @@ function installOne(pkg, tarball, homeBase) {
1554
1690
  });
1555
1691
  node_fs.default.mkdirSync(stagingDir);
1556
1692
  try {
1557
- (0, node_child_process.execSync)(`tar -xzf '${tarball}' -C '${stagingDir}' --strip-components=1`, { stdio: "ignore" });
1693
+ execCaptureErr(`tar -xzf '${tarball}' -C '${stagingDir}' --strip-components=1`);
1558
1694
  if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
1559
1695
  } catch (e) {
1560
1696
  try {
@@ -1566,8 +1702,8 @@ function installOne(pkg, tarball, homeBase) {
1566
1702
  throw e;
1567
1703
  }
1568
1704
  const hadOld = node_fs.default.existsSync(destDir);
1569
- if (hadOld) node_fs.default.renameSync(destDir, oldDir);
1570
- node_fs.default.renameSync(stagingDir, destDir);
1705
+ if (hadOld) moveSafe(destDir, oldDir);
1706
+ moveSafe(stagingDir, destDir);
1571
1707
  if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
1572
1708
  recursive: true,
1573
1709
  force: true
@@ -1592,7 +1728,7 @@ async function downloadResource(tag, ossFileMap, opts) {
1592
1728
  const format = (pkg.format ?? "").toLowerCase();
1593
1729
  const lower = pkg.ossKey.toLowerCase();
1594
1730
  if (format === "tgz" || lower.endsWith(".tgz") || lower.endsWith(".tar.gz")) {
1595
- (0, node_child_process.execSync)(`tar -xzf '${file}' -C '${extractDir}'`, { stdio: "ignore" });
1731
+ execCaptureErr(`tar -xzf '${file}' -C '${extractDir}'`);
1596
1732
  console.error(`[download-resource] ${opts.role}/${opts.name}: extracted to ${extractDir}`);
1597
1733
  } else {
1598
1734
  const basename = node_path.default.posix.basename(pkg.ossKey);
@@ -1634,7 +1770,7 @@ function writeResultFile(resultFile, result) {
1634
1770
  if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
1635
1771
  const tmpPath = resultFile + ".tmp";
1636
1772
  node_fs.default.writeFileSync(tmpPath, JSON.stringify(result), "utf-8");
1637
- node_fs.default.renameSync(tmpPath, resultFile);
1773
+ moveSafe(tmpPath, resultFile);
1638
1774
  }
1639
1775
  function updateProgress(resultFile, step, startedAt) {
1640
1776
  writeResultFile(resultFile, {
@@ -1946,7 +2082,8 @@ async function runReset(input, taskId, resultFile) {
1946
2082
  process.exit(1);
1947
2083
  }
1948
2084
  let openclawTag;
1949
- try {
2085
+ if (resetData.openclawTag) openclawTag = resetData.openclawTag;
2086
+ else try {
1950
2087
  openclawTag = getOpenclawTagFromOssFileMap(ossFileMap);
1951
2088
  } catch (e) {
1952
2089
  const err = e.message;
@@ -2340,7 +2477,8 @@ function buildResetInput(raw, configPathOverride) {
2340
2477
  secretsContent: ctx.secrets.secretsContent,
2341
2478
  providerKeyContent: ctx.secrets.providerKeyContent,
2342
2479
  coreBackup: ctx.reset.coreBackup,
2343
- ossFileMap: ctx.install.ossFileMap
2480
+ ossFileMap: ctx.install.ossFileMap,
2481
+ openclawTag: ctx.install.openclawTag
2344
2482
  }
2345
2483
  };
2346
2484
  }
@@ -2400,6 +2538,7 @@ function getMultiFlag(args, name) {
2400
2538
  return args.filter((a) => a.startsWith(prefix)).map((a) => a.slice(prefix.length));
2401
2539
  }
2402
2540
  async function main() {
2541
+ installStderrMirror();
2403
2542
  switch (mode) {
2404
2543
  case "check":
2405
2544
  case "repair": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.1-alpha.25",
3
+ "version": "0.1.1-alpha.27",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {