goalbuddy 0.3.6 → 0.3.8
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/CHANGELOG.md +61 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +27 -10
- package/{RELEASE-0.3.5.md → docs/releases/0.3.5.md} +4 -4
- package/docs/releases/0.3.7.md +129 -0
- package/docs/releases/0.3.8.md +40 -0
- package/docs/releases/README.md +83 -0
- package/goalbuddy/SKILL.md +21 -10
- package/goalbuddy/scripts/check-goal-state.mjs +53 -0
- package/goalbuddy/scripts/render-task-prompt.mjs +39 -4
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/README.md +7 -9
- package/goalbuddy/{extend → 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/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/scripts/lib/goal-board.mjs +17 -13
- package/goalbuddy/{extend → surfaces}/local-goal-board/scripts/local-goal-board.mjs +27 -6
- package/{plugins/goalbuddy/skills/goalbuddy/extend → goalbuddy/surfaces}/local-goal-board/test/local-goal-board.test.mjs +63 -12
- 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 +177 -717
- package/package.json +7 -8
- 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 +21 -10
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +53 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/render-task-prompt.mjs +39 -4
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/README.md +7 -9
- package/plugins/goalbuddy/skills/goalbuddy/{extend → 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/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/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 +27 -6
- package/{goalbuddy/extend → plugins/goalbuddy/skills/goalbuddy/surfaces}/local-goal-board/test/local-goal-board.test.mjs +35 -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/examples/improve-goal-maker/goal.md +0 -51
- package/examples/improve-goal-maker/notes/T001-repo-map.md +0 -59
- package/examples/improve-goal-maker/notes/T002-risk-map.md +0 -37
- package/examples/improve-goal-maker/state.yaml +0 -224
- 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,21 +38,18 @@ 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",
|
|
55
49
|
"--task",
|
|
56
50
|
"--board",
|
|
57
51
|
]);
|
|
52
|
+
const pathOptions = new Set(["--board", "--goal"]);
|
|
58
53
|
|
|
59
54
|
const args = process.argv.slice(2);
|
|
60
55
|
const command = args[0] === "--help" || args[0] === "-h"
|
|
@@ -117,6 +112,17 @@ async function main() {
|
|
|
117
112
|
doctorClaude();
|
|
118
113
|
}
|
|
119
114
|
break;
|
|
115
|
+
case "reset":
|
|
116
|
+
if (wantsHelp()) {
|
|
117
|
+
usage();
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
if (targetMode() !== "codex") {
|
|
121
|
+
console.error("Reset currently supports --target codex only.");
|
|
122
|
+
process.exit(2);
|
|
123
|
+
}
|
|
124
|
+
resetCodex();
|
|
125
|
+
break;
|
|
120
126
|
case "check-update":
|
|
121
127
|
case "update-check":
|
|
122
128
|
checkUpdate();
|
|
@@ -128,9 +134,6 @@ async function main() {
|
|
|
128
134
|
}
|
|
129
135
|
plugin();
|
|
130
136
|
break;
|
|
131
|
-
case "extend":
|
|
132
|
-
await extend();
|
|
133
|
-
break;
|
|
134
137
|
case "board":
|
|
135
138
|
await board();
|
|
136
139
|
break;
|
|
@@ -202,6 +205,35 @@ function positionalArgs() {
|
|
|
202
205
|
return values;
|
|
203
206
|
}
|
|
204
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Resolve goal-related paths in raw args to absolute paths.
|
|
210
|
+
* Child processes spawned with cwd=packageRoot cannot resolve
|
|
211
|
+
* relative goal paths from the user's working directory.
|
|
212
|
+
*/
|
|
213
|
+
function resolveChildGoalArgs(rawArgs) {
|
|
214
|
+
const out = [];
|
|
215
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
216
|
+
const arg = rawArgs[index];
|
|
217
|
+
const joinedMatch = [...pathOptions].find((opt) => arg.startsWith(opt + "="));
|
|
218
|
+
if (joinedMatch) {
|
|
219
|
+
const value = arg.slice(joinedMatch.length + 1);
|
|
220
|
+
out.push(`${joinedMatch}=${value ? resolve(value) : value}`);
|
|
221
|
+
} else if (pathOptions.has(arg)) {
|
|
222
|
+
out.push(arg);
|
|
223
|
+
const value = rawArgs[++index] || "";
|
|
224
|
+
out.push(value ? resolve(value) : value);
|
|
225
|
+
} else if (optionsWithValues.has(arg)) {
|
|
226
|
+
out.push(arg);
|
|
227
|
+
out.push(rawArgs[++index] || "");
|
|
228
|
+
} else if (!arg.startsWith("-")) {
|
|
229
|
+
out.push(resolve(arg));
|
|
230
|
+
} else {
|
|
231
|
+
out.push(arg);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
|
|
205
237
|
function usage() {
|
|
206
238
|
console.log(`${canonicalProductName} for Claude Code and Codex
|
|
207
239
|
|
|
@@ -212,13 +244,9 @@ Usage:
|
|
|
212
244
|
${canonicalCliName} update [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--json]
|
|
213
245
|
${canonicalCliName} agents [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--force]
|
|
214
246
|
${canonicalCliName} doctor [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--goal-ready]
|
|
247
|
+
${canonicalCliName} reset --target codex [--codex-home <path>] [--json]
|
|
215
248
|
${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]
|
|
249
|
+
${canonicalCliName} board <docs/goals/slug> [--host <host>] [--port <port>] [--once] [--json]
|
|
222
250
|
${canonicalCliName} prompt <docs/goals/slug> [--task T###] [--board <path/to/state.yaml>] [--json]
|
|
223
251
|
${canonicalCliName} parallel-plan <docs/goals/slug> [--json]
|
|
224
252
|
|
|
@@ -235,8 +263,6 @@ Compatibility:
|
|
|
235
263
|
Environment:
|
|
236
264
|
CODEX_HOME Overrides the default ~/.codex target.
|
|
237
265
|
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
266
|
`);
|
|
241
267
|
}
|
|
242
268
|
|
|
@@ -289,16 +315,11 @@ function installClaudeSkill({ quiet = false } = {}) {
|
|
|
289
315
|
|
|
290
316
|
const previousMetadata = readInstallMetadata(target);
|
|
291
317
|
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
318
|
|
|
296
319
|
mkdirSync(dirname(target), { recursive: true });
|
|
297
320
|
rmSync(target, { recursive: true, force: true });
|
|
298
321
|
cpSync(skillSource, target, { recursive: true });
|
|
299
|
-
restoreInstalledExtensions(target, extensionTempPath);
|
|
300
322
|
writeInstallMetadata(target, previousMetadata);
|
|
301
|
-
cleanupPreservedExtensions([extensionTempPath]);
|
|
302
323
|
|
|
303
324
|
const currentFingerprint = directoryFingerprint(target, { exclude: installFingerprintExcludes() });
|
|
304
325
|
const status = previousFingerprint
|
|
@@ -311,7 +332,6 @@ function installClaudeSkill({ quiet = false } = {}) {
|
|
|
311
332
|
path: target,
|
|
312
333
|
previous_version: previousMetadata?.package_version || "",
|
|
313
334
|
current_version: packageInfo.version,
|
|
314
|
-
preserved_extensions: preservedExtensionIds,
|
|
315
335
|
};
|
|
316
336
|
}
|
|
317
337
|
|
|
@@ -362,7 +382,6 @@ async function buildClaudeInstallReport() {
|
|
|
362
382
|
skill: installClaudeSkill({ quiet }),
|
|
363
383
|
agents: installClaudeAgents({ quiet }),
|
|
364
384
|
legacy_commands_cleanup: cleanupLegacyClaudeCommands({ quiet }),
|
|
365
|
-
extensions: await extensionDiscoverySummary(),
|
|
366
385
|
warnings: [],
|
|
367
386
|
};
|
|
368
387
|
|
|
@@ -463,27 +482,6 @@ function printClaudeInstallReport(report) {
|
|
|
463
482
|
if (report.legacy_commands_cleanup?.removed) {
|
|
464
483
|
console.log(`Removed legacy command: ${report.legacy_commands_cleanup.path}`);
|
|
465
484
|
}
|
|
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
485
|
console.log("");
|
|
488
486
|
console.log("Next:");
|
|
489
487
|
console.log(` Restart Claude Code, then run: /goal-prep`);
|
|
@@ -503,9 +501,6 @@ function installSkill({ force = true, quiet = false } = {}) {
|
|
|
503
501
|
|
|
504
502
|
const previousMetadata = readInstallMetadata(target) || readInstallMetadata(legacyTarget);
|
|
505
503
|
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
504
|
|
|
510
505
|
mkdirSync(dirname(target), { recursive: true });
|
|
511
506
|
if (existsSync(target)) {
|
|
@@ -520,16 +515,13 @@ function installSkill({ force = true, quiet = false } = {}) {
|
|
|
520
515
|
cpSync(skillSource, target, {
|
|
521
516
|
recursive: true,
|
|
522
517
|
});
|
|
523
|
-
restoreInstalledExtensions(target, extensionTempPath);
|
|
524
518
|
writeInstallMetadata(target, previousMetadata);
|
|
525
519
|
|
|
526
520
|
mkdirSync(dirname(legacyTarget), { recursive: true });
|
|
527
521
|
rmSync(legacyTarget, { recursive: true, force: true });
|
|
528
522
|
mkdirSync(legacyTarget, { recursive: true });
|
|
529
523
|
writeFileSync(join(legacyTarget, "SKILL.md"), compatibilitySkillBody());
|
|
530
|
-
restoreInstalledExtensions(legacyTarget, extensionTempPath);
|
|
531
524
|
writeInstallMetadata(legacyTarget, previousMetadata);
|
|
532
|
-
cleanupPreservedExtensions([extensionTempPath]);
|
|
533
525
|
|
|
534
526
|
const currentFingerprint = directoryFingerprint(target, { exclude: installFingerprintExcludes() });
|
|
535
527
|
const status = previousFingerprint
|
|
@@ -543,7 +535,6 @@ function installSkill({ force = true, quiet = false } = {}) {
|
|
|
543
535
|
compatibility_path: legacyTarget,
|
|
544
536
|
previous_version: previousMetadata?.package_version || "",
|
|
545
537
|
current_version: packageInfo.version,
|
|
546
|
-
preserved_extensions: preservedExtensionIds,
|
|
547
538
|
};
|
|
548
539
|
}
|
|
549
540
|
|
|
@@ -605,7 +596,6 @@ async function installAll() {
|
|
|
605
596
|
codex_home: codexHome(),
|
|
606
597
|
skill: installSkill({ force: true, quiet }),
|
|
607
598
|
agents: installAgents({ quiet }),
|
|
608
|
-
extensions: await extensionDiscoverySummary(),
|
|
609
599
|
warnings: [],
|
|
610
600
|
};
|
|
611
601
|
|
|
@@ -628,20 +618,36 @@ function doctor() {
|
|
|
628
618
|
const agents = existsSync(agentsPath)
|
|
629
619
|
? readdirSync(agentsPath).filter((file) => file.startsWith("goal_") && file.endsWith(".toml"))
|
|
630
620
|
: [];
|
|
631
|
-
const
|
|
621
|
+
const installSurfacePresent = plugin.skill_installed || installed || legacyInstalled;
|
|
622
|
+
const residualAgents = installSurfacePresent ? [] : agents.filter((file) => requiredAgentFiles.includes(file));
|
|
623
|
+
const missingAgents = installSurfacePresent || residualAgents.length > 0
|
|
624
|
+
? requiredAgentFiles.filter((file) => !agents.includes(file))
|
|
625
|
+
: [];
|
|
632
626
|
const staleAgents = requiredAgentFiles.filter((file) => {
|
|
633
627
|
const installedAgent = join(agentsPath, file);
|
|
634
628
|
const bundledAgent = join(skillSource, "agents", file);
|
|
635
629
|
if (!existsSync(installedAgent) || !existsSync(bundledAgent)) return false;
|
|
636
630
|
return sha256(readFileSync(installedAgent)) !== sha256(readFileSync(bundledAgent));
|
|
637
631
|
});
|
|
632
|
+
const runtimeState = codexInstallState({
|
|
633
|
+
plugin,
|
|
634
|
+
installed,
|
|
635
|
+
legacyInstalled,
|
|
636
|
+
residualAgents,
|
|
637
|
+
missingAgents,
|
|
638
|
+
staleAgents,
|
|
639
|
+
});
|
|
638
640
|
const goalRuntime = codexGoalRuntimeStatus();
|
|
639
641
|
const warnings = [];
|
|
640
642
|
const errors = [];
|
|
641
643
|
if (!goalRuntime.ready) {
|
|
642
644
|
warnings.push("native Codex /goal runtime is not ready; run `codex login` and `codex features enable goals` before using /goal.");
|
|
643
645
|
}
|
|
644
|
-
if (
|
|
646
|
+
if (runtimeState === "fully-removed") {
|
|
647
|
+
errors.push("Codex GoalBuddy is fully removed; run `npx goalbuddy --target codex` to install.");
|
|
648
|
+
} else if (runtimeState === "residual-agents-only") {
|
|
649
|
+
errors.push(`Residual GoalBuddy Codex agents remain without plugin cache/config: ${residualAgents.join(", ")}; run a GoalBuddy reset/cleanup before treating it as removed.`);
|
|
650
|
+
} else if (!plugin.skill_installed && !installed) {
|
|
645
651
|
errors.push("Codex GoalBuddy plugin is not installed; run `npx goalbuddy --target codex`.");
|
|
646
652
|
}
|
|
647
653
|
if (plugin.skill_installed && !plugin.enabled) {
|
|
@@ -673,7 +679,9 @@ function doctor() {
|
|
|
673
679
|
skill_path: skillPath,
|
|
674
680
|
compatibility_skill_installed: legacyInstalled,
|
|
675
681
|
compatibility_skill_path: legacySkillPath,
|
|
682
|
+
runtime_state: runtimeState,
|
|
676
683
|
installed_agents: agents,
|
|
684
|
+
residual_agents: residualAgents,
|
|
677
685
|
missing_agents: missingAgents,
|
|
678
686
|
stale_agents: staleAgents,
|
|
679
687
|
goal_runtime: goalRuntime,
|
|
@@ -688,6 +696,20 @@ function doctor() {
|
|
|
688
696
|
process.exit(installOk && goalReadyOk && errors.length === 0 ? 0 : 1);
|
|
689
697
|
}
|
|
690
698
|
|
|
699
|
+
function codexInstallState({ plugin, installed, legacyInstalled, residualAgents, missingAgents, staleAgents }) {
|
|
700
|
+
if (residualAgents.length > 0 && !plugin.skill_installed && !installed && !legacyInstalled) {
|
|
701
|
+
return "residual-agents-only";
|
|
702
|
+
}
|
|
703
|
+
if (!plugin.skill_installed && !installed && !legacyInstalled) {
|
|
704
|
+
return "fully-removed";
|
|
705
|
+
}
|
|
706
|
+
if (staleAgents.length > 0) return "stale-agents";
|
|
707
|
+
if (missingAgents.length > 0) return "incomplete";
|
|
708
|
+
if (plugin.skill_installed && !plugin.enabled) return "disabled";
|
|
709
|
+
if ((plugin.skill_installed && plugin.enabled) || installed) return "installed";
|
|
710
|
+
return "incomplete";
|
|
711
|
+
}
|
|
712
|
+
|
|
691
713
|
function checkUpdate() {
|
|
692
714
|
const report = updateReport();
|
|
693
715
|
|
|
@@ -771,20 +793,14 @@ function installPlugin({ quiet = false } = {}) {
|
|
|
771
793
|
|
|
772
794
|
const pluginManifest = JSON.parse(readFileSync(pluginManifestPath, "utf8"));
|
|
773
795
|
const pluginCachePath = pluginCacheRoot(pluginManifest.version);
|
|
774
|
-
const pluginSkillPath = join(pluginCachePath, "skills", canonicalSkillDirectory);
|
|
775
796
|
const marketplace = runCodex(["plugin", "marketplace", "add", source]);
|
|
776
797
|
if (!marketplace.ok) {
|
|
777
798
|
throw new Error(`Failed to add Codex plugin marketplace: ${firstLine(marketplace.stderr || marketplace.stdout)}`);
|
|
778
799
|
}
|
|
779
800
|
|
|
780
|
-
const legacySkillPaths = legacyCodexSkillRoots();
|
|
781
|
-
const existingPluginSkillPath = installedPluginSkillRoot();
|
|
782
|
-
const preservedExtensions = preserveInstalledExtensions([existingPluginSkillPath, ...legacySkillPaths], { tempRoot: dirname(pluginCachePath) });
|
|
783
801
|
mkdirSync(dirname(pluginCachePath), { recursive: true });
|
|
784
802
|
rmSync(pluginCachePath, { recursive: true, force: true });
|
|
785
803
|
cpSync(pluginSource, pluginCachePath, { recursive: true });
|
|
786
|
-
restoreInstalledExtensions(pluginSkillPath, preservedExtensions.tempPath);
|
|
787
|
-
cleanupPreservedExtensions([preservedExtensions.tempPath]);
|
|
788
804
|
const removedLegacySkillPaths = cleanupLegacyCodexSkills();
|
|
789
805
|
const configPath = enablePluginConfig();
|
|
790
806
|
const agents = installAgents({ quiet: true });
|
|
@@ -799,7 +815,6 @@ function installPlugin({ quiet = false } = {}) {
|
|
|
799
815
|
cache_path: pluginCachePath,
|
|
800
816
|
config_path: configPath,
|
|
801
817
|
agents,
|
|
802
|
-
preserved_extensions: preservedExtensions.ids,
|
|
803
818
|
removed_legacy_skill_paths: removedLegacySkillPaths,
|
|
804
819
|
};
|
|
805
820
|
|
|
@@ -815,9 +830,6 @@ function installPlugin({ quiet = false } = {}) {
|
|
|
815
830
|
console.log(`Cache: ${pluginCachePath}`);
|
|
816
831
|
console.log(`Config: ${configPath}`);
|
|
817
832
|
console.log(`Agents: ${summarizeStatuses(report.agents)}`);
|
|
818
|
-
if (report.preserved_extensions.length) {
|
|
819
|
-
console.log(`Preserved extensions: ${report.preserved_extensions.join(", ")}`);
|
|
820
|
-
}
|
|
821
833
|
if (report.removed_legacy_skill_paths.length) {
|
|
822
834
|
console.log(`Removed legacy personal skills: ${report.removed_legacy_skill_paths.join(", ")}`);
|
|
823
835
|
}
|
|
@@ -825,9 +837,8 @@ function installPlugin({ quiet = false } = {}) {
|
|
|
825
837
|
console.log("Restart Codex, then use:");
|
|
826
838
|
console.log(` $${canonicalSkillName}`);
|
|
827
839
|
console.log("");
|
|
828
|
-
console.log("
|
|
840
|
+
console.log("Goal surface:");
|
|
829
841
|
console.log(` npx ${canonicalCliName} board docs/goals/<slug>`);
|
|
830
|
-
console.log(` npx ${canonicalCliName} extend github-projects`);
|
|
831
842
|
return report;
|
|
832
843
|
}
|
|
833
844
|
|
|
@@ -845,8 +856,95 @@ function cleanupLegacyCodexSkills() {
|
|
|
845
856
|
return removed;
|
|
846
857
|
}
|
|
847
858
|
|
|
859
|
+
function resetCodex() {
|
|
860
|
+
const configPath = join(codexHome(), "config.toml");
|
|
861
|
+
const removedConfigSections = [];
|
|
862
|
+
if (existsSync(configPath)) {
|
|
863
|
+
const existing = readFileSync(configPath, "utf8");
|
|
864
|
+
let updated = existing;
|
|
865
|
+
for (const header of [`[plugins."${pluginName}@${pluginName}"]`, `[marketplaces.${pluginName}]`]) {
|
|
866
|
+
const next = removeTomlTable(updated, header);
|
|
867
|
+
if (next !== updated) {
|
|
868
|
+
removedConfigSections.push(header);
|
|
869
|
+
updated = next;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (updated !== existing) writeFileSync(configPath, updated);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const removedPluginCachePaths = [];
|
|
876
|
+
const cacheRoot = pluginCacheOwnerRoot();
|
|
877
|
+
if (existsSync(cacheRoot)) {
|
|
878
|
+
rmSync(cacheRoot, { recursive: true, force: true });
|
|
879
|
+
removedPluginCachePaths.push(cacheRoot);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const removedAgents = [];
|
|
883
|
+
const agentsRoot = join(codexHome(), "agents");
|
|
884
|
+
for (const file of requiredAgentFiles) {
|
|
885
|
+
const path = join(agentsRoot, file);
|
|
886
|
+
if (!existsSync(path)) continue;
|
|
887
|
+
rmSync(path, { recursive: true, force: true });
|
|
888
|
+
removedAgents.push(path);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const removedLegacySkillPaths = cleanupLegacyCodexSkills();
|
|
892
|
+
const report = {
|
|
893
|
+
reset: true,
|
|
894
|
+
target: "codex",
|
|
895
|
+
codex_home: codexHome(),
|
|
896
|
+
config_path: configPath,
|
|
897
|
+
removed_config_sections: removedConfigSections,
|
|
898
|
+
removed_plugin_cache_paths: removedPluginCachePaths,
|
|
899
|
+
removed_agents: removedAgents,
|
|
900
|
+
removed_legacy_skill_paths: removedLegacySkillPaths,
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
if (hasFlag("--json")) {
|
|
904
|
+
printJson(report);
|
|
905
|
+
return report;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
console.log(`Reset ${canonicalProductName} Codex-owned runtime files`);
|
|
909
|
+
console.log(`Config sections: ${removedConfigSections.length ? removedConfigSections.join(", ") : "none"}`);
|
|
910
|
+
console.log(`Plugin cache: ${removedPluginCachePaths.length ? removedPluginCachePaths.join(", ") : "none"}`);
|
|
911
|
+
console.log(`Agents: ${removedAgents.length ? removedAgents.join(", ") : "none"}`);
|
|
912
|
+
console.log(`Legacy personal skills: ${removedLegacySkillPaths.length ? removedLegacySkillPaths.join(", ") : "none"}`);
|
|
913
|
+
return report;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function removeTomlTable(text, header) {
|
|
917
|
+
const normalized = text.endsWith("\n") || text.length === 0 ? text : `${text}\n`;
|
|
918
|
+
const lines = normalized.split("\n");
|
|
919
|
+
const output = [];
|
|
920
|
+
let skipping = false;
|
|
921
|
+
let removed = false;
|
|
922
|
+
const descendantPrefix = `${header.slice(0, -1)}.`;
|
|
923
|
+
|
|
924
|
+
for (const line of lines) {
|
|
925
|
+
const trimmed = line.trim();
|
|
926
|
+
if (trimmed === header || trimmed.startsWith(descendantPrefix)) {
|
|
927
|
+
skipping = true;
|
|
928
|
+
removed = true;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
if (skipping && /^\s*\[/.test(line)) {
|
|
932
|
+
skipping = trimmed.startsWith(descendantPrefix);
|
|
933
|
+
if (skipping) continue;
|
|
934
|
+
}
|
|
935
|
+
if (!skipping) output.push(line);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (!removed) return text;
|
|
939
|
+
return output.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n*$/, "\n");
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function pluginCacheOwnerRoot() {
|
|
943
|
+
return join(codexHome(), "plugins", "cache", pluginName);
|
|
944
|
+
}
|
|
945
|
+
|
|
848
946
|
function pluginCacheRoot(version) {
|
|
849
|
-
return join(
|
|
947
|
+
return join(pluginCacheOwnerRoot(), pluginName, version);
|
|
850
948
|
}
|
|
851
949
|
|
|
852
950
|
function enablePluginConfig() {
|
|
@@ -969,33 +1067,6 @@ function firstLine(value) {
|
|
|
969
1067
|
return (value || "").split(/\r?\n/).find((line) => line.trim())?.trim() || "";
|
|
970
1068
|
}
|
|
971
1069
|
|
|
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
1070
|
async function board() {
|
|
1000
1071
|
const goal = optionValue("--goal") || positional(1);
|
|
1001
1072
|
if (!goal) {
|
|
@@ -1003,8 +1074,9 @@ async function board() {
|
|
|
1003
1074
|
process.exit(2);
|
|
1004
1075
|
}
|
|
1005
1076
|
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1077
|
+
const absoluteGoal = resolve(goal);
|
|
1078
|
+
const script = ensureLocalBoardSurface();
|
|
1079
|
+
const scriptArgs = [script, "--goal", absoluteGoal];
|
|
1008
1080
|
for (const option of ["--host", "--port"]) {
|
|
1009
1081
|
const value = optionValue(option);
|
|
1010
1082
|
if (value) scriptArgs.push(option, value);
|
|
@@ -1035,7 +1107,7 @@ async function prompt() {
|
|
|
1035
1107
|
}
|
|
1036
1108
|
|
|
1037
1109
|
const script = join(skillSource, "scripts", "render-task-prompt.mjs");
|
|
1038
|
-
const scriptArgs = [script, ...args.slice(1)];
|
|
1110
|
+
const scriptArgs = [script, ...resolveChildGoalArgs(args.slice(1))];
|
|
1039
1111
|
const result = spawnSync(process.execPath, scriptArgs, {
|
|
1040
1112
|
cwd: packageRoot,
|
|
1041
1113
|
encoding: "utf8",
|
|
@@ -1049,7 +1121,7 @@ async function prompt() {
|
|
|
1049
1121
|
|
|
1050
1122
|
async function parallelPlan() {
|
|
1051
1123
|
const script = join(skillSource, "scripts", "parallel-plan.mjs");
|
|
1052
|
-
const scriptArgs = [script, ...args.slice(1).filter((arg) => arg !== "--parallel-plan")];
|
|
1124
|
+
const scriptArgs = [script, ...resolveChildGoalArgs(args.slice(1).filter((arg) => arg !== "--parallel-plan"))];
|
|
1053
1125
|
const result = spawnSync(process.execPath, scriptArgs, {
|
|
1054
1126
|
cwd: packageRoot,
|
|
1055
1127
|
encoding: "utf8",
|
|
@@ -1061,303 +1133,18 @@ async function parallelPlan() {
|
|
|
1061
1133
|
process.exit(result.status ?? 1);
|
|
1062
1134
|
}
|
|
1063
1135
|
|
|
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);
|
|
1136
|
+
function ensureLocalBoardSurface() {
|
|
1137
|
+
const script = join(skillSource, "surfaces", "local-goal-board", "scripts", "local-goal-board.mjs");
|
|
1076
1138
|
if (!existsSync(script)) {
|
|
1077
|
-
throw new Error(`
|
|
1139
|
+
throw new Error(`Bundled GoalBuddy board surface is missing: ${script}`);
|
|
1078
1140
|
}
|
|
1079
1141
|
return script;
|
|
1080
1142
|
}
|
|
1081
1143
|
|
|
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
1144
|
function installedSkillRoot() {
|
|
1354
1145
|
return join(codexHome(), "skills", canonicalSkillDirectory);
|
|
1355
1146
|
}
|
|
1356
1147
|
|
|
1357
|
-
function installedPluginSkillRoot() {
|
|
1358
|
-
return installedCodexPlugin().skill_path;
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
1148
|
function installedCodexPlugin() {
|
|
1362
1149
|
const root = join(codexHome(), "plugins", "cache", pluginName, pluginName);
|
|
1363
1150
|
const configPath = join(codexHome(), "config.toml");
|
|
@@ -1413,119 +1200,10 @@ function pluginConfigEnabled(configPath) {
|
|
|
1413
1200
|
return false;
|
|
1414
1201
|
}
|
|
1415
1202
|
|
|
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
1203
|
function legacyInstalledSkillRoot() {
|
|
1424
1204
|
return join(codexHome(), "skills", legacySkillName);
|
|
1425
1205
|
}
|
|
1426
1206
|
|
|
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
1207
|
function sha256(content) {
|
|
1530
1208
|
return createHash("sha256").update(content).digest("hex");
|
|
1531
1209
|
}
|
|
@@ -1558,50 +1236,8 @@ function listFiles(root, { exclude = new Set(), prefix = "" } = {}) {
|
|
|
1558
1236
|
return files;
|
|
1559
1237
|
}
|
|
1560
1238
|
|
|
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
1239
|
function installFingerprintExcludes() {
|
|
1604
|
-
return new Set(["
|
|
1240
|
+
return new Set([".goalbuddy-install.json", ".goal-maker-install.json"]);
|
|
1605
1241
|
}
|
|
1606
1242
|
|
|
1607
1243
|
function installMetadataPath(target) {
|
|
@@ -1633,53 +1269,6 @@ function writeInstallMetadata(target, previousMetadata) {
|
|
|
1633
1269
|
}, null, 2)}\n`);
|
|
1634
1270
|
}
|
|
1635
1271
|
|
|
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
1272
|
function printInstallReport(report) {
|
|
1684
1273
|
const verb = report.command === "update" ? "Updated" : "Installed";
|
|
1685
1274
|
const previous = report.package.previous_version && report.package.previous_version !== report.package.current_version
|
|
@@ -1692,31 +1281,11 @@ function printInstallReport(report) {
|
|
|
1692
1281
|
console.log(`Compatibility skill: ${report.skill.compatibility_path}`);
|
|
1693
1282
|
const agentSummary = summarizeStatuses(report.agents);
|
|
1694
1283
|
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
1284
|
|
|
1716
1285
|
console.log("");
|
|
1717
1286
|
console.log("Next:");
|
|
1718
1287
|
console.log(` $${canonicalSkillName}`);
|
|
1719
|
-
console.log(` ${canonicalCliName}
|
|
1288
|
+
console.log(` ${canonicalCliName} board docs/goals/<slug>`);
|
|
1720
1289
|
console.log(` ${legacyCliName} remains a temporary compatibility alias.`);
|
|
1721
1290
|
}
|
|
1722
1291
|
|
|
@@ -1730,9 +1299,6 @@ function printEverywhereInstallReport(report) {
|
|
|
1730
1299
|
console.log(`Codex: not completed (${report.codex.error})`);
|
|
1731
1300
|
} else if (report.codex) {
|
|
1732
1301
|
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
1302
|
}
|
|
1737
1303
|
|
|
1738
1304
|
if (report.claude?.ok === false) {
|
|
@@ -1767,12 +1333,6 @@ function summarizeStatuses(items) {
|
|
|
1767
1333
|
.join(", ");
|
|
1768
1334
|
}
|
|
1769
1335
|
|
|
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
1336
|
function latestPublishedVersion() {
|
|
1777
1337
|
if (process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION) {
|
|
1778
1338
|
return normalizeVersion(process.env.GOALBUDDY_TEST_NPM_LATEST_VERSION);
|
|
@@ -1818,106 +1378,6 @@ function compareVersions(left, right) {
|
|
|
1818
1378
|
return left.localeCompare(right);
|
|
1819
1379
|
}
|
|
1820
1380
|
|
|
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
1381
|
function printJson(value) {
|
|
1922
1382
|
console.log(JSON.stringify(value, null, 2));
|
|
1923
1383
|
}
|