@lark-apaas/openclaw-scripts-diagnose-cli 0.1.20 → 0.1.21-alpha.0

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 +91 -39
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -52,7 +52,7 @@ node_assert = __toESM(node_assert);
52
52
  * it terse and parseable.
53
53
  */
54
54
  function getVersion() {
55
- return "0.1.20";
55
+ return "0.1.21-alpha.0";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/rule-engine/base.ts
@@ -2679,6 +2679,54 @@ const WORKSPACE_AGENT_REL = "workspace/agent";
2679
2679
  /** openclaw.json 默认相对路径。 */
2680
2680
  const DEFAULT_CONFIG_REL = `${WORKSPACE_AGENT_REL}/openclaw.json`;
2681
2681
  //#endregion
2682
+ //#region src/plugin-installs-store.ts
2683
+ function getExternalInstallsPath(configPath) {
2684
+ return node_path.default.join(node_path.default.dirname(configPath), "plugins", "installs.json");
2685
+ }
2686
+ function readExternalInstallRecords(filePath) {
2687
+ const raw = node_fs.default.readFileSync(filePath, "utf-8");
2688
+ const records = asRecord(asRecord(JSON.parse(raw))?.installRecords);
2689
+ if (!records) throw new Error(`plugins installs file must contain object installRecords: ${filePath}`);
2690
+ return records;
2691
+ }
2692
+ function readExternalPayload(filePath) {
2693
+ const raw = node_fs.default.readFileSync(filePath, "utf-8");
2694
+ const payload = JSON.parse(raw);
2695
+ if (!asRecord(payload)) throw new Error(`plugins installs file must be an object: ${filePath}`);
2696
+ return payload;
2697
+ }
2698
+ function cloneRecords(records) {
2699
+ return JSON.parse(JSON.stringify(records));
2700
+ }
2701
+ function getPluginInstalls(configPath, config) {
2702
+ const externalPath = getExternalInstallsPath(configPath);
2703
+ if (node_fs.default.existsSync(externalPath)) return {
2704
+ records: readExternalInstallRecords(externalPath),
2705
+ externalPath,
2706
+ hasExternalFile: true
2707
+ };
2708
+ return {
2709
+ records: asRecord(asRecord(config.plugins)?.installs) ?? {},
2710
+ externalPath,
2711
+ hasExternalFile: false
2712
+ };
2713
+ }
2714
+ function getPluginInstallsMap(configPath, config) {
2715
+ return getPluginInstalls(configPath, config).records;
2716
+ }
2717
+ function setPluginInstallsMap(configPath, config, records) {
2718
+ const externalPath = getExternalInstallsPath(configPath);
2719
+ const nextRecords = cloneRecords(records);
2720
+ const plugins = ensureRecord(config, "plugins");
2721
+ plugins.installs = cloneRecords(nextRecords);
2722
+ if (!node_fs.default.existsSync(externalPath)) return;
2723
+ const payload = readExternalPayload(externalPath);
2724
+ payload.installRecords = nextRecords;
2725
+ const tmpPath = `${externalPath}.tmp`;
2726
+ node_fs.default.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
2727
+ node_fs.default.renameSync(tmpPath, externalPath);
2728
+ }
2729
+ //#endregion
2682
2730
  //#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
2683
2731
  /**
2684
2732
  * Official miaoda-side plugins that must track manifest — version-locked specs
@@ -2695,9 +2743,7 @@ function unlockSpec(spec) {
2695
2743
  return spec.slice(0, cut);
2696
2744
  }
2697
2745
  /** Yield `[key, lockedSpec]` for every official-plugin install whose `spec` is locked. */
2698
- function* iterLockedOfficialInstalls(config) {
2699
- const installs = getNestedMap(config, "plugins", "installs");
2700
- if (!installs) return;
2746
+ function* iterLockedOfficialInstalls(installs) {
2701
2747
  for (const [key, entry] of Object.entries(installs)) {
2702
2748
  if (!OFFICIAL_EXTENSION_PLUGIN_NAMES.has(key)) continue;
2703
2749
  const spec = asRecord(entry)?.spec;
@@ -2706,7 +2752,7 @@ function* iterLockedOfficialInstalls(config) {
2706
2752
  }
2707
2753
  let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInstallSpecUnlockRule extends DiagnoseRule {
2708
2754
  validate(ctx) {
2709
- const locked = [...iterLockedOfficialInstalls(ctx.config)].map(([k]) => k);
2755
+ const locked = [...iterLockedOfficialInstalls(getPluginInstallsMap(ctx.configPath, ctx.config))].map(([k]) => k);
2710
2756
  if (locked.length === 0) return { pass: true };
2711
2757
  return {
2712
2758
  pass: false,
@@ -2714,12 +2760,16 @@ let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInst
2714
2760
  };
2715
2761
  }
2716
2762
  repair(ctx) {
2717
- for (const [key, spec] of iterLockedOfficialInstalls(ctx.config)) setNestedValue(ctx.config, [
2718
- "plugins",
2719
- "installs",
2720
- key,
2721
- "spec"
2722
- ], unlockSpec(spec));
2763
+ const installs = { ...getPluginInstallsMap(ctx.configPath, ctx.config) };
2764
+ for (const [key, spec] of iterLockedOfficialInstalls(installs)) {
2765
+ const entry = asRecord(installs[key]);
2766
+ if (!entry) continue;
2767
+ installs[key] = {
2768
+ ...entry,
2769
+ spec: unlockSpec(spec)
2770
+ };
2771
+ }
2772
+ setPluginInstallsMap(ctx.configPath, ctx.config, installs);
2723
2773
  }
2724
2774
  };
2725
2775
  MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
@@ -2837,11 +2887,11 @@ const OLD_PLUGIN_NAMES = Object.freeze([
2837
2887
  "feishu-greeting",
2838
2888
  "miaoda-keepalive"
2839
2889
  ]);
2840
- function getPluginMaps(config) {
2890
+ function getPluginMaps(configPath, config) {
2841
2891
  const rawAllow = asRecord(config.plugins)?.allow;
2842
2892
  return {
2843
2893
  entries: getNestedMap(config, "plugins", "entries"),
2844
- installs: getNestedMap(config, "plugins", "installs"),
2894
+ installs: getPluginInstallsMap(configPath, config),
2845
2895
  allow: Array.isArray(rawAllow) ? rawAllow : void 0
2846
2896
  };
2847
2897
  }
@@ -2853,7 +2903,7 @@ function findResiduals({ entries, installs, allow }, extensionsDir) {
2853
2903
  }
2854
2904
  let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends DiagnoseRule {
2855
2905
  validate(ctx) {
2856
- const maps = getPluginMaps(ctx.config);
2906
+ const maps = getPluginMaps(ctx.configPath, ctx.config);
2857
2907
  if (!hasNewMiaoda(maps)) return { pass: true };
2858
2908
  const residuals = findResiduals(maps, getExtensionsDir(ctx.configPath));
2859
2909
  if (residuals.length === 0) return { pass: true };
@@ -2863,7 +2913,7 @@ let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends Diag
2863
2913
  };
2864
2914
  }
2865
2915
  repair(ctx) {
2866
- const maps = getPluginMaps(ctx.config);
2916
+ const maps = getPluginMaps(ctx.configPath, ctx.config);
2867
2917
  if (!hasNewMiaoda(maps)) return;
2868
2918
  const extensionsDir = getExtensionsDir(ctx.configPath);
2869
2919
  const { entries, installs, allow } = maps;
@@ -2887,6 +2937,7 @@ let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends Diag
2887
2937
  console.error(`[old_miaoda_plugins_cleanup] rmSync ${target} failed: ${e.message}`);
2888
2938
  }
2889
2939
  }
2940
+ if (installs) setPluginInstallsMap(ctx.configPath, ctx.config, installs);
2890
2941
  }
2891
2942
  };
2892
2943
  OldMiaodaPluginsCleanupRule = __decorate([Rule({
@@ -2929,8 +2980,7 @@ BuiltinPluginMissingRule = __decorate([Rule({
2929
2980
  */
2930
2981
  function findOrphanedInstalls(ctx) {
2931
2982
  const extDir = getExtensionsDir(ctx.configPath);
2932
- const installs = getNestedMap(ctx.config, "plugins", "installs");
2933
- if (!installs) return [];
2983
+ const installs = getPluginInstallsMap(ctx.configPath, ctx.config);
2934
2984
  return Object.keys(installs).filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
2935
2985
  }
2936
2986
  let BuiltinPluginInstallsCleanupRule = class BuiltinPluginInstallsCleanupRule extends DiagnoseRule {
@@ -2946,11 +2996,10 @@ let BuiltinPluginInstallsCleanupRule = class BuiltinPluginInstallsCleanupRule ex
2946
2996
  const orphaned = findOrphanedInstalls(ctx);
2947
2997
  if (orphaned.length === 0) return;
2948
2998
  const orphanSet = new Set(orphaned);
2949
- const plugins = asRecord(ctx.config.plugins);
2950
- if (!plugins) return;
2951
- const installs = asRecord(plugins.installs);
2952
- if (installs) for (const name of orphaned) delete installs[name];
2953
- const rawAllow = plugins.allow;
2999
+ const installs = { ...getPluginInstallsMap(ctx.configPath, ctx.config) };
3000
+ for (const name of orphaned) delete installs[name];
3001
+ setPluginInstallsMap(ctx.configPath, ctx.config, installs);
3002
+ const rawAllow = asRecord(ctx.config.plugins)?.allow;
2954
3003
  if (Array.isArray(rawAllow)) for (let i = rawAllow.length - 1; i >= 0; i--) {
2955
3004
  const v = rawAllow[i];
2956
3005
  if (typeof v === "string" && orphanSet.has(v)) rawAllow.splice(i, 1);
@@ -3301,7 +3350,7 @@ function findLegacyResiduals(ctx) {
3301
3350
  if (asRecord(plugins?.entries)?.["feishu-openclaw-plugin"] != null) found.push("entries[legacy]");
3302
3351
  const allow = plugins?.allow;
3303
3352
  if (Array.isArray(allow) && allow.includes("feishu-openclaw-plugin")) found.push("allow[legacy]");
3304
- if (asRecord(plugins?.installs)?.["feishu-openclaw-plugin"] != null) found.push("installs[legacy]");
3353
+ if (asRecord(getPluginInstallsMap(ctx.configPath, ctx.config)["feishu-openclaw-plugin"]) != null) found.push("installs[legacy]");
3305
3354
  const extDir = getExtensionsDir(ctx.configPath);
3306
3355
  for (const name of LEGACY_DIRS_TO_REMOVE) if (node_fs.default.existsSync(node_path.default.join(extDir, name))) found.push(`fs/${name}`);
3307
3356
  return found;
@@ -3324,14 +3373,17 @@ function cleanupLegacyResiduals(ctx) {
3324
3373
  if (plugins) {
3325
3374
  const entries = asRecord(plugins.entries);
3326
3375
  if (entries && "feishu-openclaw-plugin" in entries) delete entries[LEGACY_LARK_PLUGIN_NAME];
3327
- const installs = asRecord(plugins.installs);
3328
- if (installs && "feishu-openclaw-plugin" in installs) delete installs[LEGACY_LARK_PLUGIN_NAME];
3329
3376
  const allow = plugins.allow;
3330
3377
  if (Array.isArray(allow)) {
3331
3378
  for (let i = allow.length - 1; i >= 0; i--) if (allow[i] === "feishu-openclaw-plugin") allow.splice(i, 1);
3332
3379
  if (!allow.includes("openclaw-lark")) allow.push(LARK_PLUGIN_NAME);
3333
3380
  }
3334
3381
  }
3382
+ const installs = { ...getPluginInstallsMap(ctx.configPath, ctx.config) };
3383
+ if ("feishu-openclaw-plugin" in installs) {
3384
+ delete installs[LEGACY_LARK_PLUGIN_NAME];
3385
+ setPluginInstallsMap(ctx.configPath, ctx.config, installs);
3386
+ }
3335
3387
  const extDir = getExtensionsDir(ctx.configPath);
3336
3388
  for (const name of LEGACY_DIRS_TO_REMOVE) {
3337
3389
  const target = node_path.default.join(extDir, name);
@@ -3752,13 +3804,13 @@ function detectInstalledLarkPlugin(ctx) {
3752
3804
  const allowRaw = asRecord(ctx.config.plugins)?.allow;
3753
3805
  const allow = Array.isArray(allowRaw) ? allowRaw.filter((e) => typeof e === "string") : [];
3754
3806
  const extDir = getExtensionsDir(ctx.configPath);
3755
- const installs = getNestedMap(ctx.config, "plugins", "installs");
3807
+ const installs = getPluginInstallsMap(ctx.configPath, ctx.config);
3756
3808
  for (const name of [LARK_PLUGIN_NAME, ...LEGACY_SHORT_NAMES]) {
3757
3809
  if (!allow.includes(name)) continue;
3758
3810
  const pkgPath = node_path.default.join(extDir, name, "package.json");
3759
3811
  if (!node_fs.default.existsSync(pkgPath)) continue;
3760
3812
  const pkg = readPluginPackageJson(pkgPath) ?? {};
3761
- const installEntry = installs && asRecord(installs[name]);
3813
+ const installEntry = asRecord(installs[name]);
3762
3814
  return {
3763
3815
  allowName: name,
3764
3816
  fullName: pkg.name ?? extractScopedNameFromSpec$1(installEntry?.spec),
@@ -4324,8 +4376,7 @@ function readInstalledLarkPlugin(ctx) {
4324
4376
  } catch {
4325
4377
  pkg = {};
4326
4378
  }
4327
- const installs = getNestedMap(ctx.config, "plugins", "installs");
4328
- const installEntry = installs ? asRecord(installs[LARK_PLUGIN_NAME]) : void 0;
4379
+ const installEntry = asRecord(getPluginInstallsMap(ctx.configPath, ctx.config)[LARK_PLUGIN_NAME]);
4329
4380
  return {
4330
4381
  name: pkg.name ?? extractScopedNameFromSpec(installEntry?.spec),
4331
4382
  version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
@@ -5599,12 +5650,13 @@ async function installExtension(tag, ossFileMap, opts = {}) {
5599
5650
  }
5600
5651
  const PLUGINS_TO_AUTO_ENABLE = [LARK_PLUGIN_NAME, MIAODA_PLUGIN_NAME];
5601
5652
  /**
5602
- * Merge each installed extension's installMetadata into openclaw.json's
5603
- * plugins.installs[<pkg.name>]. Atomic write via tmp + rename.
5653
+ * Merge each installed extension's installMetadata into the plugin installs
5654
+ * store. Old sandboxes only have openclaw.json.plugins.installs; newer
5655
+ * templates also carry plugins/installs.json and keep openclaw.json as a mirror.
5604
5656
  *
5605
5657
  * - No openclaw.json → log + return (not an error; some install contexts don't have it yet)
5606
5658
  * - Extension without installMetadata in manifest → skip that entry (log)
5607
- * - Existing plugins.installs entries for other extensions left untouched
5659
+ * - Existing install entries for other extensions left untouched
5608
5660
  *
5609
5661
  * Special-case for mem0: when openclaw-mem0-plugin is among the installed
5610
5662
  * targets, also append it to plugins.allow (idempotent) and seed
@@ -5613,7 +5665,7 @@ const PLUGINS_TO_AUTO_ENABLE = [LARK_PLUGIN_NAME, MIAODA_PLUGIN_NAME];
5613
5665
  */
5614
5666
  function updatePluginInstalls(configPath, installedPkgs) {
5615
5667
  if (!node_fs.default.existsSync(configPath)) {
5616
- console.error(`[install-extension] no config at ${configPath} — skip plugins.installs update`);
5668
+ console.error(`[install-extension] no config at ${configPath} — skip plugin installs update`);
5617
5669
  return;
5618
5670
  }
5619
5671
  const JSON5 = loadJSON5();
@@ -5621,8 +5673,7 @@ function updatePluginInstalls(configPath, installedPkgs) {
5621
5673
  const config = JSON5.parse(raw);
5622
5674
  if (!config.plugins || typeof config.plugins !== "object") config.plugins = {};
5623
5675
  const plugins = config.plugins;
5624
- if (!plugins.installs || typeof plugins.installs !== "object") plugins.installs = {};
5625
- const installs = plugins.installs;
5676
+ const installs = { ...getPluginInstallsMap(configPath, config) };
5626
5677
  let updated = 0;
5627
5678
  let skipped = 0;
5628
5679
  for (const pkg of installedPkgs) if (pkg.installMetadata) {
@@ -5650,10 +5701,11 @@ function updatePluginInstalls(configPath, installedPkgs) {
5650
5701
  enabled: true
5651
5702
  };
5652
5703
  }
5704
+ setPluginInstallsMap(configPath, config, installs);
5653
5705
  const tmpPath = configPath + ".installs-tmp";
5654
5706
  node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
5655
5707
  moveSafe(tmpPath, configPath);
5656
- console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
5708
+ console.error(`[install-extension] plugin installs updated: ${updated} entry(ies) for ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
5657
5709
  }
5658
5710
  function installOne$1(pkg, tarball, homeBase) {
5659
5711
  const destDir = node_path.default.join(homeBase, pkg.installPath);
@@ -11253,7 +11305,7 @@ async function reportCliRun(opts) {
11253
11305
  //#region src/help.ts
11254
11306
  const BIN = "mclaw-diagnose";
11255
11307
  function versionBanner() {
11256
- return `v0.1.20`;
11308
+ return `v0.1.21-alpha.0`;
11257
11309
  }
11258
11310
  const COMMANDS = [
11259
11311
  {
@@ -11447,7 +11499,7 @@ OPTIONS
11447
11499
  DESCRIPTION
11448
11500
  Downloads + installs one or more openclaw extension tarballs
11449
11501
  (feishu, miaoda, etc.) into <home_base>/workspace/agent/extensions/,
11450
- then splices installMetadata into openclaw.json's plugins.installs
11502
+ then splices installMetadata into the plugin installs store
11451
11503
  unless --skip-config-update is passed.
11452
11504
 
11453
11505
  ARGUMENTS
@@ -11459,7 +11511,7 @@ OPTIONS
11459
11511
  --extension=<name> Install a specific extension (repeatable).
11460
11512
  --home_base=<dir> Override the /home/gem base (tests).
11461
11513
  --config_path=<p> Override the openclaw.json path (tests).
11462
- --skip-config-update Leave plugins.installs in openclaw.json untouched.
11514
+ --skip-config-update Leave plugin install records untouched.
11463
11515
  --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
11464
11516
  `
11465
11517
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.21-alpha.0",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {