goalbuddy 0.3.6 → 0.3.7

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 (73) hide show
  1. package/README.md +17 -8
  2. package/RELEASE-0.3.5.md +4 -4
  3. package/RELEASE-0.3.7.md +127 -0
  4. package/goalbuddy/SKILL.md +19 -10
  5. package/goalbuddy/scripts/check-goal-state.mjs +53 -0
  6. package/goalbuddy/scripts/render-task-prompt.mjs +22 -1
  7. package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/README.md +7 -9
  8. package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/examples/sample-goal/state.yaml +5 -5
  9. package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/examples/subgoal-parent/state.yaml +3 -3
  10. package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +3 -3
  11. package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/lib/goal-board.mjs +2 -2
  12. package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +4 -4
  13. package/goalbuddy/{extend → surfaces}/local-goal-board/test/local-goal-board.test.mjs +8 -8
  14. package/goalbuddy/templates/goal.md +9 -0
  15. package/goalbuddy/templates/state.yaml +7 -6
  16. package/internal/assets/goalbuddy-v0.3.7-release.png +0 -0
  17. package/internal/cli/goal-maker.mjs +9 -711
  18. package/package.json +4 -4
  19. package/plugins/goalbuddy/.claude-plugin/plugin.json +3 -4
  20. package/plugins/goalbuddy/.codex-plugin/plugin.json +5 -6
  21. package/plugins/goalbuddy/README.md +4 -3
  22. package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +19 -10
  23. package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +53 -0
  24. package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +22 -1
  25. package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/README.md +7 -9
  26. package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/examples/sample-goal/state.yaml +5 -5
  27. package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/examples/subgoal-parent/state.yaml +3 -3
  28. package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +3 -3
  29. package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/scripts/lib/goal-board.mjs +2 -2
  30. package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +4 -4
  31. package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/test/local-goal-board.test.mjs +8 -8
  32. package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +9 -0
  33. package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +7 -6
  34. package/examples/extend-catalog-workflow/goal.md +0 -53
  35. package/examples/extend-catalog-workflow/notes/T001-extension-model-map.md +0 -47
  36. package/examples/extend-catalog-workflow/notes/T002-architecture-decision.md +0 -48
  37. package/examples/extend-catalog-workflow/notes/T003-implementation-summary.md +0 -43
  38. package/examples/extend-catalog-workflow/notes/T004-root-extend-folder.md +0 -24
  39. package/examples/extend-catalog-workflow/notes/T005-layout-cleanup.md +0 -46
  40. package/examples/extend-catalog-workflow/notes/T006-catalog-location.md +0 -50
  41. package/examples/extend-catalog-workflow/notes/T999-completion-audit.md +0 -36
  42. package/examples/extend-catalog-workflow/state.yaml +0 -327
  43. package/examples/github-pr-workflow-extension/pr-handoff.md +0 -46
  44. package/goalbuddy/extend/github-projects/README.md +0 -105
  45. package/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +0 -63
  46. package/goalbuddy/extend/github-projects/extension.yaml +0 -43
  47. package/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +0 -728
  48. package/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +0 -362
  49. package/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +0 -193
  50. package/goalbuddy/extend/github-projects/test/github-projects.test.mjs +0 -267
  51. package/goalbuddy/extend/local-goal-board/extension.yaml +0 -39
  52. package/internal/assets/extend-release.png +0 -0
  53. package/internal/assets/extend-release.svg +0 -83
  54. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/README.md +0 -105
  55. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +0 -63
  56. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/extension.yaml +0 -43
  57. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +0 -728
  58. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +0 -362
  59. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +0 -193
  60. package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/test/github-projects.test.mjs +0 -267
  61. package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/extension.yaml +0 -39
  62. /package/goalbuddy/{extend → surfaces}/local-goal-board/assets/goalbuddy-mark.png +0 -0
  63. /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/sample-goal/notes/T001-scout.md +0 -0
  64. /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/goal.md +0 -0
  65. /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/notes/.gitkeep +0 -0
  66. /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +0 -0
  67. /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +0 -0
  68. /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/assets/goalbuddy-mark.png +0 -0
  69. /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/sample-goal/notes/T001-scout.md +0 -0
  70. /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/goal.md +0 -0
  71. /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/notes/.gitkeep +0 -0
  72. /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +0 -0
  73. /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +0 -0
@@ -5,13 +5,12 @@ import {
5
5
  mkdirSync,
6
6
  readdirSync,
7
7
  readFileSync,
8
- renameSync,
9
8
  rmSync,
10
9
  writeFileSync,
11
10
  } from "node:fs";
12
11
  import { createHash } from "node:crypto";
13
12
  import { spawnSync } from "node:child_process";
14
- import { basename, dirname, join, normalize, resolve } from "node:path";
13
+ import { basename, dirname, join, resolve } from "node:path";
15
14
  import { homedir } from "node:os";
16
15
  import { fileURLToPath } from "node:url";
17
16
 
@@ -29,7 +28,6 @@ const claudePluginSource = join(packageRoot, "plugins", "goalbuddy");
29
28
  const packageInfo = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
30
29
  const defaultCodexHome = process.env.CODEX_HOME || join(homedir(), ".codex");
31
30
  const defaultClaudeHome = process.env.CLAUDE_HOME || join(homedir(), ".claude");
