goalbuddy 0.3.5 → 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 +46 -12
- package/RELEASE-0.3.5.md +4 -4
- package/RELEASE-0.3.7.md +127 -0
- package/goalbuddy/SKILL.md +53 -23
- package/goalbuddy/agents/README.md +1 -1
- package/goalbuddy/agents/goal_judge.toml +8 -4
- package/goalbuddy/agents/goal_worker.toml +8 -5
- package/goalbuddy/scripts/check-goal-state.mjs +129 -0
- package/goalbuddy/scripts/render-task-prompt.mjs +83 -5
- 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 +250 -9
- 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 +67 -9
- package/goalbuddy/templates/agents.md +3 -2
- package/goalbuddy/templates/goal.md +27 -4
- package/goalbuddy/templates/state.yaml +13 -7
- package/internal/assets/goalbuddy-v0.3.7-release.png +0 -0
- package/internal/cli/goal-maker.mjs +112 -714
- 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/agents/goal-judge.md +8 -4
- package/plugins/goalbuddy/agents/goal-worker.md +6 -4
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +53 -23
- package/plugins/goalbuddy/skills/goalbuddy/agents/README.md +1 -1
- package/plugins/goalbuddy/skills/goalbuddy/agents/goal_judge.toml +8 -4
- package/plugins/goalbuddy/skills/goalbuddy/agents/goal_worker.toml +8 -5
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +129 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +83 -5
- 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 +250 -9
- 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 +67 -9
- package/plugins/goalbuddy/skills/goalbuddy/templates/agents.md +3 -2
- package/plugins/goalbuddy/skills/goalbuddy/templates/goal.md +27 -4
- package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +13 -7
- 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",
|
|
@@ -83,15 +77,23 @@ async function main() {
|
|
|
83
77
|
break;
|
|
84
78
|
case "install":
|
|
85
79
|
case "update":
|
|
80
|
+
if (wantsHelp()) {
|
|
81
|
+
usage();
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
86
84
|
if (installTargetMode() === "all") {
|
|
87
85
|
await installEverywhere();
|
|
88
86
|
} else if (installTargetMode() === "codex") {
|
|
89
|
-
|
|
87
|
+
installPlugin();
|
|
90
88
|
} else {
|
|
91
89
|
await installClaudeAll();
|
|
92
90
|
}
|
|
93
91
|
break;
|
|
94
92
|
case "agents":
|
|
93
|
+
if (wantsHelp()) {
|
|
94
|
+
usage();
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
95
97
|
if (targetMode() === "codex") {
|
|
96
98
|
installAgents();
|
|
97
99
|
} else {
|
|
@@ -99,6 +101,10 @@ async function main() {
|
|
|
99
101
|
}
|
|
100
102
|
break;
|
|
101
103
|
case "doctor":
|
|
104
|
+
if (wantsHelp()) {
|
|
105
|
+
usage();
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
102
108
|
if (targetMode() === "codex") {
|
|
103
109
|
doctor();
|
|
104
110
|
} else {
|
|
@@ -110,11 +116,12 @@ async function main() {
|
|
|
110
116
|
checkUpdate();
|
|
111
117
|
break;
|
|
112
118
|
case "plugin":
|
|
119
|
+
if (wantsHelp()) {
|
|
120
|
+
pluginUsage();
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
113
123
|
plugin();
|
|
114
124
|
break;
|
|
115
|
-
case "extend":
|
|
116
|
-
await extend();
|
|
117
|
-
break;
|
|
118
125
|
case "board":
|
|
119
126
|
await board();
|
|
120
127
|
break;
|
|
@@ -164,6 +171,10 @@ function hasFlag(name) {
|
|
|
164
171
|
return args.includes(name);
|
|
165
172
|
}
|
|
166
173
|
|
|
174
|
+
function wantsHelp() {
|
|
175
|
+
return hasFlag("--help") || hasFlag("-h");
|
|
176
|
+
}
|
|
177
|
+
|
|
167
178
|
function positional(index) {
|
|
168
179
|
return positionalArgs()[index] || "";
|
|
169
180
|
}
|
|
@@ -193,12 +204,7 @@ Usage:
|
|
|
193
204
|
${canonicalCliName} agents [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--force]
|
|
194
205
|
${canonicalCliName} doctor [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--goal-ready]
|
|
195
206
|
${canonicalCliName} check-update [--json]
|
|
196
|
-
${canonicalCliName}
|
|
197
|
-
${canonicalCliName} extend <id> [--catalog-url <url-or-path>] [--json]
|
|
198
|
-
${canonicalCliName} extend install <id> [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
|
|
199
|
-
${canonicalCliName} extend install --all [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
|
|
200
|
-
${canonicalCliName} extend doctor [<id>] [--codex-home <path>] [--json]
|
|
201
|
-
${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]
|
|
202
208
|
${canonicalCliName} prompt <docs/goals/slug> [--task T###] [--board <path/to/state.yaml>] [--json]
|
|
203
209
|
${canonicalCliName} parallel-plan <docs/goals/slug> [--json]
|
|
204
210
|
|
|
@@ -215,8 +221,6 @@ Compatibility:
|
|
|
215
221
|
Environment:
|
|
216
222
|
CODEX_HOME Overrides the default ~/.codex target.
|
|
217
223
|
CLAUDE_HOME Overrides the default ~/.claude target (and selects Claude Code unless --target codex is set).
|
|
218
|
-
GOALBUDDY_EXTEND_CATALOG_URL Overrides the default GitHub-hosted extension catalog.
|
|
219
|
-
GOAL_MAKER_EXTEND_CATALOG_URL Legacy fallback for the extension catalog.
|
|
220
224
|
`);
|
|
221
225
|
}
|
|
222
226
|
|
|
@@ -269,16 +273,11 @@ function installClaudeSkill({ quiet = false } = {}) {
|
|
|
269
273
|
|
|
270
274
|
const previousMetadata = readInstallMetadata(target);
|
|
271
275
|
const previousFingerprint = existsSync(target) ? directoryFingerprint(target, { exclude: installFingerprintExcludes() }) : "";
|
|
272
|
-
const preservedExtensions = preserveInstalledExtensions([target], { tempRoot: claudeHome() });
|
|
273
|
-
const extensionTempPath = preservedExtensions.tempPath;
|
|
274
|
-
const preservedExtensionIds = preservedExtensions.ids;
|
|
275
276
|
|
|
276
277
|
mkdirSync(dirname(target), { recursive: true });
|
|
277
278
|
rmSync(target, { recursive: true, force: true });
|
|
278
279
|
cpSync(skillSource, target, { recursive: true });
|
|
279
|
-
restoreInstalledExtensions(target, extensionTempPath);
|
|
280
280
|
writeInstallMetadata(target, previousMetadata);
|
|
281
|
-
cleanupPreservedExtensions([extensionTempPath]);
|
|
282
281
|
|
|
283
282
|
const currentFingerprint = directoryFingerprint(target, { exclude: installFingerprintExcludes() });
|
|
284
283
|
const status = previousFingerprint
|
|
@@ -291,7 +290,6 @@ function installClaudeSkill({ quiet = false } = {}) {
|
|
|
291
290
|
path: target,
|
|
292
291
|
previous_version: previousMetadata?.package_version || "",
|
|
293
292
|
current_version: packageInfo.version,
|
|
294
|
-
preserved_extensions: preservedExtensionIds,
|
|
295
293
|
};
|
|
296
294
|
}
|
|
297
295
|
|
|
@@ -342,7 +340,6 @@ async function buildClaudeInstallReport() {
|
|
|
342
340
|
skill: installClaudeSkill({ quiet }),
|
|
343
341
|
agents: installClaudeAgents({ quiet }),
|
|
344
342
|
legacy_commands_cleanup: cleanupLegacyClaudeCommands({ quiet }),
|
|
345
|
-
extensions: await extensionDiscoverySummary(),
|
|
346
343
|
warnings: [],
|
|
347
344
|
};
|
|
348
345
|
|
|
@@ -443,27 +440,6 @@ function printClaudeInstallReport(report) {
|
|
|
443
440
|
if (report.legacy_commands_cleanup?.removed) {
|
|
444
441
|
console.log(`Removed legacy command: ${report.legacy_commands_cleanup.path}`);
|
|
445
442
|
}
|
|
446
|
-
if (report.skill.preserved_extensions.length) {
|
|
447
|
-
console.log(`Preserved extensions: ${report.skill.preserved_extensions.join(", ")}`);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (report.extensions?.error) {
|
|
451
|
-
console.log("");
|
|
452
|
-
console.log(`Extensions: unavailable (${report.extensions.error})`);
|
|
453
|
-
} else if (report.extensions) {
|
|
454
|
-
console.log("");
|
|
455
|
-
console.log(`Extensions: ${report.extensions.available_count} available from ${report.extensions.catalog_url}`);
|
|
456
|
-
if (report.extensions.recommended?.length) {
|
|
457
|
-
console.log("");
|
|
458
|
-
console.log("Recommended:");
|
|
459
|
-
for (const extension of report.extensions.recommended.slice(0, 3)) {
|
|
460
|
-
console.log(` ${extension.name || extension.id}`);
|
|
461
|
-
if (extension.summary) console.log(` ${extension.summary}`);
|
|
462
|
-
console.log(` Details: npx ${extension.next_command}`);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
443
|
console.log("");
|
|
468
444
|
console.log("Next:");
|
|
469
445
|
console.log(` Restart Claude Code, then run: /goal-prep`);
|
|
@@ -483,9 +459,6 @@ function installSkill({ force = true, quiet = false } = {}) {
|
|
|
483
459
|
|
|
484
460
|
const previousMetadata = readInstallMetadata(target) || readInstallMetadata(legacyTarget);
|
|
485
461
|
const previousFingerprint = existsSync(target) ? directoryFingerprint(target, { exclude: installFingerprintExcludes() }) : "";
|
|
486
|
-
const preservedExtensions = preserveInstalledExtensions([target, legacyTarget], { tempRoot: codexHome() });
|
|
487
|
-
const extensionTempPath = preservedExtensions.tempPath;
|
|
488
|
-
const preservedExtensionIds = preservedExtensions.ids;
|
|
489
462
|
|
|
490
463
|
mkdirSync(dirname(target), { recursive: true });
|
|
491
464
|
if (existsSync(target)) {
|
|
@@ -500,16 +473,13 @@ function installSkill({ force = true, quiet = false } = {}) {
|
|
|
500
473
|
cpSync(skillSource, target, {
|
|
501
474
|
recursive: true,
|
|
502
475
|
});
|
|
503
|
-
restoreInstalledExtensions(target, extensionTempPath);
|
|
504
476
|
writeInstallMetadata(target, previousMetadata);
|
|
505
477
|
|
|
506
478
|
mkdirSync(dirname(legacyTarget), { recursive: true });
|
|
507
479
|
rmSync(legacyTarget, { recursive: true, force: true });
|
|
508
480
|
mkdirSync(legacyTarget, { recursive: true });
|
|
509
481
|
writeFileSync(join(legacyTarget, "SKILL.md"), compatibilitySkillBody());
|
|
510
|
-
restoreInstalledExtensions(legacyTarget, extensionTempPath);
|
|
511
482
|
writeInstallMetadata(legacyTarget, previousMetadata);
|
|
512
|
-
cleanupPreservedExtensions([extensionTempPath]);
|
|
513
483
|
|
|
514
484
|
const currentFingerprint = directoryFingerprint(target, { exclude: installFingerprintExcludes() });
|
|
515
485
|
const status = previousFingerprint
|
|
@@ -523,7 +493,6 @@ function installSkill({ force = true, quiet = false } = {}) {
|
|
|
523
493
|
compatibility_path: legacyTarget,
|
|
524
494
|
previous_version: previousMetadata?.package_version || "",
|
|
525
495
|
current_version: packageInfo.version,
|
|
526
|
-
preserved_extensions: preservedExtensionIds,
|
|
527
496
|
};
|
|
528
497
|
}
|
|
529
498
|
|
|
@@ -552,7 +521,7 @@ This alias has the same invocation boundary as \`$${canonicalSkillName}\`: prepa
|
|
|
552
521
|
function installAgents({ quiet = false } = {}) {
|
|
553
522
|
const source = join(skillSource, "agents");
|
|
554
523
|
const target = join(codexHome(), "agents");
|
|
555
|
-
const force = hasFlag("--force") || command === "update" || command === "install";
|
|
524
|
+
const force = hasFlag("--force") || command === "update" || command === "install" || command === "default" || command === "plugin";
|
|
556
525
|
mkdirSync(target, { recursive: true });
|
|
557
526
|
|
|
558
527
|
const results = [];
|
|
@@ -585,7 +554,6 @@ async function installAll() {
|
|
|
585
554
|
codex_home: codexHome(),
|
|
586
555
|
skill: installSkill({ force: true, quiet }),
|
|
587
556
|
agents: installAgents({ quiet }),
|
|
588
|
-
extensions: await extensionDiscoverySummary(),
|
|
589
557
|
warnings: [],
|
|
590
558
|
};
|
|
591
559
|
|
|
@@ -601,6 +569,7 @@ async function installAll() {
|
|
|
601
569
|
function doctor() {
|
|
602
570
|
const skillPath = join(installedSkillRoot(), "SKILL.md");
|
|
603
571
|
const legacySkillPath = join(legacyInstalledSkillRoot(), "SKILL.md");
|
|
572
|
+
const plugin = installedCodexPlugin();
|
|
604
573
|
const agentsPath = join(codexHome(), "agents");
|
|
605
574
|
const installed = existsSync(skillPath);
|
|
606
575
|
const legacyInstalled = existsSync(legacySkillPath);
|
|
@@ -616,12 +585,38 @@ function doctor() {
|
|
|
616
585
|
});
|
|
617
586
|
const goalRuntime = codexGoalRuntimeStatus();
|
|
618
587
|
const warnings = [];
|
|
588
|
+
const errors = [];
|
|
619
589
|
if (!goalRuntime.ready) {
|
|
620
590
|
warnings.push("native Codex /goal runtime is not ready; run `codex login` and `codex features enable goals` before using /goal.");
|
|
621
591
|
}
|
|
592
|
+
if (!plugin.skill_installed && !installed) {
|
|
593
|
+
errors.push("Codex GoalBuddy plugin is not installed; run `npx goalbuddy --target codex`.");
|
|
594
|
+
}
|
|
595
|
+
if (plugin.skill_installed && !plugin.enabled) {
|
|
596
|
+
errors.push("Codex GoalBuddy plugin cache exists but is not enabled in config.toml; run `npx goalbuddy --target codex`.");
|
|
597
|
+
}
|
|
598
|
+
for (const file of missingAgents) {
|
|
599
|
+
errors.push(`Missing GoalBuddy Codex agent: ${file}; run \`npx goalbuddy --target codex\`.`);
|
|
600
|
+
}
|
|
601
|
+
for (const file of staleAgents) {
|
|
602
|
+
errors.push(`Stale GoalBuddy Codex agent: ${file}; run \`npx goalbuddy update --target codex\`.`);
|
|
603
|
+
}
|
|
604
|
+
if (hasFlag("--goal-ready") && !goalRuntime.ready) {
|
|
605
|
+
errors.push("Native Codex /goal runtime is not ready. GoalBuddy $goal-prep and local boards are separate from OpenAI-gated native /goal.");
|
|
606
|
+
}
|
|
622
607
|
|
|
623
608
|
console.log(JSON.stringify({
|
|
624
609
|
codex_home: codexHome(),
|
|
610
|
+
codex_install_model: "plugin",
|
|
611
|
+
expected_state: {
|
|
612
|
+
plugin_cache: true,
|
|
613
|
+
bundled_skill: "$goal-prep",
|
|
614
|
+
standalone_personal_skill: false,
|
|
615
|
+
compatibility_skill: false,
|
|
616
|
+
agents: requiredAgentFiles,
|
|
617
|
+
native_goal: "separate OpenAI-gated Codex feature",
|
|
618
|
+
},
|
|
619
|
+
plugin,
|
|
625
620
|
skill_installed: installed,
|
|
626
621
|
skill_path: skillPath,
|
|
627
622
|
compatibility_skill_installed: legacyInstalled,
|
|
@@ -631,11 +626,14 @@ function doctor() {
|
|
|
631
626
|
stale_agents: staleAgents,
|
|
632
627
|
goal_runtime: goalRuntime,
|
|
633
628
|
warnings,
|
|
629
|
+
errors,
|
|
634
630
|
}, null, 2));
|
|
635
631
|
|
|
636
|
-
const
|
|
632
|
+
const pluginOk = plugin.skill_installed && plugin.enabled;
|
|
633
|
+
const legacySkillOk = installed;
|
|
634
|
+
const installOk = (pluginOk || legacySkillOk) && missingAgents.length === 0 && staleAgents.length === 0;
|
|
637
635
|
const goalReadyOk = !hasFlag("--goal-ready") || goalRuntime.ready;
|
|
638
|
-
process.exit(installOk && goalReadyOk ? 0 : 1);
|
|
636
|
+
process.exit(installOk && goalReadyOk && errors.length === 0 ? 0 : 1);
|
|
639
637
|
}
|
|
640
638
|
|
|
641
639
|
function checkUpdate() {
|
|
@@ -680,6 +678,10 @@ function updateReport() {
|
|
|
680
678
|
|
|
681
679
|
function plugin() {
|
|
682
680
|
const subcommand = positional(1) || "";
|
|
681
|
+
if (wantsHelp()) {
|
|
682
|
+
pluginUsage();
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
683
685
|
switch (subcommand) {
|
|
684
686
|
case "install":
|
|
685
687
|
installPlugin();
|
|
@@ -717,22 +719,17 @@ function installPlugin({ quiet = false } = {}) {
|
|
|
717
719
|
|
|
718
720
|
const pluginManifest = JSON.parse(readFileSync(pluginManifestPath, "utf8"));
|
|
719
721
|
const pluginCachePath = pluginCacheRoot(pluginManifest.version);
|
|
720
|
-
const pluginSkillPath = join(pluginCachePath, "skills", canonicalSkillDirectory);
|
|
721
722
|
const marketplace = runCodex(["plugin", "marketplace", "add", source]);
|
|
722
723
|
if (!marketplace.ok) {
|
|
723
724
|
throw new Error(`Failed to add Codex plugin marketplace: ${firstLine(marketplace.stderr || marketplace.stdout)}`);
|
|
724
725
|
}
|
|
725
726
|
|
|
726
|
-
const legacySkillPaths = legacyCodexSkillRoots();
|
|
727
|
-
const existingPluginSkillPath = installedPluginSkillRoot();
|
|
728
|
-
const preservedExtensions = preserveInstalledExtensions([existingPluginSkillPath, ...legacySkillPaths], { tempRoot: dirname(pluginCachePath) });
|
|
729
727
|
mkdirSync(dirname(pluginCachePath), { recursive: true });
|
|
730
728
|
rmSync(pluginCachePath, { recursive: true, force: true });
|
|
731
729
|
cpSync(pluginSource, pluginCachePath, { recursive: true });
|
|
732
|
-
restoreInstalledExtensions(pluginSkillPath, preservedExtensions.tempPath);
|
|
733
|
-
cleanupPreservedExtensions([preservedExtensions.tempPath]);
|
|
734
730
|
const removedLegacySkillPaths = cleanupLegacyCodexSkills();
|
|
735
731
|
const configPath = enablePluginConfig();
|
|
732
|
+
const agents = installAgents({ quiet: true });
|
|
736
733
|
|
|
737
734
|
const report = {
|
|
738
735
|
installed: true,
|
|
@@ -743,7 +740,7 @@ function installPlugin({ quiet = false } = {}) {
|
|
|
743
740
|
marketplace_source: source,
|
|
744
741
|
cache_path: pluginCachePath,
|
|
745
742
|
config_path: configPath,
|
|
746
|
-
|
|
743
|
+
agents,
|
|
747
744
|
removed_legacy_skill_paths: removedLegacySkillPaths,
|
|
748
745
|
};
|
|
749
746
|
|
|
@@ -758,9 +755,7 @@ function installPlugin({ quiet = false } = {}) {
|
|
|
758
755
|
console.log(`Marketplace: ${source}`);
|
|
759
756
|
console.log(`Cache: ${pluginCachePath}`);
|
|
760
757
|
console.log(`Config: ${configPath}`);
|
|
761
|
-
|
|
762
|
-
console.log(`Preserved extensions: ${report.preserved_extensions.join(", ")}`);
|
|
763
|
-
}
|
|
758
|
+
console.log(`Agents: ${summarizeStatuses(report.agents)}`);
|
|
764
759
|
if (report.removed_legacy_skill_paths.length) {
|
|
765
760
|
console.log(`Removed legacy personal skills: ${report.removed_legacy_skill_paths.join(", ")}`);
|
|
766
761
|
}
|
|
@@ -768,9 +763,8 @@ function installPlugin({ quiet = false } = {}) {
|
|
|
768
763
|
console.log("Restart Codex, then use:");
|
|
769
764
|
console.log(` $${canonicalSkillName}`);
|
|
770
765
|
console.log("");
|
|
771
|
-
console.log("
|
|
766
|
+
console.log("Goal surface:");
|
|
772
767
|
console.log(` npx ${canonicalCliName} board docs/goals/<slug>`);
|
|
773
|
-
console.log(` npx ${canonicalCliName} extend github-projects`);
|
|
774
768
|
return report;
|
|
775
769
|
}
|
|
776
770
|
|
|
@@ -912,33 +906,6 @@ function firstLine(value) {
|
|
|
912
906
|
return (value || "").split(/\r?\n/).find((line) => line.trim())?.trim() || "";
|
|
913
907
|
}
|
|
914
908
|
|
|
915
|
-
async function extend() {
|
|
916
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
917
|
-
extendUsage();
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
const subcommand = positional(1) || "";
|
|
922
|
-
switch (subcommand) {
|
|
923
|
-
case "":
|
|
924
|
-
await extendCatalog();
|
|
925
|
-
break;
|
|
926
|
-
case "install":
|
|
927
|
-
await extendInstall();
|
|
928
|
-
break;
|
|
929
|
-
case "doctor":
|
|
930
|
-
extendDoctor();
|
|
931
|
-
break;
|
|
932
|
-
case "help":
|
|
933
|
-
case "--help":
|
|
934
|
-
case "-h":
|
|
935
|
-
extendUsage();
|
|
936
|
-
break;
|
|
937
|
-
default:
|
|
938
|
-
await extendDetails(subcommand);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
909
|
async function board() {
|
|
943
910
|
const goal = optionValue("--goal") || positional(1);
|
|
944
911
|
if (!goal) {
|
|
@@ -946,7 +913,7 @@ async function board() {
|
|
|
946
913
|
process.exit(2);
|
|
947
914
|
}
|
|
948
915
|
|
|
949
|
-
const script =
|
|
916
|
+
const script = ensureLocalBoardSurface();
|
|
950
917
|
const scriptArgs = [script, "--goal", goal];
|
|
951
918
|
for (const option of ["--host", "--port"]) {
|
|
952
919
|
const value = optionValue(option);
|
|
@@ -1004,302 +971,33 @@ async function parallelPlan() {
|
|
|
1004
971
|
process.exit(result.status ?? 1);
|
|
1005
972
|
}
|
|
1006
973
|
|
|
1007
|
-
|
|
1008
|
-
const
|
|
1009
|
-
const script = join(extensionTarget(id), "scripts", "local-goal-board.mjs");
|
|
1010
|
-
if (existsSync(script)) return script;
|
|
1011
|
-
|
|
1012
|
-
const catalog = await loadCatalog();
|
|
1013
|
-
const extension = catalog.extensions.find((candidate) => candidate.id === id);
|
|
1014
|
-
if (!extension) {
|
|
1015
|
-
throw new Error(`Extension ${id} is not available in ${catalog.url}.`);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
await installCatalogExtension(catalog, extension);
|
|
974
|
+
function ensureLocalBoardSurface() {
|
|
975
|
+
const script = join(skillSource, "surfaces", "local-goal-board", "scripts", "local-goal-board.mjs");
|
|
1019
976
|
if (!existsSync(script)) {
|
|
1020
|
-
throw new Error(`
|
|
977
|
+
throw new Error(`Bundled GoalBuddy board surface is missing: ${script}`);
|
|
1021
978
|
}
|
|
1022
979
|
return script;
|
|
1023
980
|
}
|
|
1024
981
|
|
|
1025
|
-
function extendUsage() {
|
|
1026
|
-
console.log(`${canonicalProductName} Extend
|
|
1027
|
-
|
|
1028
|
-
Usage:
|
|
1029
|
-
${canonicalCliName} extend [--catalog-url <url-or-path>] [--kind <kind>] [--json]
|
|
1030
|
-
${canonicalCliName} extend <id> [--catalog-url <url-or-path>] [--json]
|
|
1031
|
-
${canonicalCliName} extend install <id> [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
|
|
1032
|
-
${canonicalCliName} extend install --all [--catalog-url <url-or-path>] [--dry-run] [--force] [--json]
|
|
1033
|
-
${canonicalCliName} extend doctor [<id>] [--codex-home <path>] [--json]
|
|
1034
|
-
|
|
1035
|
-
States:
|
|
1036
|
-
available Listed in the catalog.
|
|
1037
|
-
installed Copied into the local ${canonicalProductName} skill install.
|
|
1038
|
-
enabled Allowed by a goal or task. Not implemented by this command yet.
|
|
1039
|
-
configured Required local env/provider settings are present.
|
|
1040
|
-
|
|
1041
|
-
Catalog:
|
|
1042
|
-
Defaults to ${defaultCatalogUrl}
|
|
1043
|
-
Override with --catalog-url, GOALBUDDY_EXTEND_CATALOG_URL, or legacy GOAL_MAKER_EXTEND_CATALOG_URL.
|
|
1044
|
-
`);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
async function extendCatalog() {
|
|
1048
|
-
const catalog = await loadCatalog();
|
|
1049
|
-
const kind = optionValue("--kind");
|
|
1050
|
-
const extensions = catalog.extensions
|
|
1051
|
-
.filter((extension) => !kind || extension.kind === kind)
|
|
1052
|
-
.map(extensionWithLocalState);
|
|
1053
|
-
|
|
1054
|
-
if (hasFlag("--json")) {
|
|
1055
|
-
printJson({ catalog_url: catalog.url, extensions });
|
|
1056
|
-
return;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
console.log("Available extensions");
|
|
1060
|
-
if (extensions.length === 0) {
|
|
1061
|
-
console.log("");
|
|
1062
|
-
console.log(kind ? `No ${kind} extensions found.` : "No extensions found.");
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
console.log("");
|
|
1066
|
-
for (const extension of extensions) {
|
|
1067
|
-
console.log(extension.name || extension.id);
|
|
1068
|
-
if (extension.summary) console.log(` ${extension.summary}`);
|
|
1069
|
-
console.log(` Details: npx ${canonicalCliName} extend ${extension.id}`);
|
|
1070
|
-
console.log("");
|
|
1071
|
-
}
|
|
1072
|
-
console.log("Install all:");
|
|
1073
|
-
console.log(` npx ${canonicalCliName} extend install --all`);
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
async function extendDetails(id) {
|
|
1077
|
-
const catalog = await loadCatalog();
|
|
1078
|
-
const extension = catalog.extensions.find((candidate) => candidate.id === id);
|
|
1079
|
-
if (!extension) {
|
|
1080
|
-
printExtensionNotFound(id, catalog.extensions);
|
|
1081
|
-
process.exit(1);
|
|
1082
|
-
}
|
|
1083
|
-
validateCatalogExtension(extension);
|
|
1084
|
-
const detailed = extensionWithLocalState(extension);
|
|
1085
|
-
|
|
1086
|
-
if (hasFlag("--json")) {
|
|
1087
|
-
printJson({ catalog_url: catalog.url, extension: detailed });
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
console.log(extension.name || extension.summary || "");
|
|
1092
|
-
console.log("");
|
|
1093
|
-
if (extension.summary && extension.summary !== extension.name) {
|
|
1094
|
-
console.log(extension.summary);
|
|
1095
|
-
console.log("");
|
|
1096
|
-
}
|
|
1097
|
-
console.log(`Status: ${detailed.state.installed ? "installed" : "available"}`);
|
|
1098
|
-
console.log(`Configured: ${detailed.state.configured ? "yes" : "no"}`);
|
|
1099
|
-
console.log(`ID: ${extension.id}`);
|
|
1100
|
-
console.log(`Kind: ${extension.kind}`);
|
|
1101
|
-
if (extension.version) console.log(`Version: ${extension.version}`);
|
|
1102
|
-
console.log(`Activation: ${extension.activation || "unspecified"}`);
|
|
1103
|
-
console.log(`Safe by default: ${extension.safe_by_default ? "yes" : "no"}`);
|
|
1104
|
-
console.log(`Requires approval: ${extension.requires_approval ? "yes" : "no"}`);
|
|
1105
|
-
if (!detailed.state.configured && detailed.state.missing_env.length) {
|
|
1106
|
-
console.log(`Missing env: ${detailed.state.missing_env.join(", ")}`);
|
|
1107
|
-
}
|
|
1108
|
-
printListSection("Use when", extension.use_when);
|
|
1109
|
-
printListSection("Outputs", extension.outputs);
|
|
1110
|
-
printListSection("Reads", extension.reads);
|
|
1111
|
-
printListSection("Writes", extension.writes);
|
|
1112
|
-
printListSection("Side effects", extension.side_effects);
|
|
1113
|
-
printListSection("Agent instructions", extension.agent_instructions);
|
|
1114
|
-
printListSection("Auth env", extension.auth?.env);
|
|
1115
|
-
printSupports(extension.supports);
|
|
1116
|
-
console.log("");
|
|
1117
|
-
console.log("Local use prompt:");
|
|
1118
|
-
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.`}`);
|
|
1119
|
-
console.log("");
|
|
1120
|
-
console.log("Install:");
|
|
1121
|
-
console.log(` npx ${canonicalCliName} extend install ${extension.id}`);
|
|
1122
|
-
console.log("");
|
|
1123
|
-
console.log("Preview install:");
|
|
1124
|
-
console.log(` npx ${canonicalCliName} extend install ${extension.id} --dry-run`);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
function printListSection(title, values) {
|
|
1128
|
-
if (!Array.isArray(values) || values.length === 0) return;
|
|
1129
|
-
console.log("");
|
|
1130
|
-
console.log(`${title}:`);
|
|
1131
|
-
for (const value of values) console.log(` - ${value}`);
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
function printSupports(supports) {
|
|
1135
|
-
if (!supports || typeof supports !== "object") return;
|
|
1136
|
-
const entries = Object.entries(supports);
|
|
1137
|
-
if (entries.length === 0) return;
|
|
1138
|
-
console.log("");
|
|
1139
|
-
console.log("Supports:");
|
|
1140
|
-
for (const [key, value] of entries) console.log(` - ${key}: ${value}`);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
function firstValue(values, fallback) {
|
|
1144
|
-
return Array.isArray(values) && values.length ? values[0] : fallback;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
async function extendInstall() {
|
|
1148
|
-
const id = positional(2);
|
|
1149
|
-
const catalog = await loadCatalog();
|
|
1150
|
-
if (hasFlag("--all")) {
|
|
1151
|
-
await extendInstallAll(catalog);
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
if (!id) throw new Error(`Missing extension id. Usage: ${canonicalCliName} extend install <id>`);
|
|
1156
|
-
const extension = catalog.extensions.find((candidate) => candidate.id === id);
|
|
1157
|
-
if (!extension) {
|
|
1158
|
-
printExtensionNotFound(id, catalog.extensions);
|
|
1159
|
-
process.exit(1);
|
|
1160
|
-
}
|
|
1161
|
-
const result = await installCatalogExtension(catalog, extension);
|
|
1162
|
-
|
|
1163
|
-
if (hasFlag("--dry-run")) {
|
|
1164
|
-
if (hasFlag("--json")) {
|
|
1165
|
-
printJson({ dry_run: true, extension: extensionWithLocalState(extension), target: result.target, files: result.plan });
|
|
1166
|
-
} else {
|
|
1167
|
-
console.log(`Would install ${extension.id} to ${result.target}`);
|
|
1168
|
-
for (const file of result.plan) console.log(` ${file.path}`);
|
|
1169
|
-
}
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
if (hasFlag("--json")) {
|
|
1174
|
-
printJson({ installed: true, extension: extension.id, target: result.target });
|
|
1175
|
-
} else {
|
|
1176
|
-
console.log(`Installed ${extension.id} to ${result.target}`);
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
async function extendInstallAll(catalog) {
|
|
1181
|
-
const results = [];
|
|
1182
|
-
for (const extension of catalog.extensions) {
|
|
1183
|
-
if (existsSync(extensionTarget(extension.id)) && !hasFlag("--force")) {
|
|
1184
|
-
validateCatalogExtension(extension);
|
|
1185
|
-
results.push({ extension, target: extensionTarget(extension.id), plan: installPlan(catalog, extension, extensionTarget(extension.id)), skipped: true });
|
|
1186
|
-
continue;
|
|
1187
|
-
}
|
|
1188
|
-
results.push(await installCatalogExtension(catalog, extension));
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
if (hasFlag("--dry-run")) {
|
|
1192
|
-
if (hasFlag("--json")) {
|
|
1193
|
-
printJson({
|
|
1194
|
-
dry_run: true,
|
|
1195
|
-
extensions: results.map(({ extension, target, plan }) => ({
|
|
1196
|
-
extension: extensionWithLocalState(extension),
|
|
1197
|
-
target,
|
|
1198
|
-
files: plan,
|
|
1199
|
-
})),
|
|
1200
|
-
});
|
|
1201
|
-
} else {
|
|
1202
|
-
console.log(`Would install ${results.length} extensions`);
|
|
1203
|
-
for (const { extension, target, plan } of results) {
|
|
1204
|
-
console.log(`${extension.id} -> ${target}`);
|
|
1205
|
-
for (const file of plan) console.log(` ${file.path}`);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
if (hasFlag("--json")) {
|
|
1212
|
-
printJson({
|
|
1213
|
-
installed: true,
|
|
1214
|
-
count: results.length,
|
|
1215
|
-
extensions: results.map(({ extension, target, skipped }) => ({ id: extension.id, target, skipped: Boolean(skipped) })),
|
|
1216
|
-
});
|
|
1217
|
-
} else {
|
|
1218
|
-
const installedCount = results.filter((result) => !result.skipped).length;
|
|
1219
|
-
const skippedCount = results.length - installedCount;
|
|
1220
|
-
console.log(`Installed ${installedCount} extensions${skippedCount ? `, skipped ${skippedCount} already installed` : ""}`);
|
|
1221
|
-
for (const { extension, target, skipped } of results) console.log(` ${extension.id} -> ${target}${skipped ? " (already installed)" : ""}`);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
async function installCatalogExtension(catalog, extension) {
|
|
1226
|
-
validateCatalogExtension(extension);
|
|
1227
|
-
const target = extensionTarget(extension.id);
|
|
1228
|
-
const plan = installPlan(catalog, extension, target);
|
|
1229
|
-
|
|
1230
|
-
if (hasFlag("--dry-run")) return { extension, target, plan };
|
|
1231
|
-
|
|
1232
|
-
assertSkillInstalledForExtensionInstall();
|
|
1233
|
-
if (existsSync(target) && !hasFlag("--force")) {
|
|
1234
|
-
throw new Error(`Extension already installed: ${target}. Use --force to overwrite.`);
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const temp = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
1238
|
-
rmSync(temp, { recursive: true, force: true });
|
|
1239
|
-
mkdirSync(temp, { recursive: true });
|
|
1240
|
-
|
|
1241
|
-
try {
|
|
1242
|
-
for (const file of plan) {
|
|
1243
|
-
const content = await readResource(file.url);
|
|
1244
|
-
const actualSha = sha256(content);
|
|
1245
|
-
if (actualSha !== file.sha256) {
|
|
1246
|
-
throw new Error(`Checksum mismatch for ${file.path}: expected ${file.sha256}, got ${actualSha}`);
|
|
1247
|
-
}
|
|
1248
|
-
const destination = join(temp, file.path);
|
|
1249
|
-
mkdirSync(dirname(destination), { recursive: true });
|
|
1250
|
-
writeFileSync(destination, content);
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
writeFileSync(join(temp, ".installed.json"), `${JSON.stringify({
|
|
1254
|
-
id: extension.id,
|
|
1255
|
-
version: extension.version || "",
|
|
1256
|
-
kind: extension.kind,
|
|
1257
|
-
catalog_url: catalog.url,
|
|
1258
|
-
installed_at: new Date().toISOString(),
|
|
1259
|
-
manifest: publicExtension(extension),
|
|
1260
|
-
files: plan.map(({ path, sha256: digest }) => ({ path, sha256: digest })),
|
|
1261
|
-
}, null, 2)}\n`);
|
|
1262
|
-
|
|
1263
|
-
rmSync(target, { recursive: true, force: true });
|
|
1264
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
1265
|
-
renameSync(temp, target);
|
|
1266
|
-
} catch (error) {
|
|
1267
|
-
rmSync(temp, { recursive: true, force: true });
|
|
1268
|
-
throw error;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
return { extension, target, plan };
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
function extendDoctor() {
|
|
1275
|
-
const id = positional(2);
|
|
1276
|
-
const targets = installedExtensions().filter((extension) => !id || extension.id === id);
|
|
1277
|
-
if (id && targets.length === 0) throw new Error(`Extension is not installed: ${id}`);
|
|
1278
|
-
|
|
1279
|
-
const reports = targets.map(doctorInstalledExtension);
|
|
1280
|
-
const ok = reports.every((report) => report.ok);
|
|
1281
|
-
|
|
1282
|
-
if (hasFlag("--json")) {
|
|
1283
|
-
printJson({ ok, extensions: reports });
|
|
1284
|
-
} else if (reports.length === 0) {
|
|
1285
|
-
console.log("No extensions installed.");
|
|
1286
|
-
} else {
|
|
1287
|
-
for (const report of reports) {
|
|
1288
|
-
console.log(`${report.ok ? "ok" : "not ok"}\t${report.id}`);
|
|
1289
|
-
for (const issue of report.issues) console.log(` - ${issue}`);
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
process.exit(ok ? 0 : 1);
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
982
|
function installedSkillRoot() {
|
|
1297
983
|
return join(codexHome(), "skills", canonicalSkillDirectory);
|
|
1298
984
|
}
|
|
1299
985
|
|
|
1300
|
-
function
|
|
986
|
+
function installedCodexPlugin() {
|
|
1301
987
|
const root = join(codexHome(), "plugins", "cache", pluginName, pluginName);
|
|
1302
|
-
|
|
988
|
+
const configPath = join(codexHome(), "config.toml");
|
|
989
|
+
const base = {
|
|
990
|
+
installed: false,
|
|
991
|
+
enabled: pluginConfigEnabled(configPath),
|
|
992
|
+
name: `${pluginName}@${pluginName}`,
|
|
993
|
+
version: "",
|
|
994
|
+
cache_path: "",
|
|
995
|
+
manifest_path: "",
|
|
996
|
+
skill_installed: false,
|
|
997
|
+
skill_path: "",
|
|
998
|
+
config_path: configPath,
|
|
999
|
+
};
|
|
1000
|
+
if (!existsSync(root)) return base;
|
|
1303
1001
|
const versions = readdirSync(root, { withFileTypes: true })
|
|
1304
1002
|
.filter((entry) => entry.isDirectory())
|
|
1305
1003
|
.map((entry) => entry.name)
|
|
@@ -1307,123 +1005,41 @@ function installedPluginSkillRoot() {
|
|
|
1307
1005
|
.sort(compareVersions)
|
|
1308
1006
|
.reverse();
|
|
1309
1007
|
for (const version of versions) {
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
function legacyInstalledSkillRoot() {
|
|
1324
|
-
return join(codexHome(), "skills", legacySkillName);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
function extendRoot() {
|
|
1328
|
-
return join(activeSkillRoot(), "extend");
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
function extensionTarget(id) {
|
|
1332
|
-
return join(extendRoot(), id);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
function catalogUrl() {
|
|
1336
|
-
return optionValue("--catalog-url")
|
|
1337
|
-
|| optionValue("--catalog")
|
|
1338
|
-
|| process.env.GOALBUDDY_EXTEND_CATALOG_URL
|
|
1339
|
-
|| process.env.GOAL_MAKER_EXTEND_CATALOG_URL
|
|
1340
|
-
|| defaultCatalogUrl;
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
async function loadCatalog() {
|
|
1344
|
-
const url = catalogUrl();
|
|
1345
|
-
const text = await readResource(url);
|
|
1346
|
-
const catalog = JSON.parse(text);
|
|
1347
|
-
if (!Array.isArray(catalog.extensions)) {
|
|
1348
|
-
throw new Error("Extension catalog must contain an extensions array.");
|
|
1349
|
-
}
|
|
1350
|
-
return { ...catalog, url };
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
function printExtensionNotFound(id, extensions) {
|
|
1354
|
-
console.error(`Extension not found: ${id}`);
|
|
1355
|
-
if (extensions.length) {
|
|
1356
|
-
console.error("");
|
|
1357
|
-
console.error("Available extensions:");
|
|
1358
|
-
for (const extension of extensions) {
|
|
1359
|
-
console.error(` ${extension.id}`);
|
|
1008
|
+
const cachePath = join(root, version);
|
|
1009
|
+
const skillPath = join(cachePath, "skills", canonicalSkillDirectory);
|
|
1010
|
+
const manifestPath = join(cachePath, ".codex-plugin", "plugin.json");
|
|
1011
|
+
if (existsSync(join(skillPath, "SKILL.md"))) {
|
|
1012
|
+
return {
|
|
1013
|
+
...base,
|
|
1014
|
+
installed: true,
|
|
1015
|
+
version,
|
|
1016
|
+
cache_path: cachePath,
|
|
1017
|
+
manifest_path: manifestPath,
|
|
1018
|
+
skill_installed: true,
|
|
1019
|
+
skill_path: skillPath,
|
|
1020
|
+
};
|
|
1360
1021
|
}
|
|
1361
1022
|
}
|
|
1362
|
-
|
|
1363
|
-
console.error("Try:");
|
|
1364
|
-
console.error(` npx ${canonicalCliName} extend`);
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
function validateCatalogExtension(extension) {
|
|
1368
|
-
if (!extension.id || !/^[a-z0-9][a-z0-9-]*$/.test(extension.id)) {
|
|
1369
|
-
throw new Error(`Invalid extension id: ${extension.id || "<missing>"}`);
|
|
1370
|
-
}
|
|
1371
|
-
if (!extension.kind) throw new Error(`Extension ${extension.id} missing kind.`);
|
|
1372
|
-
if (!Array.isArray(extension.files) || extension.files.length === 0) {
|
|
1373
|
-
throw new Error(`Extension ${extension.id} must list files.`);
|
|
1374
|
-
}
|
|
1375
|
-
for (const file of extension.files) {
|
|
1376
|
-
validateCatalogFile(extension, file);
|
|
1377
|
-
}
|
|
1023
|
+
return base;
|
|
1378
1024
|
}
|
|
1379
1025
|
|
|
1380
|
-
function
|
|
1381
|
-
if (!
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
path: safeRelativePath(file.path),
|
|
1392
|
-
url: resolveResourceUrl(catalog.url, file.url),
|
|
1393
|
-
sha256: file.sha256.toLowerCase(),
|
|
1394
|
-
target: join(target, safeRelativePath(file.path)),
|
|
1395
|
-
}));
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
function safeRelativePath(path) {
|
|
1399
|
-
const normalized = normalize(path).replaceAll("\\", "/");
|
|
1400
|
-
if (!normalized || normalized.startsWith("../") || normalized === ".." || normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) {
|
|
1401
|
-
throw new Error(`Unsafe extension file path: ${path}`);
|
|
1026
|
+
function pluginConfigEnabled(configPath) {
|
|
1027
|
+
if (!existsSync(configPath)) return false;
|
|
1028
|
+
const lines = readFileSync(configPath, "utf8").split(/\r?\n/);
|
|
1029
|
+
const header = `[plugins."${pluginName}@${pluginName}"]`;
|
|
1030
|
+
const start = lines.findIndex((line) => line.trim() === header);
|
|
1031
|
+
if (start === -1) return false;
|
|
1032
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
1033
|
+
const line = lines[index].trim();
|
|
1034
|
+
if (line.startsWith("[")) break;
|
|
1035
|
+
if (/^enabled\s*=\s*true\b/.test(line)) return true;
|
|
1036
|
+
if (/^enabled\s*=/.test(line)) return false;
|
|
1402
1037
|
}
|
|
1403
|
-
return
|
|
1038
|
+
return false;
|
|
1404
1039
|
}
|
|
1405
1040
|
|
|
1406
|
-
function
|
|
1407
|
-
|
|
1408
|
-
if (/^https?:\/\//.test(base) || base.startsWith("file://")) {
|
|
1409
|
-
return new URL(value, base).href;
|
|
1410
|
-
}
|
|
1411
|
-
return resolve(dirname(resolve(base)), value);
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
async function readResource(location) {
|
|
1415
|
-
if (/^https?:\/\//.test(location)) {
|
|
1416
|
-
if (!globalThis.fetch) throw new Error("This Node runtime does not provide fetch.");
|
|
1417
|
-
const response = await globalThis.fetch(location, {
|
|
1418
|
-
headers: {
|
|
1419
|
-
"accept-encoding": "identity",
|
|
1420
|
-
},
|
|
1421
|
-
});
|
|
1422
|
-
if (!response.ok) throw new Error(`Failed to fetch ${location}: HTTP ${response.status}`);
|
|
1423
|
-
return Buffer.from(await response.arrayBuffer());
|
|
1424
|
-
}
|
|
1425
|
-
const path = location.startsWith("file://") ? fileURLToPath(location) : resolve(location);
|
|
1426
|
-
return readFileSync(path);
|
|
1041
|
+
function legacyInstalledSkillRoot() {
|
|
1042
|
+
return join(codexHome(), "skills", legacySkillName);
|
|
1427
1043
|
}
|
|
1428
1044
|
|
|
1429
1045
|
function sha256(content) {
|
|
@@ -1458,50 +1074,8 @@ function listFiles(root, { exclude = new Set(), prefix = "" } = {}) {
|
|
|
1458
1074
|
return files;
|
|
1459
1075
|
}
|
|
1460
1076
|
|
|
1461
|
-
function preserveInstalledExtensions(targets, { tempRoot = "" } = {}) {
|
|
1462
|
-
const ids = [];
|
|
1463
|
-
const firstTarget = targets.find(Boolean) || codexHome();
|
|
1464
|
-
const tempPath = join(tempRoot || dirname(dirname(firstTarget)), `.goalbuddy-preserved-extend-${process.pid}-${Date.now()}`);
|
|
1465
|
-
let hasExtensions = false;
|
|
1466
|
-
for (const target of targets) {
|
|
1467
|
-
if (!target) continue;
|
|
1468
|
-
const source = join(target, "extend");
|
|
1469
|
-
if (!existsSync(source)) continue;
|
|
1470
|
-
for (const entry of readdirSync(source, { withFileTypes: true })) {
|
|
1471
|
-
if (bundledCoreExtensionIds.has(entry.name)) continue;
|
|
1472
|
-
mkdirSync(tempPath, { recursive: true });
|
|
1473
|
-
const from = join(source, entry.name);
|
|
1474
|
-
const to = join(tempPath, entry.name);
|
|
1475
|
-
cpSync(from, to, { recursive: true, force: true });
|
|
1476
|
-
if (entry.isDirectory()) ids.push(entry.name);
|
|
1477
|
-
hasExtensions = true;
|
|
1478
|
-
}
|
|
1479
|
-
rmSync(source, { recursive: true, force: true });
|
|
1480
|
-
}
|
|
1481
|
-
return { tempPath: hasExtensions ? tempPath : "", ids: uniqueSorted(ids) };
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
function restoreInstalledExtensions(target, tempPath) {
|
|
1485
|
-
if (!tempPath) return;
|
|
1486
|
-
const destinationRoot = join(target, "extend");
|
|
1487
|
-
mkdirSync(destinationRoot, { recursive: true });
|
|
1488
|
-
for (const entry of readdirSync(tempPath, { withFileTypes: true })) {
|
|
1489
|
-
cpSync(join(tempPath, entry.name), join(destinationRoot, entry.name), { recursive: true, force: true });
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
function cleanupPreservedExtensions(paths) {
|
|
1494
|
-
for (const path of uniqueSorted(paths.filter(Boolean))) {
|
|
1495
|
-
rmSync(path, { recursive: true, force: true });
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
function uniqueSorted(values) {
|
|
1500
|
-
return [...new Set(values)].sort();
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
1077
|
function installFingerprintExcludes() {
|
|
1504
|
-
return new Set(["
|
|
1078
|
+
return new Set([".goalbuddy-install.json", ".goal-maker-install.json"]);
|
|
1505
1079
|
}
|
|
1506
1080
|
|
|
1507
1081
|
function installMetadataPath(target) {
|
|
@@ -1533,53 +1107,6 @@ function writeInstallMetadata(target, previousMetadata) {
|
|
|
1533
1107
|
}, null, 2)}\n`);
|
|
1534
1108
|
}
|
|
1535
1109
|
|
|
1536
|
-
async function extensionDiscoverySummary() {
|
|
1537
|
-
try {
|
|
1538
|
-
const catalog = await loadCatalog();
|
|
1539
|
-
const extensions = catalog.extensions.map(extensionWithLocalState);
|
|
1540
|
-
return {
|
|
1541
|
-
catalog_url: catalog.url,
|
|
1542
|
-
catalog_version: catalog.version || null,
|
|
1543
|
-
available_count: extensions.length,
|
|
1544
|
-
installed_count: extensions.filter((extension) => extension.state.installed).length,
|
|
1545
|
-
available: extensions.map((extension) => ({
|
|
1546
|
-
id: extension.id,
|
|
1547
|
-
name: extension.name,
|
|
1548
|
-
kind: extension.kind,
|
|
1549
|
-
version: extension.version,
|
|
1550
|
-
summary: extension.summary,
|
|
1551
|
-
activation: extension.activation,
|
|
1552
|
-
safe_by_default: extension.safe_by_default,
|
|
1553
|
-
installed: extension.state.installed,
|
|
1554
|
-
configured: extension.state.configured,
|
|
1555
|
-
use_when: extension.use_when,
|
|
1556
|
-
next_command: `${canonicalCliName} extend ${extension.id}`,
|
|
1557
|
-
})),
|
|
1558
|
-
recommended: extensions
|
|
1559
|
-
.filter((extension) => extension.safe_by_default && !extension.state.installed)
|
|
1560
|
-
.map((extension) => ({
|
|
1561
|
-
id: extension.id,
|
|
1562
|
-
name: extension.name,
|
|
1563
|
-
kind: extension.kind,
|
|
1564
|
-
activation: extension.activation,
|
|
1565
|
-
summary: extension.summary,
|
|
1566
|
-
use_when: extension.use_when.slice(0, 1),
|
|
1567
|
-
next_command: `${canonicalCliName} extend ${extension.id}`,
|
|
1568
|
-
})),
|
|
1569
|
-
};
|
|
1570
|
-
} catch (error) {
|
|
1571
|
-
return {
|
|
1572
|
-
catalog_url: catalogUrl(),
|
|
1573
|
-
catalog_version: null,
|
|
1574
|
-
available_count: 0,
|
|
1575
|
-
installed_count: 0,
|
|
1576
|
-
available: [],
|
|
1577
|
-
recommended: [],
|
|
1578
|
-
error: error.message,
|
|
1579
|
-
};
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
1110
|
function printInstallReport(report) {
|
|
1584
1111
|
const verb = report.command === "update" ? "Updated" : "Installed";
|
|
1585
1112
|
const previous = report.package.previous_version && report.package.previous_version !== report.package.current_version
|
|
@@ -1592,31 +1119,11 @@ function printInstallReport(report) {
|
|
|
1592
1119
|
console.log(`Compatibility skill: ${report.skill.compatibility_path}`);
|
|
1593
1120
|
const agentSummary = summarizeStatuses(report.agents);
|
|
1594
1121
|
console.log(`Agents: ${agentSummary}`);
|
|
1595
|
-
if (report.skill.preserved_extensions.length) {
|
|
1596
|
-
console.log(`Preserved extensions: ${report.skill.preserved_extensions.join(", ")}`);
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
if (report.extensions.error) {
|
|
1600
|
-
console.log("");
|
|
1601
|
-
console.log(`Extensions: unavailable (${report.extensions.error})`);
|
|
1602
|
-
} else {
|
|
1603
|
-
console.log("");
|
|
1604
|
-
console.log(`Extensions: ${report.extensions.available_count} available from ${report.extensions.catalog_url}`);
|
|
1605
|
-
if (report.extensions.recommended.length) {
|
|
1606
|
-
console.log("");
|
|
1607
|
-
console.log("Recommended:");
|
|
1608
|
-
for (const extension of report.extensions.recommended.slice(0, 3)) {
|
|
1609
|
-
console.log(` ${extension.name || extension.id}`);
|
|
1610
|
-
if (extension.summary) console.log(` ${extension.summary}`);
|
|
1611
|
-
console.log(` Details: npx ${extension.next_command}`);
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
1122
|
|
|
1616
1123
|
console.log("");
|
|
1617
1124
|
console.log("Next:");
|
|
1618
1125
|
console.log(` $${canonicalSkillName}`);
|
|
1619
|
-
console.log(` ${canonicalCliName}
|
|
1126
|
+
console.log(` ${canonicalCliName} board docs/goals/<slug>`);
|
|
1620
1127
|
console.log(` ${legacyCliName} remains a temporary compatibility alias.`);
|
|
1621
1128
|
}
|
|
1622
1129
|
|
|
@@ -1630,9 +1137,6 @@ function printEverywhereInstallReport(report) {
|
|
|
1630
1137
|
console.log(`Codex: not completed (${report.codex.error})`);
|
|
1631
1138
|
} else if (report.codex) {
|
|
1632
1139
|
console.log(`Codex: plugin ${report.codex.version} enabled at ${report.codex.cache_path}`);
|
|
1633
|
-
if (report.codex.preserved_extensions?.length) {
|
|
1634
|
-
console.log(`Codex preserved extensions: ${report.codex.preserved_extensions.join(", ")}`);
|
|
1635
|
-
}
|
|
1636
1140
|
}
|
|
1637
1141
|
|
|
1638
1142
|
if (report.claude?.ok === false) {
|
|
@@ -1667,12 +1171,6 @@ function summarizeStatuses(items) {
|
|
|
1667
1171
|
.join(", ");
|
|
1668
1172
|
}
|
|
1669
1173
|
|
|
1670
|
-
function assertSkillInstalledForExtensionInstall() {
|
|
1671
|
-
if (!existsSync(join(activeSkillRoot(), "SKILL.md"))) {
|
|
1672
|
-
throw new Error(`${canonicalProductName} skill is not installed. Run: npx ${canonicalCliName}`);
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
1174
|
function latestPublishedVersion() {
|
|
1677
1175
|
if (process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION) {
|
|
1678
1176
|
return normalizeVersion(process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION);
|
|
@@ -1718,106 +1216,6 @@ function compareVersions(left, right) {
|
|
|
1718
1216
|
return left.localeCompare(right);
|
|
1719
1217
|
}
|
|
1720
1218
|
|
|
1721
|
-
function installedExtensions() {
|
|
1722
|
-
const root = extendRoot();
|
|
1723
|
-
if (!existsSync(root)) return [];
|
|
1724
|
-
return readdirSync(root, { withFileTypes: true })
|
|
1725
|
-
.filter((entry) => entry.isDirectory())
|
|
1726
|
-
.map((entry) => readInstalledExtension(join(root, entry.name)))
|
|
1727
|
-
.filter(Boolean);
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
function readInstalledExtension(path) {
|
|
1731
|
-
const installedPath = join(path, ".installed.json");
|
|
1732
|
-
if (!existsSync(installedPath)) {
|
|
1733
|
-
return { id: basename(path), path, issues: ["missing .installed.json"] };
|
|
1734
|
-
}
|
|
1735
|
-
const data = JSON.parse(readFileSync(installedPath, "utf8"));
|
|
1736
|
-
return {
|
|
1737
|
-
...data,
|
|
1738
|
-
path,
|
|
1739
|
-
};
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
function doctorInstalledExtension(extension) {
|
|
1743
|
-
const issues = [];
|
|
1744
|
-
for (const file of extension.files || []) {
|
|
1745
|
-
const path = join(extension.path, safeRelativePath(file.path));
|
|
1746
|
-
if (!existsSync(path)) {
|
|
1747
|
-
issues.push(`missing file: ${file.path}`);
|
|
1748
|
-
continue;
|
|
1749
|
-
}
|
|
1750
|
-
const actualSha = sha256(readFileSync(path));
|
|
1751
|
-
if (actualSha !== file.sha256) {
|
|
1752
|
-
issues.push(`checksum mismatch: ${file.path}`);
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
for (const envName of extension.manifest?.auth?.env || []) {
|
|
1756
|
-
if (!process.env[envName]) issues.push(`missing env: ${envName}`);
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
return {
|
|
1760
|
-
id: extension.id,
|
|
1761
|
-
version: extension.version || "",
|
|
1762
|
-
kind: extension.kind || "",
|
|
1763
|
-
path: extension.path,
|
|
1764
|
-
ok: issues.length === 0,
|
|
1765
|
-
issues,
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
function publicExtension(extension) {
|
|
1770
|
-
return {
|
|
1771
|
-
id: extension.id,
|
|
1772
|
-
name: extension.name || "",
|
|
1773
|
-
kind: extension.kind || "",
|
|
1774
|
-
version: extension.version || "",
|
|
1775
|
-
summary: extension.summary || "",
|
|
1776
|
-
description: extension.description || "",
|
|
1777
|
-
local_use_prompt: extension.local_use_prompt || "",
|
|
1778
|
-
source: extension.source || "",
|
|
1779
|
-
docs: extension.docs || "",
|
|
1780
|
-
use_when: extension.use_when || [],
|
|
1781
|
-
activation: extension.activation || "",
|
|
1782
|
-
outputs: extension.outputs || [],
|
|
1783
|
-
requires_approval: extension.requires_approval || false,
|
|
1784
|
-
safe_by_default: extension.safe_by_default || false,
|
|
1785
|
-
applies_to: extension.applies_to || {},
|
|
1786
|
-
reads: extension.reads || [],
|
|
1787
|
-
writes: extension.writes || [],
|
|
1788
|
-
side_effects: extension.side_effects || [],
|
|
1789
|
-
agent_instructions: extension.agent_instructions || [],
|
|
1790
|
-
auth: extension.auth || { env: [] },
|
|
1791
|
-
supports: extension.supports || {},
|
|
1792
|
-
source_of_truth: extension.source_of_truth || "local",
|
|
1793
|
-
files: (extension.files || []).map((file) => ({
|
|
1794
|
-
path: file.path,
|
|
1795
|
-
sha256: file.sha256,
|
|
1796
|
-
})),
|
|
1797
|
-
};
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
function extensionWithLocalState(extension) {
|
|
1801
|
-
return {
|
|
1802
|
-
...publicExtension(extension),
|
|
1803
|
-
state: {
|
|
1804
|
-
available: true,
|
|
1805
|
-
installed: existsSync(extensionTarget(extension.id)),
|
|
1806
|
-
enabled: false,
|
|
1807
|
-
configured: configuredFor(extension),
|
|
1808
|
-
missing_env: missingEnv(extension),
|
|
1809
|
-
},
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
function configuredFor(extension) {
|
|
1814
|
-
return missingEnv(extension).length === 0;
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
function missingEnv(extension) {
|
|
1818
|
-
return (extension.auth?.env || []).filter((envName) => !process.env[envName]);
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
1219
|
function printJson(value) {
|
|
1822
1220
|
console.log(JSON.stringify(value, null, 2));
|
|
1823
1221
|
}
|