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

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 +297 -25
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -32,6 +32,8 @@ node_path = __toESM(node_path);
32
32
  let node_child_process = require("node:child_process");
33
33
  let node_crypto = require("node:crypto");
34
34
  node_crypto = __toESM(node_crypto);
35
+ let node_os = require("node:os");
36
+ node_os = __toESM(node_os);
35
37
  let node_stream = require("node:stream");
36
38
  let node_stream_promises = require("node:stream/promises");
37
39
  let node_assert = require("node:assert");
@@ -684,18 +686,10 @@ FeishuChannelRule = _FeishuChannelRule = __decorate([Rule({
684
686
  //#endregion
685
687
  //#region src/rules/feishu-default-account.ts
686
688
  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
- };
689
+ validate(_ctx) {
693
690
  return { pass: true };
694
691
  }
695
- repair(ctx) {
696
- const feishu = getNestedMap(ctx.config, "channels", "feishu");
697
- if (feishu && "defaultAccount" in feishu) delete feishu.defaultAccount;
698
- }
692
+ repair(_ctx) {}
699
693
  };
700
694
  FeishuDefaultAccountRule = __decorate([Rule({
701
695
  key: "feishu_default_account",
@@ -1582,32 +1576,51 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
1582
1576
  if (!pkg) throw new Error("install-openclaw: role=cli,name=openclaw not found in manifest");
1583
1577
  const targetDir = opts.targetDir ?? node_path.default.join(homeBase, pkg.installPath);
1584
1578
  const bakDir = targetDir + ".bak";
1579
+ const newDir = targetDir + ".new";
1585
1580
  const tarball = await downloadWithCache(pkg, ossFileMap, opts);
1586
1581
  console.error(`[install-openclaw] tag=${openclawTag} shasum=${pkg.shasum.slice(0, 12)}...`);
1582
+ if (node_fs.default.existsSync(newDir)) node_fs.default.rmSync(newDir, {
1583
+ recursive: true,
1584
+ force: true
1585
+ });
1587
1586
  if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
1588
1587
  recursive: true,
1589
1588
  force: true
1590
1589
  });
1591
- const hadExisting = node_fs.default.existsSync(targetDir);
1592
- if (hadExisting) moveSafe(targetDir, bakDir);
1590
+ node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
1591
+ const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(opts.tmpRoot ?? node_os.default.tmpdir(), "openclaw-install-"));
1593
1592
  try {
1594
- node_fs.default.mkdirSync(targetDir, { recursive: true });
1595
- execCaptureErr(`tar -xzf '${tarball}' -C '${targetDir}' --strip-components=1`);
1596
- if (!node_fs.default.existsSync(node_path.default.join(targetDir, "package.json"))) throw new Error("extracted tarball missing package.json");
1597
- } catch (e) {
1593
+ execCaptureErr(`tar -xzf '${tarball}' -C '${tmpStage}' --strip-components=1`);
1594
+ if (!node_fs.default.existsSync(node_path.default.join(tmpStage, "package.json"))) throw new Error("extracted tarball missing package.json");
1595
+ moveSafe(tmpStage, newDir);
1596
+ const hadExisting = node_fs.default.existsSync(targetDir);
1598
1597
  try {
1599
- node_fs.default.rmSync(targetDir, {
1598
+ if (hadExisting) moveSafe(targetDir, bakDir);
1599
+ moveSafe(newDir, targetDir);
1600
+ } catch (e) {
1601
+ if (hadExisting && !node_fs.default.existsSync(targetDir) && node_fs.default.existsSync(bakDir)) try {
1602
+ moveSafe(bakDir, targetDir);
1603
+ } catch {}
1604
+ try {
1605
+ node_fs.default.rmSync(newDir, {
1606
+ recursive: true,
1607
+ force: true
1608
+ });
1609
+ } catch {}
1610
+ throw e;
1611
+ }
1612
+ if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
1613
+ recursive: true,
1614
+ force: true
1615
+ });
1616
+ } finally {
1617
+ if (node_fs.default.existsSync(tmpStage)) try {
1618
+ node_fs.default.rmSync(tmpStage, {
1600
1619
  recursive: true,
1601
1620
  force: true
1602
1621
  });
1603
1622
  } catch {}
1604
- if (hadExisting && node_fs.default.existsSync(bakDir)) moveSafe(bakDir, targetDir);
1605
- throw e;
1606
1623
  }
1607
- if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
1608
- recursive: true,
1609
- force: true
1610
- });
1611
1624
  console.error(`[install-openclaw] done in ${Date.now() - t0}ms`);
1612
1625
  }
