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.
- package/README.md +17 -8
- package/RELEASE-0.3.5.md +4 -4
- package/RELEASE-0.3.7.md +127 -0
- package/goalbuddy/SKILL.md +19 -10
- package/goalbuddy/scripts/check-goal-state.mjs +53 -0
- package/goalbuddy/scripts/render-task-prompt.mjs +22 -1
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/README.md +7 -9
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/examples/sample-goal/state.yaml +5 -5
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/examples/subgoal-parent/state.yaml +3 -3
- package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +3 -3
- package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/lib/goal-board.mjs +2 -2
- package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +4 -4
- package/goalbuddy/{extend → surfaces}/local-goal-board/test/local-goal-board.test.mjs +8 -8
- package/goalbuddy/templates/goal.md +9 -0
- package/goalbuddy/templates/state.yaml +7 -6
- package/internal/assets/goalbuddy-v0.3.7-release.png +0 -0
- package/internal/cli/goal-maker.mjs +9 -711
- package/package.json +4 -4
- package/plugins/goalbuddy/.claude-plugin/plugin.json +3 -4
- package/plugins/goalbuddy/.codex-plugin/plugin.json +5 -6
- package/plugins/goalbuddy/README.md +4 -3
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +19 -10
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +53 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +22 -1
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/README.md +7 -9
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/examples/sample-goal/state.yaml +5 -5
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/examples/subgoal-parent/state.yaml +3 -3
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/state.yaml +3 -3
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/scripts/lib/goal-board.mjs +2 -2
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +4 -4
- package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/test/local-goal-board.test.mjs +8 -8
- package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +9 -0
- package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +7 -6
- package/examples/extend-catalog-workflow/goal.md +0 -53
- package/examples/extend-catalog-workflow/notes/T001-extension-model-map.md +0 -47
- package/examples/extend-catalog-workflow/notes/T002-architecture-decision.md +0 -48
- package/examples/extend-catalog-workflow/notes/T003-implementation-summary.md +0 -43
- package/examples/extend-catalog-workflow/notes/T004-root-extend-folder.md +0 -24
- package/examples/extend-catalog-workflow/notes/T005-layout-cleanup.md +0 -46
- package/examples/extend-catalog-workflow/notes/T006-catalog-location.md +0 -50
- package/examples/extend-catalog-workflow/notes/T999-completion-audit.md +0 -36
- package/examples/extend-catalog-workflow/state.yaml +0 -327
- package/examples/github-pr-workflow-extension/pr-handoff.md +0 -46
- package/goalbuddy/extend/github-projects/README.md +0 -105
- package/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +0 -63
- package/goalbuddy/extend/github-projects/extension.yaml +0 -43
- package/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +0 -728
- package/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +0 -362
- package/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +0 -193
- package/goalbuddy/extend/github-projects/test/github-projects.test.mjs +0 -267
- package/goalbuddy/extend/local-goal-board/extension.yaml +0 -39
- package/internal/assets/extend-release.png +0 -0
- package/internal/assets/extend-release.svg +0 -83
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/README.md +0 -105
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +0 -63
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/extension.yaml +0 -43
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +0 -728
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +0 -362
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +0 -193
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/test/github-projects.test.mjs +0 -267
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/extension.yaml +0 -39
- /package/goalbuddy/{extend → surfaces}/local-goal-board/assets/goalbuddy-mark.png +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/sample-goal/notes/T001-scout.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/goal.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/notes/.gitkeep +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +0 -0
- /package/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/notes/.gitkeep +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/assets/goalbuddy-mark.png +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/sample-goal/notes/T001-scout.md +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/goal.md +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/notes/.gitkeep +0 -0
- /package/plugins/goalbuddy/skills/goalbuddy/{extend → surfaces}/local-goal-board/examples/subgoal-parent/subgoals/T004-board-view/goal.md +0 -0
- /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,
|
|
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}
|
|
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("
|
|
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 =
|
|
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
|
-
|
|
1065
|
-
const
|
|
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(`
|
|
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(["
|
|
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}
|
|
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
|
}
|