32
- const defaultCatalogUrl = "https://raw.githubusercontent.com/tolibear/goalbuddy/main/extend/catalog.json";
33
31
  const requiredAgentFiles = [
34
32
  "goal_judge.toml",
35
33
  "goal_scout.toml",
@@ -40,15 +38,11 @@ const requiredClaudeAgentFiles = [
40
38
  "goal-judge.md",
41
39
  "goal-worker.md",
42
40
  ];
43
- const bundledCoreExtensionIds = new Set(["github-projects", "local-goal-board"]);
44
41
  const optionsWithValues = new Set([
45
- "--catalog",
46
- "--catalog-url",
47
42
  "--claude-home",
48
43
  "--codex-home",
49
44
  "--goal",
50
45
  "--host",
51
- "--kind",
52
46
  "--port",
53
47
  "--source",
54
48
  "--target",
@@ -128,9 +122,6 @@ async function main() {
128
122
  }
129
123
  plugin();
130
124
  break;
131
- case "extend":
132
- await extend();
133
- break;
134
125
  case "board":
135
126
  await board();
136
127
  break;
@@ -213,12 +204,7 @@ Usage:
213
204
  ${canonicalCliName} agents [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--force]
214
205
  ${canonicalCliName} doctor [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--goal-ready]
215
206
  ${canonicalCliName} check-update [--json]
216
- ${canonicalCliName} extend [--catalog-url <url-or-path>] [--kind <kind>] [--json]
217
- ${canonicalCliName} extend <id> [--catalog-url <url-or-path>] [--json]
218
- ${canonicalCliName} extend install <id> [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
219
- ${canonicalCliName} extend install --all [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
220
- ${canonicalCliName} extend doctor [<id>] [--codex-home <path>] [--json]
221
- ${canonicalCliName} board <docs/goals/slug> [--catalog-url <url-or-path>] [--host <host>] [--port <port>] [--once] [--json]
207
+ ${canonicalCliName} board <docs/goals/slug> [--host <host>] [--port <port>] [--once] [--json]
222
208
  ${canonicalCliName} prompt <docs/goals/slug> [--task T###] [--board <path/to/state.yaml>] [--json]
223
209
  ${canonicalCliName} parallel-plan <docs/goals/slug> [--json]
224
210
 
@@ -235,8 +221,6 @@ Compatibility:
235
221
  Environment:
236
222
  CODEX_HOME Overrides the default ~/.codex target.
237
223
  CLAUDE_HOME Overrides the default ~/.claude target (and selects Claude Code unless --target codex is set).
238
- GOALBUDDY_EXTEND_CATALOG_URL Overrides the default GitHub-hosted extension catalog.
239
- GOAL_MAKER_EXTEND_CATALOG_URL Legacy fallback for the extension catalog.
240
224
  `);
241
225
  }
242
226
 
@@ -289,16 +273,11 @@ function installClaudeSkill({ quiet = false } = {}) {
289
273
 
290
274
  const previousMetadata = readInstallMetadata(target);
291
275
  const previousFingerprint = existsSync(target) ? directoryFingerprint(target, { exclude: installFingerprintExcludes() }) : "";
292
- const preservedExtensions = preserveInstalledExtensions([target], { tempRoot: claudeHome() });
293
- const extensionTempPath = preservedExtensions.tempPath;
294
- const preservedExtensionIds = preservedExtensions.ids;
295
276
 
296
277
  mkdirSync(dirname(target), { recursive: true });
297
278
  rmSync(target, { recursive: true, force: true });
298
279
  cpSync(skillSource, target, { recursive: true });
299
- restoreInstalledExtensions(target, extensionTempPath);
300
280
  writeInstallMetadata(target, previousMetadata);
301
- cleanupPreservedExtensions([extensionTempPath]);
302
281
 
303
282
  const currentFingerprint = directoryFingerprint(target, { exclude: installFingerprintExcludes() });
304
283
  const status = previousFingerprint
@@ -311,7 +290,6 @@ function installClaudeSkill({ quiet = false } = {}) {
311
290
  path: target,
312
291
  previous_version: previousMetadata?.package_version || "",
313
292
  current_version: packageInfo.version,
314
- preserved_extensions: preservedExtensionIds,
315
293
  };
316
294
  }
317
295
 
@@ -362,7 +340,6 @@ async function buildClaudeInstallReport() {
362
340
  skill: installClaudeSkill({ quiet }),
363
341
  agents: installClaudeAgents({ quiet }),
364
342
  legacy_commands_cleanup: cleanupLegacyClaudeCommands({ quiet }),
365
- extensions: await extensionDiscoverySummary(),
366
343
  warnings: [],
367
344
  };
368
345
 
@@ -463,27 +440,6 @@ function printClaudeInstallReport(report) {
463
440
  if (report.legacy_commands_cleanup?.removed) {
464
441
  console.log(`Removed legacy command: ${report.legacy_commands_cleanup.path}`);
465
442
  }
466
- if (report.skill.preserved_extensions.length) {
467
- console.log(`Preserved extensions: ${report.skill.preserved_extensions.join(", ")}`);
468
- }
469
-
470
- if (report.extensions?.error) {
471
- console.log("");
472
- console.log(`Extensions: unavailable (${report.extensions.error})`);
473
- } else if (report.extensions) {
474
- console.log("");
475
- console.log(`Extensions: ${report.extensions.available_count} available from ${report.extensions.catalog_url}`);
476
- if (report.extensions.recommended?.length) {
477
- console.log("");
478
- console.log("Recommended:");
479
- for (const extension of report.extensions.recommended.slice(0, 3)) {
480
- console.log(` ${extension.name || extension.id}`);
481
- if (extension.summary) console.log(` ${extension.summary}`);
482
- console.log(` Details: npx ${extension.next_command}`);
483
- }
484
- }
485
- }
486
-
487
443
  console.log("");
488
444
  console.log("Next:");
489
445
  console.log(` Restart Claude Code, then run: /goal-prep`);
@@ -503,9 +459,6 @@ function installSkill({ force = true, quiet = false } = {}) {
503
459
 
504
460
  const previousMetadata = readInstallMetadata(target) || readInstallMetadata(legacyTarget);
505
461
  const previousFingerprint = existsSync(target) ? directoryFingerprint(target, { exclude: installFingerprintExcludes() }) : "";
506
- const preservedExtensions = preserveInstalledExtensions([target, legacyTarget], { tempRoot: codexHome() });
507
- const extensionTempPath = preservedExtensions.tempPath;
508
- const preservedExtensionIds = preservedExtensions.ids;
509
462
 
510
463
  mkdirSync(dirname(target), { recursive: true });
511
464
  if (existsSync(target)) {
@@ -520,16 +473,13 @@ function installSkill({ force = true, quiet = false } = {}) {
520
473
  cpSync(skillSource, target, {
521
474
  recursive: true,
522
475
  });
523
- restoreInstalledExtensions(target, extensionTempPath);
524
476
  writeInstallMetadata(target, previousMetadata);
525
477
 
526
478
  mkdirSync(dirname(legacyTarget), { recursive: true });
527
479
  rmSync(legacyTarget, { recursive: true, force: true });
528
480
  mkdirSync(legacyTarget, { recursive: true });
529
481
  writeFileSync(join(legacyTarget, "SKILL.md"), compatibilitySkillBody());
530
- restoreInstalledExtensions(legacyTarget, extensionTempPath);
531
482
  writeInstallMetadata(legacyTarget, previousMetadata);
532
- cleanupPreservedExtensions([extensionTempPath]);
533
483
 
534
484
  const currentFingerprint = directoryFingerprint(target, { exclude: installFingerprintExcludes() });
535
485
  const status = previousFingerprint
@@ -543,7 +493,6 @@ function installSkill({ force = true, quiet = false } = {}) {
543
493
  compatibility_path: legacyTarget,
544
494
  previous_version: previousMetadata?.package_version || "",
545
495
  current_version: packageInfo.version,
546
- preserved_extensions: preservedExtensionIds,
547
496
  };
548
497
  }
549
498
 
@@ -605,7 +554,6 @@ async function installAll() {
605
554
  codex_home: codexHome(),
606
555
  skill: installSkill({ force: true, quiet }),
607
556
  agents: installAgents({ quiet }),
608
- extensions: await extensionDiscoverySummary(),
609
557
  warnings: [],
610
558
  };
611
559
 
@@ -771,20 +719,14 @@ function installPlugin({ quiet = false } = {}) {
771
719
 
772
720
  const pluginManifest = JSON.parse(readFileSync(pluginManifestPath, "utf8"));
773
721
  const pluginCachePath = pluginCacheRoot(pluginManifest.version);
774
- const pluginSkillPath = join(pluginCachePath, "skills", canonicalSkillDirectory);
775
722
  const marketplace = runCodex(["plugin", "marketplace", "add", source]);
776
723
  if (!marketplace.ok) {
777
724
  throw new Error(`Failed to add Codex plugin marketplace: ${firstLine(marketplace.stderr || marketplace.stdout)}`);
778
725
  }
779
726
 
780
- const legacySkillPaths = legacyCodexSkillRoots();
781
- const existingPluginSkillPath = installedPluginSkillRoot();
782
- const preservedExtensions = preserveInstalledExtensions([existingPluginSkillPath, ...legacySkillPaths], { tempRoot: dirname(pluginCachePath) });
783
727
  mkdirSync(dirname(pluginCachePath), { recursive: true });
784
728
  rmSync(pluginCachePath, { recursive: true, force: true });
785
729
  cpSync(pluginSource, pluginCachePath, { recursive: true });
786
- restoreInstalledExtensions(pluginSkillPath, preservedExtensions.tempPath);
787
- cleanupPreservedExtensions([preservedExtensions.tempPath]);
788
730
  const removedLegacySkillPaths = cleanupLegacyCodexSkills();
789
731
  const configPath = enablePluginConfig();
790
732
  const agents = installAgents({ quiet: true });
@@ -799,7 +741,6 @@ function installPlugin({ quiet = false } = {}) {
799
741
  cache_path: pluginCachePath,
800
742
  config_path: configPath,
801
743
  agents,
802
- preserved_extensions: preservedExtensions.ids,
803
744
  removed_legacy_skill_paths: removedLegacySkillPaths,
804
745
  };
805
746
 
@@ -815,9 +756,6 @@ function installPlugin({ quiet = false } = {}) {
815
756
  console.log(`Cache: ${pluginCachePath}`);
816
757
  console.log(`Config: ${configPath}`);
817
758
  console.log(`Agents: ${summarizeStatuses(report.agents)}`);
818
- if (report.preserved_extensions.length) {
819
- console.log(`Preserved extensions: ${report.preserved_extensions.join(", ")}`);
820
- }
821
759
  if (report.removed_legacy_skill_paths.length) {
822
760
  console.log(`Removed legacy personal skills: ${report.removed_legacy_skill_paths.join(", ")}`);
823
761
  }
@@ -825,9 +763,8 @@ function installPlugin({ quiet = false } = {}) {
825
763
  console.log("Restart Codex, then use:");
826
764
  console.log(` $${canonicalSkillName}`);
827
765
  console.log("");
828
- console.log("Bundled visual boards:");
766
+ console.log("Goal surface:");
829
767
  console.log(` npx ${canonicalCliName} board docs/goals/<slug>`);
830
- console.log(` npx ${canonicalCliName} extend github-projects`);
831
768
  return report;
832
769
  }
833
770
 
@@ -969,33 +906,6 @@ function firstLine(value) {
969
906
  return (value || "").split(/\r?\n/).find((line) => line.trim())?.trim() || "";
970
907
  }
971
908
 
972
- async function extend() {
973
- if (args.includes("--help") || args.includes("-h")) {
974
- extendUsage();
975
- return;
976
- }
977
-
978
- const subcommand = positional(1) || "";
979
- switch (subcommand) {
980
- case "":
981
- await extendCatalog();
982
- break;
983
- case "install":
984
- await extendInstall();
985
- break;
986
- case "doctor":
987
- extendDoctor();
988
- break;
989
- case "help":
990
- case "--help":
991
- case "-h":
992
- extendUsage();
993
- break;
994
- default:
995
- await extendDetails(subcommand);
996
- }
997
- }
998
-
999
909
  async function board() {
1000
910
  const goal = optionValue("--goal") || positional(1);
1001
911
  if (!goal) {
@@ -1003,7 +913,7 @@ async function board() {
1003
913
  process.exit(2);
1004
914
  }
1005
915
 
1006
- const script = await ensureLocalBoardExtension();
916
+ const script = ensureLocalBoardSurface();
1007
917
  const scriptArgs = [script, "--goal", goal];
1008
918
  for (const option of ["--host", "--port"]) {
1009
919
  const value = optionValue(option);
@@ -1061,303 +971,18 @@ async function parallelPlan() {
1061
971
  process.exit(result.status ?? 1);
1062
972
  }
1063
973
 
1064
- async function ensureLocalBoardExtension() {
1065
- const id = "local-goal-board";
1066
- const script = join(extensionTarget(id), "scripts", "local-goal-board.mjs");
1067
- if (existsSync(script)) return script;
1068
-
1069
- const catalog = await loadCatalog();
1070
- const extension = catalog.extensions.find((candidate) => candidate.id === id);
1071
- if (!extension) {
1072
- throw new Error(`Extension ${id} is not available in ${catalog.url}.`);
1073
- }
1074
-
1075
- await installCatalogExtension(catalog, extension);
974
+ function ensureLocalBoardSurface() {
975
+ const script = join(skillSource, "surfaces", "local-goal-board", "scripts", "local-goal-board.mjs");
1076
976
  if (!existsSync(script)) {
1077
- throw new Error(`Extension ${id} installed, but script is missing: ${script}`);
977
+ throw new Error(`Bundled GoalBuddy board surface is missing: ${script}`);
1078
978
  }
1079
979
  return script;
1080
980
  }
1081
981
 
1082
- function extendUsage() {
1083
- console.log(`${canonicalProductName} Extend
1084
-
1085
- Usage:
1086
- ${canonicalCliName} extend [--catalog-url <url-or-path>] [--kind <kind>] [--json]
1087
- ${canonicalCliName} extend <id> [--catalog-url <url-or-path>] [--json]
1088
- ${canonicalCliName} extend install <id> [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
1089
- ${canonicalCliName} extend install --all [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
1090
- ${canonicalCliName} extend doctor [<id>] [--codex-home <path>] [--json]
1091
-
1092
- States:
1093
- available Listed in the catalog.
1094
- installed Copied into the local ${canonicalProductName} skill install.
1095
- enabled Allowed by a goal or task. Not implemented by this command yet.
1096
- configured Required local env/provider settings are present.
1097
-
1098
- Catalog:
1099
- Defaults to ${defaultCatalogUrl}
1100
- Override with --catalog-url, GOALBUDDY_EXTEND_CATALOG_URL, or legacy GOAL_MAKER_EXTEND_CATALOG_URL.
1101
- `);
1102
- }
1103
-
1104
- async function extendCatalog() {
1105
- const catalog = await loadCatalog();
1106
- const kind = optionValue("--kind");
1107
- const extensions = catalog.extensions
1108
- .filter((extension) => !kind || extension.kind === kind)
1109
- .map(extensionWithLocalState);
1110
-
1111
- if (hasFlag("--json")) {
1112
- printJson({ catalog_url: catalog.url, extensions });
1113
- return;
1114
- }
1115
-
1116
- console.log("Available extensions");
1117
- if (extensions.length === 0) {
1118
- console.log("");
1119
- console.log(kind ? `No ${kind} extensions found.` : "No extensions found.");
1120
- return;
1121
- }
1122
- console.log("");
1123
- for (const extension of extensions) {
1124
- console.log(extension.name || extension.id);
1125
- if (extension.summary) console.log(` ${extension.summary}`);
1126
- console.log(` Details: npx ${canonicalCliName} extend ${extension.id}`);
1127
- console.log("");
1128
- }
1129
- console.log("Install all:");
1130
- console.log(` npx ${canonicalCliName} extend install --all`);
1131
- }
1132
-
1133
- async function extendDetails(id) {
1134
- const catalog = await loadCatalog();
1135
- const extension = catalog.extensions.find((candidate) => candidate.id === id);
1136
- if (!extension) {
1137
- printExtensionNotFound(id, catalog.extensions);
1138
- process.exit(1);
1139
- }
1140
- validateCatalogExtension(extension);
1141
- const detailed = extensionWithLocalState(extension);
1142
-
1143
- if (hasFlag("--json")) {
1144
- printJson({ catalog_url: catalog.url, extension: detailed });
1145
- return;
1146
- }
1147
-
1148
- console.log(extension.name || extension.summary || "");
1149
- console.log("");
1150
- if (extension.summary && extension.summary !== extension.name) {
1151
- console.log(extension.summary);
1152
- console.log("");
1153
- }
1154
- console.log(`Status: ${detailed.state.installed ? "installed" : "available"}`);
1155
- console.log(`Configured: ${detailed.state.configured ? "yes" : "no"}`);
1156
- console.log(`ID: ${extension.id}`);
1157
- console.log(`Kind: ${extension.kind}`);
1158
- if (extension.version) console.log(`Version: ${extension.version}`);
1159
- console.log(`Activation: ${extension.activation || "unspecified"}`);
1160
- console.log(`Safe by default: ${extension.safe_by_default ? "yes" : "no"}`);
1161
- console.log(`Requires approval: ${extension.requires_approval ? "yes" : "no"}`);
1162
- if (!detailed.state.configured && detailed.state.missing_env.length) {
1163
- console.log(`Missing env: ${detailed.state.missing_env.join(", ")}`);
1164
- }
1165
- printListSection("Use when", extension.use_when);
1166
- printListSection("Outputs", extension.outputs);
1167
- printListSection("Reads", extension.reads);
1168
- printListSection("Writes", extension.writes);
1169
- printListSection("Side effects", extension.side_effects);
1170
- printListSection("Agent instructions", extension.agent_instructions);
1171
- printListSection("Auth env", extension.auth?.env);
1172
- printSupports(extension.supports);
1173
- console.log("");
1174
- console.log("Local use prompt:");
1175
- console.log(` ${extension.local_use_prompt || `Use the ${extension.name || extension.id} extension for docs/goals/<slug>/goal.md and write its ${firstValue(extension.outputs, "artifact")} as Markdown.`}`);
1176
- console.log("");
1177
- console.log("Install:");
1178
- console.log(` npx ${canonicalCliName} extend install ${extension.id}`);
1179
- console.log("");
1180
- console.log("Preview install:");
1181
- console.log(` npx ${canonicalCliName} extend install ${extension.id} --dry-run`);
1182
- }
1183
-
1184
- function printListSection(title, values) {
1185
- if (!Array.isArray(values) || values.length === 0) return;
1186
- console.log("");
1187
- console.log(`${title}:`);
1188
- for (const value of values) console.log(` - ${value}`);
1189
- }
1190
-
1191
- function printSupports(supports) {
1192
- if (!supports || typeof supports !== "object") return;
1193
- const entries = Object.entries(supports);
1194
- if (entries.length === 0) return;
1195
- console.log("");
1196
- console.log("Supports:");
1197
- for (const [key, value] of entries) console.log(` - ${key}: ${value}`);
1198
- }
1199
-
1200
- function firstValue(values, fallback) {
1201
- return Array.isArray(values) && values.length ? values[0] : fallback;
1202
- }
1203
-
1204
- async function extendInstall() {
1205
- const id = positional(2);
1206
- const catalog = await loadCatalog();
1207
- if (hasFlag("--all")) {
1208
- await extendInstallAll(catalog);
1209
- return;
1210
- }
1211
-
1212
- if (!id) throw new Error(`Missing extension id. Usage: ${canonicalCliName} extend install <id>`);
1213
- const extension = catalog.extensions.find((candidate) => candidate.id === id);
1214
- if (!extension) {
1215
- printExtensionNotFound(id, catalog.extensions);
1216
- process.exit(1);
1217
- }
1218
- const result = await installCatalogExtension(catalog, extension);
1219
-
1220
- if (hasFlag("--dry-run")) {
1221
- if (hasFlag("--json")) {
1222
- printJson({ dry_run: true, extension: extensionWithLocalState(extension), target: result.target, files: result.plan });
1223
- } else {
1224
- console.log(`Would install ${extension.id} to ${result.target}`);
1225
- for (const file of result.plan) console.log(` ${file.path}`);
1226
- }
1227
- return;
1228
- }
1229
-
1230
- if (hasFlag("--json")) {
1231
- printJson({ installed: true, extension: extension.id, target: result.target });
1232
- } else {
1233
- console.log(`Installed ${extension.id} to ${result.target}`);
1234
- }
1235
- }
1236
-
1237
- async function extendInstallAll(catalog) {
1238
- const results = [];
1239
- for (const extension of catalog.extensions) {
1240
- if (existsSync(extensionTarget(extension.id)) && !hasFlag("--force")) {
1241
- validateCatalogExtension(extension);
1242
- results.push({ extension, target: extensionTarget(extension.id), plan: installPlan(catalog, extension, extensionTarget(extension.id)), skipped: true });
1243
- continue;
1244
- }
1245
- results.push(await installCatalogExtension(catalog, extension));
1246
- }
1247
-
1248
- if (hasFlag("--dry-run")) {
1249
- if (hasFlag("--json")) {
1250
- printJson({
1251
- dry_run: true,
1252
- extensions: results.map(({ extension, target, plan }) => ({
1253
- extension: extensionWithLocalState(extension),
1254
- target,
1255
- files: plan,
1256
- })),
1257
- });
1258
- } else {
1259
- console.log(`Would install ${results.length} extensions`);
1260
- for (const { extension, target, plan } of results) {
1261
- console.log(`${extension.id} -> ${target}`);
1262
- for (const file of plan) console.log(` ${file.path}`);
1263
- }
1264
- }
1265
- return;
1266
- }
1267
-
1268
- if (hasFlag("--json")) {
1269
- printJson({
1270
- installed: true,
1271
- count: results.length,
1272
- extensions: results.map(({ extension, target, skipped }) => ({ id: extension.id, target, skipped: Boolean(skipped) })),
1273
- });
1274
- } else {
1275
- const installedCount = results.filter((result) => !result.skipped).length;
1276
- const skippedCount = results.length - installedCount;
1277
- console.log(`Installed ${installedCount} extensions${skippedCount ? `, skipped ${skippedCount} already installed` : ""}`);
1278
- for (const { extension, target, skipped } of results) console.log(` ${extension.id} -> ${target}${skipped ? " (already installed)" : ""}`);
1279
- }
1280
- }
1281
-
1282
- async function installCatalogExtension(catalog, extension) {
1283
- validateCatalogExtension(extension);
1284
- const target = extensionTarget(extension.id);
1285
- const plan = installPlan(catalog, extension, target);
1286
-
1287
- if (hasFlag("--dry-run")) return { extension, target, plan };
1288
-
1289
- assertSkillInstalledForExtensionInstall();
1290
- if (existsSync(target) && !hasFlag("--force")) {
1291
- throw new Error(`Extension already installed: ${target}. Use --force to overwrite.`);
1292
- }
1293
-
1294
- const temp = `${target}.tmp-${process.pid}-${Date.now()}`;
1295
- rmSync(temp, { recursive: true, force: true });
1296
- mkdirSync(temp, { recursive: true });
1297
-
1298
- try {
1299
- for (const file of plan) {
1300
- const content = await readResource(file.url);
1301
- const actualSha = sha256(content);
1302
- if (actualSha !== file.sha256) {
1303
- throw new Error(`Checksum mismatch for ${file.path}: expected ${file.sha256}, got ${actualSha}`);
1304
- }
1305
- const destination = join(temp, file.path);
1306
- mkdirSync(dirname(destination), { recursive: true });
1307
- writeFileSync(destination, content);
1308
- }
1309
-
1310
- writeFileSync(join(temp, ".installed.json"), `${JSON.stringify({
1311
- id: extension.id,
1312
- version: extension.version || "",
1313
- kind: extension.kind,
1314
- catalog_url: catalog.url,
1315
- installed_at: new Date().toISOString(),
1316
- manifest: publicExtension(extension),
1317
- files: plan.map(({ path, sha256: digest }) => ({ path, sha256: digest })),
1318
- }, null, 2)}\n`);
1319
-
1320
- rmSync(target, { recursive: true, force: true });
1321
- mkdirSync(dirname(target), { recursive: true });
1322
- renameSync(temp, target);
1323
- } catch (error) {
1324
- rmSync(temp, { recursive: true, force: true });
1325
- throw error;
1326
- }
1327
-
1328
- return { extension, target, plan };
1329
- }
1330
-
1331
- function extendDoctor() {
1332
- const id = positional(2);
1333
- const targets = installedExtensions().filter((extension) => !id || extension.id === id);
1334
- if (id && targets.length === 0) throw new Error(`Extension is not installed: ${id}`);
1335
-
1336
- const reports = targets.map(doctorInstalledExtension);
1337
- const ok = reports.every((report) => report.ok);
1338
-
1339
- if (hasFlag("--json")) {
1340
- printJson({ ok, extensions: reports });
1341
- } else if (reports.length === 0) {
1342
- console.log("No extensions installed.");
1343
- } else {
1344
- for (const report of reports) {
1345
- console.log(`${report.ok ? "ok" : "not ok"}\t${report.id}`);
1346
- for (const issue of report.issues) console.log(` - ${issue}`);
1347
- }
1348
- }
1349
-
1350
- process.exit(ok ? 0 : 1);
1351
- }
1352
-
1353
982
  function installedSkillRoot() {
1354
983
  return join(codexHome(), "skills", canonicalSkillDirectory);
1355
984
  }
1356
985
 
1357
- function installedPluginSkillRoot() {
1358
- return installedCodexPlugin().skill_path;
1359
- }
1360
-
1361
986
  function installedCodexPlugin() {
1362
987
  const root = join(codexHome(), "plugins", "cache", pluginName, pluginName);
1363
988
  const configPath = join(codexHome(), "config.toml");
@@ -1413,119 +1038,10 @@ function pluginConfigEnabled(configPath) {
1413
1038
  return false;
1414
1039
  }
1415
1040
 
1416
- function activeSkillRoot() {
1417
- if (existsSync(join(installedSkillRoot(), "SKILL.md"))) return installedSkillRoot();
1418
- const pluginSkillRoot = installedPluginSkillRoot();
1419
- if (pluginSkillRoot) return pluginSkillRoot;
1420
- return installedSkillRoot();
1421
- }
1422
-
1423
1041
  function legacyInstalledSkillRoot() {
1424
1042
  return join(codexHome(), "skills", legacySkillName);
1425
1043
  }
1426
1044
 
1427
- function extendRoot() {
1428
- return join(activeSkillRoot(), "extend");
1429
- }
1430
-
1431
- function extensionTarget(id) {
1432
- return join(extendRoot(), id);
1433
- }
1434
-
1435
- function catalogUrl() {
1436
- return optionValue("--catalog-url")
1437
- || optionValue("--catalog")
1438
- || process.env.GOALBUDDY_EXTEND_CATALOG_URL
1439
- || process.env.GOAL_MAKER_EXTEND_CATALOG_URL
1440
- || defaultCatalogUrl;
1441
- }
1442
-
1443
- async function loadCatalog() {
1444
- const url = catalogUrl();
1445
- const text = await readResource(url);
1446
- const catalog = JSON.parse(text);
1447
- if (!Array.isArray(catalog.extensions)) {
1448
- throw new Error("Extension catalog must contain an extensions array.");
1449
- }
1450
- return { ...catalog, url };
1451
- }
1452
-
1453
- function printExtensionNotFound(id, extensions) {
1454
- console.error(`Extension not found: ${id}`);
1455
- if (extensions.length) {
1456
- console.error("");
1457
- console.error("Available extensions:");
1458
- for (const extension of extensions) {
1459
- console.error(` ${extension.id}`);
1460
- }
1461
- }
1462
- console.error("");
1463
- console.error("Try:");
1464
- console.error(` npx ${canonicalCliName} extend`);
1465
- }
1466
-
1467
- function validateCatalogExtension(extension) {
1468
- if (!extension.id || !/^[a-z0-9][a-z0-9-]*$/.test(extension.id)) {
1469
- throw new Error(`Invalid extension id: ${extension.id || "<missing>"}`);
1470
- }
1471
- if (!extension.kind) throw new Error(`Extension ${extension.id} missing kind.`);
1472
- if (!Array.isArray(extension.files) || extension.files.length === 0) {
1473
- throw new Error(`Extension ${extension.id} must list files.`);
1474
- }
1475
- for (const file of extension.files) {
1476
- validateCatalogFile(extension, file);
1477
- }
1478
- }
1479
-
1480
- function validateCatalogFile(extension, file) {
1481
- if (!file.path) throw new Error(`Extension ${extension.id} has a file without path.`);
1482
- if (!file.url) throw new Error(`Extension ${extension.id} file ${file.path} missing url.`);
1483
- if (!/^[a-f0-9]{64}$/i.test(file.sha256 || "")) {
1484
- throw new Error(`Extension ${extension.id} file ${file.path} must include sha256.`);
1485
- }
1486
- safeRelativePath(file.path);
1487
- }
1488
-
1489
- function installPlan(catalog, extension, target) {
1490
- return extension.files.map((file) => ({
1491
- path: safeRelativePath(file.path),
1492
- url: resolveResourceUrl(catalog.url, file.url),
1493
- sha256: file.sha256.toLowerCase(),
1494
- target: join(target, safeRelativePath(file.path)),
1495
- }));
1496
- }
1497
-
1498
- function safeRelativePath(path) {
1499
- const normalized = normalize(path).replaceAll("\\", "/");
1500
- if (!normalized || normalized.startsWith("../") || normalized === ".." || normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) {
1501
- throw new Error(`Unsafe extension file path: ${path}`);
1502
- }
1503
- return normalized;
1504
- }
1505
-
1506
- function resolveResourceUrl(base, value) {
1507
- if (/^https?:\/\//.test(value) || value.startsWith("file://") || value.startsWith("/")) return value;
1508
- if (/^https?:\/\//.test(base) || base.startsWith("file://")) {
1509
- return new URL(value, base).href;
1510
- }
1511
- return resolve(dirname(resolve(base)), value);
1512
- }
1513
-
1514
- async function readResource(location) {
1515
- if (/^https?:\/\//.test(location)) {
1516
- if (!globalThis.fetch) throw new Error("This Node runtime does not provide fetch.");
1517
- const response = await globalThis.fetch(location, {
1518
- headers: {
1519
- "accept-encoding": "identity",
1520
- },
1521
- });
1522
- if (!response.ok) throw new Error(`Failed to fetch ${location}: HTTP ${response.status}`);
1523
- return Buffer.from(await response.arrayBuffer());
1524
- }
1525
- const path = location.startsWith("file://") ? fileURLToPath(location) : resolve(location);
1526
- return readFileSync(path);
1527
- }
1528
-
1529
1045
  function sha256(content) {
1530
1046
  return createHash("sha256").update(content).digest("hex");
1531
1047
  }
@@ -1558,50 +1074,8 @@ function listFiles(root, { exclude = new Set(), prefix = "" } = {}) {
1558
1074
  return files;
1559
1075
  }
1560
1076
 
1561
- function preserveInstalledExtensions(targets, { tempRoot = "" } = {}) {
1562
- const ids = [];
1563
- const firstTarget = targets.find(Boolean) || codexHome();
1564
- const tempPath = join(tempRoot || dirname(dirname(firstTarget)), `.goalbuddy-preserved-extend-${process.pid}-${Date.now()}`);
1565
- let hasExtensions = false;
1566
- for (const target of targets) {
1567
- if (!target) continue;
1568
- const source = join(target, "extend");
1569
- if (!existsSync(source)) continue;
1570
- for (const entry of readdirSync(source, { withFileTypes: true })) {
1571
- if (bundledCoreExtensionIds.has(entry.name)) continue;
1572
- mkdirSync(tempPath, { recursive: true });
1573
- const from = join(source, entry.name);
1574
- const to = join(tempPath, entry.name);
1575
- cpSync(from, to, { recursive: true, force: true });
1576
- if (entry.isDirectory()) ids.push(entry.name);
1577
- hasExtensions = true;
1578
- }
1579
- rmSync(source, { recursive: true, force: true });
1580
- }
1581
- return { tempPath: hasExtensions ? tempPath : "", ids: uniqueSorted(ids) };
1582
- }
1583
-
1584
- function restoreInstalledExtensions(target, tempPath) {
1585
- if (!tempPath) return;
1586
- const destinationRoot = join(target, "extend");
1587
- mkdirSync(destinationRoot, { recursive: true });
1588
- for (const entry of readdirSync(tempPath, { withFileTypes: true })) {
1589
- cpSync(join(tempPath, entry.name), join(destinationRoot, entry.name), { recursive: true, force: true });
1590
- }
1591
- }
1592
-
1593
- function cleanupPreservedExtensions(paths) {
1594
- for (const path of uniqueSorted(paths.filter(Boolean))) {
1595
- rmSync(path, { recursive: true, force: true });
1596
- }
1597
- }
1598
-
1599
- function uniqueSorted(values) {
1600
- return [...new Set(values)].sort();
1601
- }
1602
-
1603
1077
  function installFingerprintExcludes() {
1604
- return new Set(["extend", ".goalbuddy-install.json", ".goal-maker-install.json"]);
1078
+ return new Set([".goalbuddy-install.json", ".goal-maker-install.json"]);
1605
1079
  }
1606
1080
 
1607
1081
  function installMetadataPath(target) {
@@ -1633,53 +1107,6 @@ function writeInstallMetadata(target, previousMetadata) {
1633
1107
  }, null, 2)}\n`);
1634
1108
  }
1635
1109
 
1636
- async function extensionDiscoverySummary() {
1637
- try {
1638
- const catalog = await loadCatalog();
1639
- const extensions = catalog.extensions.map(extensionWithLocalState);
1640
- return {
1641
- catalog_url: catalog.url,
1642
- catalog_version: catalog.version || null,
1643
- available_count: extensions.length,
1644
- installed_count: extensions.filter((extension) => extension.state.installed).length,
1645
- available: extensions.map((extension) => ({
1646
- id: extension.id,
1647
- name: extension.name,
1648
- kind: extension.kind,
1649
- version: extension.version,
1650
- summary: extension.summary,
1651
- activation: extension.activation,
1652
- safe_by_default: extension.safe_by_default,
1653
- installed: extension.state.installed,
1654
- configured: extension.state.configured,
1655
- use_when: extension.use_when,
1656
- next_command: `${canonicalCliName} extend ${extension.id}`,
1657
- })),
1658
- recommended: extensions
1659
- .filter((extension) => extension.safe_by_default && !extension.state.installed)
1660
- .map((extension) => ({
1661
- id: extension.id,
1662
- name: extension.name,
1663
- kind: extension.kind,
1664
- activation: extension.activation,
1665
- summary: extension.summary,
1666
- use_when: extension.use_when.slice(0, 1),
1667
- next_command: `${canonicalCliName} extend ${extension.id}`,
1668
- })),
1669
- };
1670
- } catch (error) {
1671
- return {
1672
- catalog_url: catalogUrl(),
1673
- catalog_version: null,
1674
- available_count: 0,
1675
- installed_count: 0,
1676
- available: [],
1677
- recommended: [],
1678
- error: error.message,
1679
- };
1680
- }
1681
- }
1682
-
1683
1110
  function printInstallReport(report) {
1684
1111
  const verb = report.command === "update" ? "Updated" : "Installed";
1685
1112
  const previous = report.package.previous_version && report.package.previous_version !== report.package.current_version
@@ -1692,31 +1119,11 @@ function printInstallReport(report) {
1692
1119
  console.log(`Compatibility skill: ${report.skill.compatibility_path}`);
1693
1120
  const agentSummary = summarizeStatuses(report.agents);
1694
1121
  console.log(`Agents: ${agentSummary}`);
1695
- if (report.skill.preserved_extensions.length) {
1696
- console.log(`Preserved extensions: ${report.skill.preserved_extensions.join(", ")}`);
1697
- }
1698
-
1699
- if (report.extensions.error) {
1700
- console.log("");
1701
- console.log(`Extensions: unavailable (${report.extensions.error})`);
1702
- } else {
1703
- console.log("");
1704
- console.log(`Extensions: ${report.extensions.available_count} available from ${report.extensions.catalog_url}`);
1705
- if (report.extensions.recommended.length) {
1706
- console.log("");
1707
- console.log("Recommended:");
1708
- for (const extension of report.extensions.recommended.slice(0, 3)) {
1709
- console.log(` ${extension.name || extension.id}`);
1710
- if (extension.summary) console.log(` ${extension.summary}`);
1711
- console.log(` Details: npx ${extension.next_command}`);
1712
- }
1713
- }
1714
- }
1715
1122
 
1716
1123
  console.log("");
1717
1124
  console.log("Next:");
1718
1125
  console.log(` $${canonicalSkillName}`);
1719
- console.log(` ${canonicalCliName} extend`);
1126
+ console.log(` ${canonicalCliName} board docs/goals/<slug>`);
1720
1127
  console.log(` ${legacyCliName} remains a temporary compatibility alias.`);
1721
1128
  }
1722
1129
 
@@ -1730,9 +1137,6 @@ function printEverywhereInstallReport(report) {
1730
1137
  console.log(`Codex: not completed (${report.codex.error})`);
1731
1138
  } else if (report.codex) {
1732
1139
  console.log(`Codex: plugin ${report.codex.version} enabled at ${report.codex.cache_path}`);
1733
- if (report.codex.preserved_extensions?.length) {
1734
- console.log(`Codex preserved extensions: ${report.codex.preserved_extensions.join(", ")}`);
1735
- }
1736
1140
  }
1737
1141
 
1738
1142
  if (report.claude?.ok === false) {
@@ -1767,12 +1171,6 @@ function summarizeStatuses(items) {
1767
1171
  .join(", ");
1768
1172
  }
1769
1173
 
1770
- function assertSkillInstalledForExtensionInstall() {
1771
- if (!existsSync(join(activeSkillRoot(), "SKILL.md"))) {
1772
- throw new Error(`${canonicalProductName} skill is not installed. Run: npx ${canonicalCliName}`);
1773
- }
1774
- }
1775
-
1776
1174
  function latestPublishedVersion() {
1777
1175
  if (process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION) {
1778
1176
  return normalizeVersion(process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION);
@@ -1818,106 +1216,6 @@ function compareVersions(left, right) {
1818
1216
  return left.localeCompare(right);
1819
1217
  }
1820
1218
 
1821
- function installedExtensions() {
1822
- const root = extendRoot();
1823
- if (!existsSync(root)) return [];
1824
- return readdirSync(root, { withFileTypes: true })
1825
- .filter((entry) => entry.isDirectory())
1826
- .map((entry) => readInstalledExtension(join(root, entry.name)))
1827
- .filter(Boolean);
1828
- }
1829
-
1830
- function readInstalledExtension(path) {
1831
- const installedPath = join(path, ".installed.json");
1832
- if (!existsSync(installedPath)) {
1833
- return { id: basename(path), path, issues: ["missing .installed.json"] };
1834
- }
1835
- const data = JSON.parse(readFileSync(installedPath, "utf8"));
1836
- return {
1837
- ...data,
1838
- path,
1839
- };
1840
- }
1841
-
1842
- function doctorInstalledExtension(extension) {
1843
- const issues = [];
1844
- for (const file of extension.files || []) {
1845
- const path = join(extension.path, safeRelativePath(file.path));
1846
- if (!existsSync(path)) {
1847
- issues.push(`missing file: ${file.path}`);
1848
- continue;
1849
- }
1850
- const actualSha = sha256(readFileSync(path));
1851
- if (actualSha !== file.sha256) {
1852
- issues.push(`checksum mismatch: ${file.path}`);
1853
- }
1854
- }
1855
- for (const envName of extension.manifest?.auth?.env || []) {
1856
- if (!process.env[envName]) issues.push(`missing env: ${envName}`);
1857
- }
1858
-
1859
- return {
1860
- id: extension.id,
1861
- version: extension.version || "",
1862
- kind: extension.kind || "",
1863
- path: extension.path,
1864
- ok: issues.length === 0,
1865
- issues,
1866
- };
1867
- }
1868
-
1869
- function publicExtension(extension) {
1870
- return {
1871
- id: extension.id,
1872
- name: extension.name || "",
1873
- kind: extension.kind || "",
1874
- version: extension.version || "",
1875
- summary: extension.summary || "",
1876
- description: extension.description || "",
1877
- local_use_prompt: extension.local_use_prompt || "",
1878
- source: extension.source || "",
1879
- docs: extension.docs || "",
1880
- use_when: extension.use_when || [],
1881
- activation: extension.activation || "",
1882
- outputs: extension.outputs || [],
1883
- requires_approval: extension.requires_approval || false,
1884
- safe_by_default: extension.safe_by_default || false,
1885
- applies_to: extension.applies_to || {},
1886
- reads: extension.reads || [],
1887
- writes: extension.writes || [],
1888
- side_effects: extension.side_effects || [],
1889
- agent_instructions: extension.agent_instructions || [],
1890
- auth: extension.auth || { env: [] },
1891
- supports: extension.supports || {},
1892
- source_of_truth: extension.source_of_truth || "local",
1893
- files: (extension.files || []).map((file) => ({
1894
- path: file.path,
1895
- sha256: file.sha256,
1896
- })),
1897
- };
1898
- }
1899
-
1900
- function extensionWithLocalState(extension) {
1901
- return {
1902
- ...publicExtension(extension),
1903
- state: {
1904
- available: true,
1905
- installed: existsSync(extensionTarget(extension.id)),
1906
- enabled: false,
1907
- configured: configuredFor(extension),
1908
- missing_env: missingEnv(extension),
1909
- },
1910
- };
1911
- }
1912
-
1913
- function configuredFor(extension) {
1914
- return missingEnv(extension).length === 0;
1915
- }
1916
-
1917
- function missingEnv(extension) {
1918
- return (extension.auth?.env || []).filter((envName) => !process.env[envName]);
1919
- }
1920
-
1921
1219
  function printJson(value) {
1922
1220
  console.log(JSON.stringify(value, null, 2));
1923
1221
  }