1613
1626
  async function installExtension(tag, ossFileMap, opts = {}) {
@@ -2504,9 +2517,248 @@ async function runDoctor(rawCtx, opts) {
2504
2517
  };
2505
2518
  }
2506
2519
  //#endregion
2520
+ //#region src/help.ts
2521
+ const BIN = "mclaw-diagnose";
2522
+ function versionBanner() {
2523
+ return `v0.1.1-alpha.29`;
2524
+ }
2525
+ const COMMANDS = [
2526
+ {
2527
+ name: "doctor",
2528
+ hidden: false,
2529
+ summary: "Diagnose openclaw config; apply repairs with --fix",
2530
+ help: `USAGE
2531
+ ${BIN} doctor [--fix] [--rule=<key>]...
2532
+
2533
+ DESCRIPTION
2534
+ Fetches DoctorCtx via innerapi, then runs one of three modes depending
2535
+ on the flags. Output is a single JSON object on stdout.
2536
+
2537
+ MODES
2538
+ (no flags) Check-only. Runs the rule engine against the
2539
+ sandbox's current openclaw config and returns
2540
+ { failedRules: { standard, ai, reset } }
2541
+ No files are mutated. Use this when you just
2542
+ want to know what's wrong.
2543
+
2544
+ --fix Check + repair-all. First runs the rule engine,
2545
+ then repairs every failing standard-mode rule.
2546
+ Returns
2547
+ { check: {...}, repair: {...} }
2548
+ Use this as the default "fix everything" action.
2549
+
2550
+ --fix --rule=<key>... Targeted repair. Skips the check pass entirely
2551
+ and runs repair against the listed rule keys
2552
+ only. Unknown keys are silently ignored.
2553
+ Returns { repair: {...} } with only those
2554
+ rules' outcomes. Use this when you already
2555
+ know which rules need fixing.
2556
+
2557
+ OPTIONS
2558
+ --fix Enable repair. See MODES above.
2559
+ --rule=<key> Repair only this rule key. Repeatable. Only
2560
+ meaningful together with --fix.
2561
+
2562
+ EXAMPLES
2563
+ ${BIN} doctor # check only
2564
+ ${BIN} doctor --fix # check then repair all
2565
+ ${BIN} doctor --fix --rule=gateway # repair 'gateway' only
2566
+ ${BIN} doctor --fix --rule=gateway --rule=jwt_token # repair multiple
2567
+
2568
+ EXIT CODES
2569
+ 0 success
2570
+ 1 generic error
2571
+ 77 innerapi authentication failed (sandbox JWT expired/invalid)
2572
+ `
2573
+ },
2574
+ {
2575
+ name: "check",
2576
+ hidden: true,
2577
+ summary: "Run rule-engine check only",
2578
+ help: `USAGE
2579
+ ${BIN} check [--ctx=<base64>]
2580
+
2581
+ DESCRIPTION
2582
+ Runs the rule engine against the sandbox's current openclaw config and
2583
+ returns { failedRules }. Used by sandbox_console's push-style callers
2584
+ that already own the ctx — end-users should prefer \`doctor\`.
2585
+
2586
+ OPTIONS
2587
+ --ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
2588
+ innerapi (same path as doctor).
2589
+ `
2590
+ },
2591
+ {
2592
+ name: "repair",
2593
+ hidden: true,
2594
+ summary: "Apply standard-mode repairs",
2595
+ help: `USAGE
2596
+ ${BIN} repair [--ctx=<base64>]
2597
+
2598
+ DESCRIPTION
2599
+ Runs repair for the failing rules listed inside the ctx's repairData.
2600
+ Intended for sandbox_console's push path — end-users should use
2601
+ \`doctor --fix\` instead.
2602
+
2603
+ OPTIONS
2604
+ --ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
2605
+ innerapi.
2606
+ `
2607
+ },
2608
+ {
2609
+ name: "reset",
2610
+ hidden: true,
2611
+ summary: "Re-initialize sandbox via the 9-step reset pipeline",
2612
+ help: `USAGE
2613
+ ${BIN} reset --async [--ctx=<base64>]
2614
+ ${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
2615
+
2616
+ DESCRIPTION
2617
+ Two-phase pipeline driven asynchronously: the --async invocation spawns
2618
+ a detached worker and returns { taskId } immediately; the --worker
2619
+ invocation (spawned by --async) runs the actual 9 steps and writes
2620
+ progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
2621
+
2622
+ Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
2623
+
2624
+ OPTIONS
2625
+ --async Start a detached worker and return taskId on stdout.
2626
+ --worker Internal — run the 9-step pipeline (launched by --async).
2627
+ --task-id=<id> Required with --worker; identifies the progress file.
2628
+ --ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
2629
+ `
2630
+ },
2631
+ {
2632
+ name: "get_reset_task",
2633
+ hidden: true,
2634
+ summary: "Poll progress of an async reset task",
2635
+ help: `USAGE
2636
+ ${BIN} get_reset_task --task-id=<id>
2637
+
2638
+ DESCRIPTION
2639
+ Reads /tmp/openclaw-diagnose/reset-<taskId>.json and prints its content
2640
+ as JSON on stdout. Safe to call repeatedly while reset is in progress.
2641
+
2642
+ OPTIONS
2643
+ --task-id=<id> Required. Matches the id returned by \`reset --async\`.
2644
+ `
2645
+ },
2646
+ {
2647
+ name: "install-openclaw",
2648
+ hidden: true,
2649
+ summary: "Download + install the openclaw tarball",
2650
+ help: `USAGE
2651
+ ${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
2652
+
2653
+ DESCRIPTION
2654
+ Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
2655
+ ctx's install.ossFileMap, extracts it into a tmpfs staging dir, and
2656
+ atomically swaps it into /home/gem/.npm-global/lib/node_modules/openclaw.
2657
+ Used by step 5 of reset.
2658
+
2659
+ ARGUMENTS
2660
+ <tag> Openclaw version tag, e.g. 2026.4.11.
2661
+
2662
+ OPTIONS
2663
+ --ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
2664
+ --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
2665
+ entirely. Wins over --ctx when both provided.
2666
+ `
2667
+ },
2668
+ {
2669
+ name: "install-extension",
2670
+ hidden: true,
2671
+ summary: "Install openclaw extension package(s)",
2672
+ help: `USAGE
2673
+ ${BIN} install-extension <tag> (--all | --extension=<name>...) [options]
2674
+
2675
+ DESCRIPTION
2676
+ Downloads + installs one or more openclaw extension tarballs
2677
+ (feishu, miaoda, etc.) into <home_base>/workspace/agent/extensions/,
2678
+ then splices installMetadata into openclaw.json's plugins.installs
2679
+ unless --skip-config-update is passed.
2680
+
2681
+ ARGUMENTS
2682
+ <tag> Openclaw version tag; extension versions resolved
2683
+ against the matching manifest.
2684
+
2685
+ OPTIONS
2686
+ --all Install every extension in the manifest.
2687
+ --extension=<name> Install a specific extension (repeatable).
2688
+ --home_base=<dir> Override the /home/gem base (tests).
2689
+ --config_path=<p> Override the openclaw.json path (tests).
2690
+ --skip-config-update Leave plugins.installs in openclaw.json untouched.
2691
+ --ctx=<base64> Opaque ctx; see install-openclaw for semantics.
2692
+ --oss_file_map=... Pre-built OSS URL map (base64 JSON).
2693
+ `
2694
+ },
2695
+ {
2696
+ name: "download-resource",
2697
+ hidden: true,
2698
+ summary: "Download + extract a single OSS resource",
2699
+ help: `USAGE
2700
+ ${BIN} download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [options]
2701
+
2702
+ DESCRIPTION
2703
+ Downloads one resource (template, config asset, etc.) identified by
2704
+ (role, name) from the manifest and extracts/copies it to <dir>.
2705
+
2706
+ ARGUMENTS
2707
+ <tag> Openclaw version tag.
2708
+
2709
+ OPTIONS
2710
+ --role=<role> Package role (e.g. template, config).
2711
+ --name=<name> Package name within the role.
2712
+ --dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
2713
+ --ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
2714
+ --oss_file_map=... Pre-built OSS URL map (base64 JSON).
2715
+ `
2716
+ }
2717
+ ];
2718
+ function parseHelpFlags(args) {
2719
+ return {
2720
+ help: args.includes("--help") || args.includes("-h"),
2721
+ expert: args.includes("-x") || args.includes("--expert")
2722
+ };
2723
+ }
2724
+ /**
2725
+ * Render the top-level help to the given stream. When `expert` is true,
2726
+ * hidden commands are listed alongside the user-facing ones.
2727
+ */
2728
+ function formatTopLevelHelp(expert) {
2729
+ const visible = COMMANDS.filter((c) => !c.hidden);
2730
+ const hidden = COMMANDS.filter((c) => c.hidden);
2731
+ const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
2732
+ const w = Math.max(...COMMANDS.map((c) => c.name.length)) + 2;
2733
+ const lines = [];
2734
+ lines.push(`${BIN} — OpenClaw config diagnose / repair CLI`);
2735
+ lines.push(versionBanner());
2736
+ lines.push("");
2737
+ lines.push("USAGE");
2738
+ lines.push(` ${BIN} <command> [options]`);
2739
+ lines.push(` ${BIN} <command> --help per-command help`);
2740
+ lines.push(` ${BIN} --help this message`);
2741
+ lines.push("");
2742
+ lines.push("COMMANDS");
2743
+ for (const c of visible) lines.push(` ${pad(c.name, w)}${c.summary}`);
2744
+ if (expert && hidden.length > 0) {
2745
+ lines.push("");
2746
+ lines.push("INTERNAL COMMANDS (revealed by -x)");
2747
+ for (const c of hidden) lines.push(` ${pad(c.name, w)}${c.summary}`);
2748
+ }
2749
+ lines.push("");
2750
+ return lines.join("\n");
2751
+ }
2752
+ /** Render per-command help. Returns undefined when the name is unknown. */
2753
+ function formatCommandHelp(name) {
2754
+ const cmd = COMMANDS.find((c) => c.name === name);
2755
+ if (!cmd) return void 0;
2756
+ return cmd.help;
2757
+ }
2758
+ //#endregion
2507
2759
  //#region src/index.ts
2508
2760
  const args = node_process.default.argv.slice(2);
2509
- const mode = args.find((a) => !a.startsWith("--"));
2761
+ const mode = args.find((a) => !a.startsWith("-"));
2510
2762
  /**
2511
2763
  * Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
2512
2764
  * the flag isn't present — the caller decides whether to fall back to the
@@ -2539,6 +2791,25 @@ function getMultiFlag(args, name) {
2539
2791
  }
2540
2792
  async function main() {
2541
2793
  installStderrMirror();
2794
+ const helpFlags = parseHelpFlags(args);
2795
+ if (mode && helpFlags.help) {
2796
+ const body = formatCommandHelp(mode);
2797
+ if (body) {
2798
+ node_process.default.stdout.write(body);
2799
+ return;
2800
+ }
2801
+ node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
2802
+ node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
2803
+ node_process.default.exit(1);
2804
+ }
2805
+ if (!mode) {
2806
+ if (helpFlags.help) {
2807
+ node_process.default.stdout.write(formatTopLevelHelp(helpFlags.expert));
2808
+ return;
2809
+ }
2810
+ node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
2811
+ node_process.default.exit(1);
2812
+ }
2542
2813
  switch (mode) {
2543
2814
  case "check":
2544
2815
  case "repair": {
@@ -2660,7 +2931,8 @@ async function main() {
2660
2931
  break;
2661
2932
  }
2662
2933
  default:
2663
- console.error("Usage: mclaw-diagnose <check|repair|doctor|reset|get_reset_task|install-openclaw|install-extension|download-resource> [options]");
2934
+ node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
2935
+ node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
2664
2936
  node_process.default.exit(1);
2665
2937
  }
2666
2938
  }
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.27",
3
+ "version": "0.1.1-alpha.29",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {