goalbuddy 0.2.21 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +14 -5
- package/README.md +68 -55
- package/goalbuddy/SKILL.md +44 -14
- package/goalbuddy/agents/README.md +15 -8
- package/goalbuddy/extend/github-projects/README.md +105 -0
- package/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +63 -0
- package/goalbuddy/extend/github-projects/extension.yaml +43 -0
- package/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +728 -0
- package/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +362 -0
- package/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +193 -0
- package/goalbuddy/extend/github-projects/test/github-projects.test.mjs +267 -0
- package/goalbuddy/extend/local-goal-board/README.md +75 -0
- package/goalbuddy/extend/local-goal-board/assets/goalbuddy-mark.png +0 -0
- package/goalbuddy/extend/local-goal-board/examples/sample-goal/notes/T001-scout.md +3 -0
- package/goalbuddy/extend/local-goal-board/examples/sample-goal/state.yaml +124 -0
- package/goalbuddy/extend/local-goal-board/extension.yaml +37 -0
- package/goalbuddy/extend/local-goal-board/scripts/lib/goal-board.mjs +1225 -0
- package/goalbuddy/extend/local-goal-board/scripts/local-goal-board.mjs +258 -0
- package/goalbuddy/extend/local-goal-board/test/local-goal-board.test.mjs +146 -0
- package/goalbuddy/scripts/check-goal-state.mjs +24 -9
- package/goalbuddy/templates/state.yaml +18 -3
- package/internal/assets/goalbuddy-live-board.jpg +0 -0
- package/internal/cli/goal-maker.mjs +424 -31
- package/internal/cli/postinstall.mjs +3 -3
- package/package.json +7 -2
- package/plugins/goalbuddy/.claude-plugin/plugin.json +24 -0
- package/plugins/goalbuddy/.codex-plugin/plugin.json +5 -4
- package/plugins/goalbuddy/README.md +23 -13
- package/plugins/goalbuddy/agents/goal-judge.md +27 -0
- package/plugins/goalbuddy/agents/goal-scout.md +24 -0
- package/plugins/goalbuddy/agents/goal-worker.md +26 -0
- package/plugins/goalbuddy/commands/goal-prep.md +12 -0
- package/plugins/goalbuddy/skills/goalbuddy/SKILL.md +44 -14
- package/plugins/goalbuddy/skills/goalbuddy/agents/README.md +15 -8
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/README.md +105 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/examples/goal-board-sync/state.yaml +63 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/extension.yaml +43 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/github-projects.mjs +728 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/lib/goal-state.mjs +362 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/scripts/sync-github-project.mjs +193 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/github-projects/test/github-projects.test.mjs +267 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/README.md +75 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/assets/goalbuddy-mark.png +0 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/sample-goal/notes/T001-scout.md +3 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/examples/sample-goal/state.yaml +124 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/extension.yaml +37 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/scripts/lib/goal-board.mjs +1225 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/scripts/local-goal-board.mjs +258 -0
- package/plugins/goalbuddy/skills/goalbuddy/extend/local-goal-board/test/local-goal-board.test.mjs +146 -0
- package/plugins/goalbuddy/skills/goalbuddy/scripts/check-goal-state.mjs +24 -9
- package/plugins/goalbuddy/skills/goalbuddy/templates/state.yaml +18 -3
|
@@ -25,23 +25,33 @@ const canonicalSkillDirectory = "goalbuddy";
|
|
|
25
25
|
const legacyCliName = "goal-maker";
|
|
26
26
|
const legacySkillName = "goal-maker";
|
|
27
27
|
const skillSource = join(packageRoot, canonicalSkillDirectory);
|
|
28
|
+
const claudePluginSource = join(packageRoot, "plugins", "goalbuddy");
|
|
28
29
|
const packageInfo = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
29
30
|
const defaultCodexHome = process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
31
|
+
const defaultClaudeHome = process.env.CLAUDE_HOME || join(homedir(), ".claude");
|
|
30
32
|
const defaultCatalogUrl = "https://raw.githubusercontent.com/tolibear/goalbuddy/main/extend/catalog.json";
|
|
31
33
|
const requiredAgentFiles = [
|
|
32
34
|
"goal_judge.toml",
|
|
33
35
|
"goal_scout.toml",
|
|
34
36
|
"goal_worker.toml",
|
|
35
37
|
];
|
|
38
|
+
const requiredClaudeAgentFiles = [
|
|
39
|
+
"goal-scout.md",
|
|
40
|
+
"goal-judge.md",
|
|
41
|
+
"goal-worker.md",
|
|
42
|
+
];
|
|
43
|
+
const bundledCoreExtensionIds = new Set(["github-projects", "local-goal-board"]);
|
|
36
44
|
const optionsWithValues = new Set([
|
|
37
45
|
"--catalog",
|
|
38
46
|
"--catalog-url",
|
|
47
|
+
"--claude-home",
|
|
39
48
|
"--codex-home",
|
|
40
49
|
"--goal",
|
|
41
50
|
"--host",
|
|
42
51
|
"--kind",
|
|
43
52
|
"--port",
|
|
44
53
|
"--source",
|
|
54
|
+
"--target",
|
|
45
55
|
]);
|
|
46
56
|
|
|
47
57
|
const args = process.argv.slice(2);
|
|
@@ -61,17 +71,37 @@ async function main() {
|
|
|
61
71
|
maybePrintLegacyNotice();
|
|
62
72
|
switch (command) {
|
|
63
73
|
case "default":
|
|
64
|
-
|
|
74
|
+
if (installTargetMode() === "all") {
|
|
75
|
+
await installEverywhere();
|
|
76
|
+
} else if (installTargetMode() === "codex") {
|
|
77
|
+
installPlugin();
|
|
78
|
+
} else {
|
|
79
|
+
await installClaudeAll();
|
|
80
|
+
}
|
|
65
81
|
break;
|
|
66
82
|
case "install":
|
|
67
83
|
case "update":
|
|
68
|
-
|
|
84
|
+
if (installTargetMode() === "all") {
|
|
85
|
+
await installEverywhere();
|
|
86
|
+
} else if (installTargetMode() === "codex") {
|
|
87
|
+
await installAll();
|
|
88
|
+
} else {
|
|
89
|
+
await installClaudeAll();
|
|
90
|
+
}
|
|
69
91
|
break;
|
|
70
92
|
case "agents":
|
|
71
|
-
|
|
93
|
+
if (targetMode() === "codex") {
|
|
94
|
+
installAgents();
|
|
95
|
+
} else {
|
|
96
|
+
installClaudeAgents();
|
|
97
|
+
}
|
|
72
98
|
break;
|
|
73
99
|
case "doctor":
|
|
74
|
-
|
|
100
|
+
if (targetMode() === "codex") {
|
|
101
|
+
doctor();
|
|
102
|
+
} else {
|
|
103
|
+
doctorClaude();
|
|
104
|
+
}
|
|
75
105
|
break;
|
|
76
106
|
case "check-update":
|
|
77
107
|
case "update-check":
|
|
@@ -145,15 +175,15 @@ function positionalArgs() {
|
|
|
145
175
|
}
|
|
146
176
|
|
|
147
177
|
function usage() {
|
|
148
|
-
console.log(
|
|
178
|
+
console.log(`${canonicalProductName} for Claude Code and Codex
|
|
149
179
|
|
|
150
180
|
Usage:
|
|
151
|
-
${canonicalCliName} [--codex-home <path>] [--json]
|
|
181
|
+
${canonicalCliName} [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--json]
|
|
152
182
|
${canonicalCliName} plugin install [--source <marketplace-source>] [--codex-home <path>] [--json]
|
|
153
|
-
${canonicalCliName} install [--codex-home <path>] [--force] [--json]
|
|
154
|
-
${canonicalCliName} update [--codex-home <path>] [--json]
|
|
155
|
-
${canonicalCliName} agents [--codex-home <path>] [--force]
|
|
156
|
-
${canonicalCliName} doctor [--codex-home <path>] [--goal-ready]
|
|
183
|
+
${canonicalCliName} install [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--force] [--json]
|
|
184
|
+
${canonicalCliName} update [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--json]
|
|
185
|
+
${canonicalCliName} agents [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--force]
|
|
186
|
+
${canonicalCliName} doctor [--target claude|codex] [--claude-home <path>] [--codex-home <path>] [--goal-ready]
|
|
157
187
|
${canonicalCliName} check-update [--json]
|
|
158
188
|
${canonicalCliName} extend [--catalog-url <url-or-path>] [--kind <kind>] [--json]
|
|
159
189
|
${canonicalCliName} extend <id> [--catalog-url <url-or-path>] [--json]
|
|
@@ -162,17 +192,19 @@ Usage:
|
|
|
162
192
|
${canonicalCliName} extend doctor [<id>] [--codex-home <path>] [--json]
|
|
163
193
|
${canonicalCliName} board <docs/goals/slug> [--catalog-url <url-or-path>] [--host <host>] [--port <port>] [--once] [--json]
|
|
164
194
|
|
|
165
|
-
|
|
166
|
-
${canonicalCliName} Installs and enables the native Codex plugin.
|
|
195
|
+
Targets: by default, install/update prepares both Codex (~/.codex) and Claude Code (~/.claude). Use --target codex or --target claude to limit the command.
|
|
167
196
|
|
|
168
|
-
|
|
169
|
-
${canonicalCliName}
|
|
197
|
+
Default:
|
|
198
|
+
${canonicalCliName} Installs and enables Codex, then installs Claude Code skill + agents + /goal-prep command.
|
|
199
|
+
${canonicalCliName} --target claude Installs ${canonicalProductName} for Claude Code (skill + agents + /goal-prep command).
|
|
200
|
+
${canonicalCliName} --target codex Installs and enables the native Codex plugin.
|
|
170
201
|
|
|
171
202
|
Compatibility:
|
|
172
203
|
${legacyCliName} remains a temporary alias and prints the new npx command for human-facing use.
|
|
173
204
|
|
|
174
205
|
Environment:
|
|
175
206
|
CODEX_HOME Overrides the default ~/.codex target.
|
|
207
|
+
CLAUDE_HOME Overrides the default ~/.claude target (and selects Claude Code unless --target codex is set).
|
|
176
208
|
GOALBUDDY_EXTEND_CATALOG_URL Overrides the default GitHub-hosted extension catalog.
|
|
177
209
|
GOAL_MAKER_EXTEND_CATALOG_URL Legacy fallback for the extension catalog.
|
|
178
210
|
`);
|
|
@@ -182,6 +214,272 @@ function codexHome() {
|
|
|
182
214
|
return resolve(optionValue("--codex-home") || defaultCodexHome);
|
|
183
215
|
}
|
|
184
216
|
|
|
217
|
+
function claudeHome() {
|
|
218
|
+
return resolve(optionValue("--claude-home") || defaultClaudeHome);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function targetMode() {
|
|
222
|
+
const value = (optionValue("--target") || "").toLowerCase();
|
|
223
|
+
if (value === "codex" || value === "claude") return value;
|
|
224
|
+
// Explicit --claude-home or CLAUDE_HOME implies Claude target unless --target codex is set.
|
|
225
|
+
if (optionValue("--claude-home") || process.env.CLAUDE_HOME) return "claude";
|
|
226
|
+
return "codex";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function installTargetMode() {
|
|
230
|
+
const value = (optionValue("--target") || "").toLowerCase();
|
|
231
|
+
if (value === "codex" || value === "claude") return value;
|
|
232
|
+
|
|
233
|
+
const hasCodexHomeOption = Boolean(optionValue("--codex-home"));
|
|
234
|
+
const hasClaudeHomeOption = Boolean(optionValue("--claude-home"));
|
|
235
|
+
if (hasCodexHomeOption && !hasClaudeHomeOption) return "codex";
|
|
236
|
+
if (hasClaudeHomeOption && !hasCodexHomeOption) return "claude";
|
|
237
|
+
if (process.env.CLAUDE_HOME && !hasCodexHomeOption) return "claude";
|
|
238
|
+
return "all";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function claudeSkillRoot() {
|
|
242
|
+
return join(claudeHome(), "skills", canonicalSkillDirectory);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function claudeAgentsRoot() {
|
|
246
|
+
return join(claudeHome(), "agents");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function claudeCommandsRoot() {
|
|
250
|
+
return join(claudeHome(), "commands");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function installClaudeSkill({ quiet = false } = {}) {
|
|
254
|
+
const target = claudeSkillRoot();
|
|
255
|
+
if (!existsSync(skillSource)) {
|
|
256
|
+
console.error(`Skill payload not found: ${skillSource}`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const previousMetadata = readInstallMetadata(target);
|
|
261
|
+
const previousFingerprint = existsSync(target) ? directoryFingerprint(target, { exclude: installFingerprintExcludes() }) : "";
|
|
262
|
+
const preservedExtensions = preserveInstalledExtensions([target], { tempRoot: claudeHome() });
|
|
263
|
+
const extensionTempPath = preservedExtensions.tempPath;
|
|
264
|
+
const preservedExtensionIds = preservedExtensions.ids;
|
|
265
|
+
|
|
266
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
267
|
+
rmSync(target, { recursive: true, force: true });
|
|
268
|
+
cpSync(skillSource, target, { recursive: true });
|
|
269
|
+
restoreInstalledExtensions(target, extensionTempPath);
|
|
270
|
+
writeInstallMetadata(target, previousMetadata);
|
|
271
|
+
cleanupPreservedExtensions([extensionTempPath]);
|
|
272
|
+
|
|
273
|
+
const currentFingerprint = directoryFingerprint(target, { exclude: installFingerprintExcludes() });
|
|
274
|
+
const status = previousFingerprint
|
|
275
|
+
? previousFingerprint === currentFingerprint ? "unchanged" : "updated"
|
|
276
|
+
: "installed";
|
|
277
|
+
if (!quiet) console.log(`Installed Claude Code ${canonicalProductName} skill to ${target}`);
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
status,
|
|
281
|
+
path: target,
|
|
282
|
+
previous_version: previousMetadata?.package_version || "",
|
|
283
|
+
current_version: packageInfo.version,
|
|
284
|
+
preserved_extensions: preservedExtensionIds,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function installClaudeAgents({ quiet = false } = {}) {
|
|
289
|
+
const source = join(claudePluginSource, "agents");
|
|
290
|
+
const target = claudeAgentsRoot();
|
|
291
|
+
const force = hasFlag("--force") || command === "update" || command === "install" || command === "default";
|
|
292
|
+
mkdirSync(target, { recursive: true });
|
|
293
|
+
|
|
294
|
+
const results = [];
|
|
295
|
+
if (!existsSync(source)) return results;
|
|
296
|
+
for (const file of readdirSync(source)) {
|
|
297
|
+
if (!file.endsWith(".md")) continue;
|
|
298
|
+
const dest = join(target, file);
|
|
299
|
+
const sourceHash = sha256(readFileSync(join(source, file)));
|
|
300
|
+
const previousHash = existsSync(dest) ? sha256(readFileSync(dest)) : "";
|
|
301
|
+
if (existsSync(dest) && !force) {
|
|
302
|
+
if (!quiet) console.log(`skip existing ${dest} (use --force to overwrite)`);
|
|
303
|
+
results.push({ file, status: "skipped", path: dest });
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
cpSync(join(source, file), dest);
|
|
307
|
+
const status = previousHash ? previousHash === sourceHash ? "unchanged" : "updated" : "installed";
|
|
308
|
+
if (!quiet) console.log(`installed ${dest}`);
|
|
309
|
+
results.push({ file, status, path: dest });
|
|
310
|
+
}
|
|
311
|
+
return results;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function installClaudeCommands({ quiet = false } = {}) {
|
|
315
|
+
const source = join(claudePluginSource, "commands");
|
|
316
|
+
const target = claudeCommandsRoot();
|
|
317
|
+
const force = hasFlag("--force") || command === "update" || command === "install" || command === "default";
|
|
318
|
+
mkdirSync(target, { recursive: true });
|
|
319
|
+
|
|
320
|
+
const results = [];
|
|
321
|
+
if (!existsSync(source)) return results;
|
|
322
|
+
for (const file of readdirSync(source)) {
|
|
323
|
+
if (!file.endsWith(".md")) continue;
|
|
324
|
+
const dest = join(target, file);
|
|
325
|
+
if (existsSync(dest) && !force) {
|
|
326
|
+
if (!quiet) console.log(`skip existing ${dest} (use --force to overwrite)`);
|
|
327
|
+
results.push({ file, status: "skipped", path: dest });
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const sourceHash = sha256(readFileSync(join(source, file)));
|
|
331
|
+
const previousHash = existsSync(dest) ? sha256(readFileSync(dest)) : "";
|
|
332
|
+
cpSync(join(source, file), dest);
|
|
333
|
+
const status = previousHash ? previousHash === sourceHash ? "unchanged" : "updated" : "installed";
|
|
334
|
+
if (!quiet) console.log(`installed ${dest}`);
|
|
335
|
+
results.push({ file, status, path: dest });
|
|
336
|
+
}
|
|
337
|
+
return results;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function buildClaudeInstallReport() {
|
|
341
|
+
const quiet = true;
|
|
342
|
+
const report = {
|
|
343
|
+
command,
|
|
344
|
+
target: "claude",
|
|
345
|
+
package: {
|
|
346
|
+
name: packageInfo.name,
|
|
347
|
+
current_version: packageInfo.version,
|
|
348
|
+
},
|
|
349
|
+
claude_home: claudeHome(),
|
|
350
|
+
skill: installClaudeSkill({ quiet }),
|
|
351
|
+
agents: installClaudeAgents({ quiet }),
|
|
352
|
+
commands: installClaudeCommands({ quiet }),
|
|
353
|
+
extensions: await extensionDiscoverySummary(),
|
|
354
|
+
warnings: [],
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
report.package.previous_version = report.skill.previous_version;
|
|
358
|
+
return report;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function installClaudeAll() {
|
|
362
|
+
const report = await buildClaudeInstallReport();
|
|
363
|
+
|
|
364
|
+
if (hasFlag("--json")) {
|
|
365
|
+
printJson(report);
|
|
366
|
+
} else {
|
|
367
|
+
printClaudeInstallReport(report);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function installEverywhere() {
|
|
372
|
+
const report = {
|
|
373
|
+
command,
|
|
374
|
+
package: {
|
|
375
|
+
name: packageInfo.name,
|
|
376
|
+
current_version: packageInfo.version,
|
|
377
|
+
},
|
|
378
|
+
codex: null,
|
|
379
|
+
claude: null,
|
|
380
|
+
errors: [],
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
report.codex = installPlugin({ quiet: true });
|
|
385
|
+
} catch (error) {
|
|
386
|
+
report.errors.push({ target: "codex", error: error.message });
|
|
387
|
+
report.codex = { target: "codex", ok: false, error: error.message };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
report.claude = await buildClaudeInstallReport();
|
|
392
|
+
} catch (error) {
|
|
393
|
+
report.errors.push({ target: "claude", error: error.message });
|
|
394
|
+
report.claude = { target: "claude", ok: false, error: error.message };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
report.ok = report.errors.length === 0;
|
|
398
|
+
|
|
399
|
+
if (hasFlag("--json")) {
|
|
400
|
+
printJson(report);
|
|
401
|
+
} else {
|
|
402
|
+
printEverywhereInstallReport(report);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!report.ok) process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function doctorClaude() {
|
|
409
|
+
const skillPath = join(claudeSkillRoot(), "SKILL.md");
|
|
410
|
+
const agentsPath = claudeAgentsRoot();
|
|
411
|
+
const commandsPath = claudeCommandsRoot();
|
|
412
|
+
const installed = existsSync(skillPath);
|
|
413
|
+
const agents = existsSync(agentsPath)
|
|
414
|
+
? readdirSync(agentsPath).filter((file) => file.startsWith("goal-") && file.endsWith(".md"))
|
|
415
|
+
: [];
|
|
416
|
+
const missingAgents = requiredClaudeAgentFiles.filter((file) => !agents.includes(file));
|
|
417
|
+
const staleAgents = requiredClaudeAgentFiles.filter((file) => {
|
|
418
|
+
const installedAgent = join(agentsPath, file);
|
|
419
|
+
const bundledAgent = join(claudePluginSource, "agents", file);
|
|
420
|
+
if (!existsSync(installedAgent) || !existsSync(bundledAgent)) return false;
|
|
421
|
+
return sha256(readFileSync(installedAgent)) !== sha256(readFileSync(bundledAgent));
|
|
422
|
+
});
|
|
423
|
+
const commands = existsSync(commandsPath)
|
|
424
|
+
? readdirSync(commandsPath).filter((file) => file === "goal-prep.md")
|
|
425
|
+
: [];
|
|
426
|
+
|
|
427
|
+
console.log(JSON.stringify({
|
|
428
|
+
target: "claude",
|
|
429
|
+
claude_home: claudeHome(),
|
|
430
|
+
skill_installed: installed,
|
|
431
|
+
skill_path: skillPath,
|
|
432
|
+
installed_agents: agents,
|
|
433
|
+
missing_agents: missingAgents,
|
|
434
|
+
stale_agents: staleAgents,
|
|
435
|
+
installed_commands: commands,
|
|
436
|
+
}, null, 2));
|
|
437
|
+
|
|
438
|
+
const installOk = installed && missingAgents.length === 0 && staleAgents.length === 0;
|
|
439
|
+
process.exit(installOk ? 0 : 1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function printClaudeInstallReport(report) {
|
|
443
|
+
const verb = report.command === "update" ? "Updated" : "Installed";
|
|
444
|
+
const previous = report.package.previous_version && report.package.previous_version !== report.package.current_version
|
|
445
|
+
? ` ${report.package.previous_version} -> ${report.package.current_version}`
|
|
446
|
+
: ` ${report.package.current_version}`;
|
|
447
|
+
console.log("");
|
|
448
|
+
console.log(`${verb} ${canonicalProductName} for Claude Code${previous}`);
|
|
449
|
+
console.log("");
|
|
450
|
+
console.log(`Skill: ${report.skill.status} at ${report.skill.path}`);
|
|
451
|
+
console.log(`Agents: ${summarizeStatuses(report.agents)}`);
|
|
452
|
+
console.log(`Commands: ${summarizeStatuses(report.commands)}`);
|
|
453
|
+
if (report.skill.preserved_extensions.length) {
|
|
454
|
+
console.log(`Preserved extensions: ${report.skill.preserved_extensions.join(", ")}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (report.extensions?.error) {
|
|
458
|
+
console.log("");
|
|
459
|
+
console.log(`Extensions: unavailable (${report.extensions.error})`);
|
|
460
|
+
} else if (report.extensions) {
|
|
461
|
+
console.log("");
|
|
462
|
+
console.log(`Extensions: ${report.extensions.available_count} available from ${report.extensions.catalog_url}`);
|
|
463
|
+
if (report.extensions.recommended?.length) {
|
|
464
|
+
console.log("");
|
|
465
|
+
console.log("Recommended:");
|
|
466
|
+
for (const extension of report.extensions.recommended.slice(0, 3)) {
|
|
467
|
+
console.log(` ${extension.name || extension.id}`);
|
|
468
|
+
if (extension.summary) console.log(` ${extension.summary}`);
|
|
469
|
+
console.log(` Details: npx ${extension.next_command}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
console.log("");
|
|
475
|
+
console.log("Next:");
|
|
476
|
+
console.log(` Restart Claude Code, then run: /goal-prep`);
|
|
477
|
+
console.log(` Or invoke the skill: ${canonicalSkillName}`);
|
|
478
|
+
console.log("");
|
|
479
|
+
console.log("Also available for Codex:");
|
|
480
|
+
console.log(` npx ${canonicalCliName} --target codex`);
|
|
481
|
+
}
|
|
482
|
+
|
|
185
483
|
function installSkill({ force = true, quiet = false } = {}) {
|
|
186
484
|
const target = installedSkillRoot();
|
|
187
485
|
const legacyTarget = legacyInstalledSkillRoot();
|
|
@@ -192,7 +490,7 @@ function installSkill({ force = true, quiet = false } = {}) {
|
|
|
192
490
|
|
|
193
491
|
const previousMetadata = readInstallMetadata(target) || readInstallMetadata(legacyTarget);
|
|
194
492
|
const previousFingerprint = existsSync(target) ? directoryFingerprint(target, { exclude: installFingerprintExcludes() }) : "";
|
|
195
|
-
const preservedExtensions = preserveInstalledExtensions([target, legacyTarget]);
|
|
493
|
+
const preservedExtensions = preserveInstalledExtensions([target, legacyTarget], { tempRoot: codexHome() });
|
|
196
494
|
const extensionTempPath = preservedExtensions.tempPath;
|
|
197
495
|
const preservedExtensionIds = preservedExtensions.ids;
|
|
198
496
|
|
|
@@ -416,7 +714,7 @@ Default source:
|
|
|
416
714
|
`);
|
|
417
715
|
}
|
|
418
716
|
|
|
419
|
-
function installPlugin() {
|
|
717
|
+
function installPlugin({ quiet = false } = {}) {
|
|
420
718
|
const source = optionValue("--source") || "tolibear/goalbuddy";
|
|
421
719
|
const pluginSource = join(packageRoot, "plugins", pluginName);
|
|
422
720
|
const pluginManifestPath = join(pluginSource, ".codex-plugin", "plugin.json");
|
|
@@ -426,42 +724,55 @@ function installPlugin() {
|
|
|
426
724
|
|
|
427
725
|
const pluginManifest = JSON.parse(readFileSync(pluginManifestPath, "utf8"));
|
|
428
726
|
const pluginCachePath = pluginCacheRoot(pluginManifest.version);
|
|
727
|
+
const pluginSkillPath = join(pluginCachePath, "skills", canonicalSkillDirectory);
|
|
429
728
|
const marketplace = runCodex(["plugin", "marketplace", "add", source]);
|
|
430
729
|
if (!marketplace.ok) {
|
|
431
730
|
throw new Error(`Failed to add Codex plugin marketplace: ${firstLine(marketplace.stderr || marketplace.stdout)}`);
|
|
432
731
|
}
|
|
433
732
|
|
|
733
|
+
const existingPluginSkillPath = installedPluginSkillRoot();
|
|
734
|
+
const preservedExtensions = preserveInstalledExtensions([existingPluginSkillPath], { tempRoot: dirname(pluginCachePath) });
|
|
434
735
|
mkdirSync(dirname(pluginCachePath), { recursive: true });
|
|
435
736
|
rmSync(pluginCachePath, { recursive: true, force: true });
|
|
436
737
|
cpSync(pluginSource, pluginCachePath, { recursive: true });
|
|
738
|
+
restoreInstalledExtensions(pluginSkillPath, preservedExtensions.tempPath);
|
|
739
|
+
cleanupPreservedExtensions([preservedExtensions.tempPath]);
|
|
437
740
|
const configPath = enablePluginConfig();
|
|
438
741
|
|
|
439
742
|
const report = {
|
|
440
743
|
installed: true,
|
|
744
|
+
target: "codex",
|
|
441
745
|
plugin: `${pluginName}@${pluginName}`,
|
|
442
746
|
version: pluginManifest.version,
|
|
443
747
|
codex_home: codexHome(),
|
|
444
748
|
marketplace_source: source,
|
|
445
749
|
cache_path: pluginCachePath,
|
|
446
750
|
config_path: configPath,
|
|
751
|
+
preserved_extensions: preservedExtensions.ids,
|
|
447
752
|
};
|
|
448
753
|
|
|
449
|
-
if (hasFlag("--json")) {
|
|
754
|
+
if (hasFlag("--json") && !quiet) {
|
|
450
755
|
printJson(report);
|
|
451
|
-
return;
|
|
756
|
+
return report;
|
|
452
757
|
}
|
|
453
758
|
|
|
759
|
+
if (quiet) return report;
|
|
760
|
+
|
|
454
761
|
console.log(`Installed ${canonicalProductName} Codex plugin ${pluginManifest.version}`);
|
|
455
762
|
console.log(`Marketplace: ${source}`);
|
|
456
763
|
console.log(`Cache: ${pluginCachePath}`);
|
|
457
764
|
console.log(`Config: ${configPath}`);
|
|
765
|
+
if (report.preserved_extensions.length) {
|
|
766
|
+
console.log(`Preserved extensions: ${report.preserved_extensions.join(", ")}`);
|
|
767
|
+
}
|
|
458
768
|
console.log("");
|
|
459
769
|
console.log("Restart Codex, then use:");
|
|
460
770
|
console.log(` $${canonicalSkillName}`);
|
|
461
771
|
console.log("");
|
|
462
|
-
console.log("
|
|
463
|
-
console.log(` npx ${canonicalCliName}
|
|
464
|
-
console.log(` npx ${canonicalCliName} extend
|
|
772
|
+
console.log("Bundled visual boards:");
|
|
773
|
+
console.log(` npx ${canonicalCliName} board docs/goals/<slug>`);
|
|
774
|
+
console.log(` npx ${canonicalCliName} extend github-projects`);
|
|
775
|
+
return report;
|
|
465
776
|
}
|
|
466
777
|
|
|
467
778
|
function pluginCacheRoot(version) {
|
|
@@ -527,9 +838,12 @@ function codexGoalRuntimeStatus() {
|
|
|
527
838
|
}
|
|
528
839
|
|
|
529
840
|
function runCodex(args) {
|
|
530
|
-
const
|
|
841
|
+
const env = { ...process.env, CODEX_HOME: codexHome() };
|
|
842
|
+
const command = codexSpawnCommand(args, env);
|
|
843
|
+
const result = spawnSync(command.file, command.args, {
|
|
531
844
|
encoding: "utf8",
|
|
532
|
-
env
|
|
845
|
+
env,
|
|
846
|
+
shell: command.shell || false,
|
|
533
847
|
});
|
|
534
848
|
return {
|
|
535
849
|
ok: result.status === 0,
|
|
@@ -539,6 +853,38 @@ function runCodex(args) {
|
|
|
539
853
|
};
|
|
540
854
|
}
|
|
541
855
|
|
|
856
|
+
function codexSpawnCommand(args, env) {
|
|
857
|
+
if (process.platform !== "win32") return { file: "codex", args };
|
|
858
|
+
|
|
859
|
+
const command = resolveWindowsCommand("codex", env);
|
|
860
|
+
if (!command) return { file: "codex", args };
|
|
861
|
+
if (/\.(?:cmd|bat)$/i.test(command)) {
|
|
862
|
+
const commandLine = [quoteWindowsCommandArg(command), ...args.map(quoteWindowsCommandArg)].join(" ");
|
|
863
|
+
return {
|
|
864
|
+
file: commandLine,
|
|
865
|
+
args: [],
|
|
866
|
+
shell: true,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
return { file: command, args };
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function resolveWindowsCommand(name, env) {
|
|
873
|
+
const systemWhere = env.SystemRoot ? join(env.SystemRoot, "System32", "where.exe") : "";
|
|
874
|
+
const whereCommand = systemWhere && existsSync(systemWhere) ? systemWhere : "where.exe";
|
|
875
|
+
const where = spawnSync(whereCommand, [name], { encoding: "utf8", env });
|
|
876
|
+
if (where.status !== 0) return "";
|
|
877
|
+
const candidates = where.stdout
|
|
878
|
+
.split(/\r?\n/)
|
|
879
|
+
.map((line) => line.trim())
|
|
880
|
+
.filter(Boolean);
|
|
881
|
+
return candidates.find((candidate) => /\.(?:exe|cmd|bat)$/i.test(candidate)) || "";
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function quoteWindowsCommandArg(value) {
|
|
885
|
+
return `"${String(value).replace(/(["^&|<>()%])/g, "^$1")}"`;
|
|
886
|
+
}
|
|
887
|
+
|
|
542
888
|
function parseGoalFeature(output) {
|
|
543
889
|
const line = output.split(/\r?\n/).find((candidate) => candidate.trim().startsWith("goals"));
|
|
544
890
|
if (!line) return { enabled: false, stage: "" };
|
|
@@ -788,6 +1134,11 @@ async function extendInstall() {
|
|
|
788
1134
|
async function extendInstallAll(catalog) {
|
|
789
1135
|
const results = [];
|
|
790
1136
|
for (const extension of catalog.extensions) {
|
|
1137
|
+
if (existsSync(extensionTarget(extension.id)) && !hasFlag("--force")) {
|
|
1138
|
+
validateCatalogExtension(extension);
|
|
1139
|
+
results.push({ extension, target: extensionTarget(extension.id), plan: installPlan(catalog, extension, extensionTarget(extension.id)), skipped: true });
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
791
1142
|
results.push(await installCatalogExtension(catalog, extension));
|
|
792
1143
|
}
|
|
793
1144
|
|
|
@@ -815,11 +1166,13 @@ async function extendInstallAll(catalog) {
|
|
|
815
1166
|
printJson({
|
|
816
1167
|
installed: true,
|
|
817
1168
|
count: results.length,
|
|
818
|
-
extensions: results.map(({ extension, target }) => ({ id: extension.id, target })),
|
|
1169
|
+
extensions: results.map(({ extension, target, skipped }) => ({ id: extension.id, target, skipped: Boolean(skipped) })),
|
|
819
1170
|
});
|
|
820
1171
|
} else {
|
|
821
|
-
|
|
822
|
-
|
|
1172
|
+
const installedCount = results.filter((result) => !result.skipped).length;
|
|
1173
|
+
const skippedCount = results.length - installedCount;
|
|
1174
|
+
console.log(`Installed ${installedCount} extensions${skippedCount ? `, skipped ${skippedCount} already installed` : ""}`);
|
|
1175
|
+
for (const { extension, target, skipped } of results) console.log(` ${extension.id} -> ${target}${skipped ? " (already installed)" : ""}`);
|
|
823
1176
|
}
|
|
824
1177
|
}
|
|
825
1178
|
|
|
@@ -1058,15 +1411,18 @@ function listFiles(root, { exclude = new Set(), prefix = "" } = {}) {
|
|
|
1058
1411
|
return files;
|
|
1059
1412
|
}
|
|
1060
1413
|
|
|
1061
|
-
function preserveInstalledExtensions(targets) {
|
|
1414
|
+
function preserveInstalledExtensions(targets, { tempRoot = "" } = {}) {
|
|
1062
1415
|
const ids = [];
|
|
1063
|
-
const
|
|
1416
|
+
const firstTarget = targets.find(Boolean) || codexHome();
|
|
1417
|
+
const tempPath = join(tempRoot || dirname(dirname(firstTarget)), `.goalbuddy-preserved-extend-${process.pid}-${Date.now()}`);
|
|
1064
1418
|
let hasExtensions = false;
|
|
1065
1419
|
for (const target of targets) {
|
|
1420
|
+
if (!target) continue;
|
|
1066
1421
|
const source = join(target, "extend");
|
|
1067
1422
|
if (!existsSync(source)) continue;
|
|
1068
1423
|
mkdirSync(tempPath, { recursive: true });
|
|
1069
1424
|
for (const entry of readdirSync(source, { withFileTypes: true })) {
|
|
1425
|
+
if (bundledCoreExtensionIds.has(entry.name)) continue;
|
|
1070
1426
|
const from = join(source, entry.name);
|
|
1071
1427
|
const to = join(tempPath, entry.name);
|
|
1072
1428
|
cpSync(from, to, { recursive: true, force: true });
|
|
@@ -1080,9 +1436,11 @@ function preserveInstalledExtensions(targets) {
|
|
|
1080
1436
|
|
|
1081
1437
|
function restoreInstalledExtensions(target, tempPath) {
|
|
1082
1438
|
if (!tempPath) return;
|
|
1083
|
-
|
|
1084
|
-
mkdirSync(
|
|
1085
|
-
|
|
1439
|
+
const destinationRoot = join(target, "extend");
|
|
1440
|
+
mkdirSync(destinationRoot, { recursive: true });
|
|
1441
|
+
for (const entry of readdirSync(tempPath, { withFileTypes: true })) {
|
|
1442
|
+
cpSync(join(tempPath, entry.name), join(destinationRoot, entry.name), { recursive: true, force: true });
|
|
1443
|
+
}
|
|
1086
1444
|
}
|
|
1087
1445
|
|
|
1088
1446
|
function cleanupPreservedExtensions(paths) {
|
|
@@ -1215,6 +1573,41 @@ function printInstallReport(report) {
|
|
|
1215
1573
|
console.log(` ${legacyCliName} remains a temporary compatibility alias.`);
|
|
1216
1574
|
}
|
|
1217
1575
|
|
|
1576
|
+
function printEverywhereInstallReport(report) {
|
|
1577
|
+
const verb = report.command === "update" ? "Updated" : "Installed";
|
|
1578
|
+
console.log("");
|
|
1579
|
+
console.log(`${verb} ${canonicalProductName} for Codex and Claude Code ${report.package.current_version}`);
|
|
1580
|
+
console.log("");
|
|
1581
|
+
|
|
1582
|
+
if (report.codex?.ok === false) {
|
|
1583
|
+
console.log(`Codex: not completed (${report.codex.error})`);
|
|
1584
|
+
} else if (report.codex) {
|
|
1585
|
+
console.log(`Codex: plugin ${report.codex.version} enabled at ${report.codex.cache_path}`);
|
|
1586
|
+
if (report.codex.preserved_extensions?.length) {
|
|
1587
|
+
console.log(`Codex preserved extensions: ${report.codex.preserved_extensions.join(", ")}`);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
if (report.claude?.ok === false) {
|
|
1592
|
+
console.log(`Claude Code: not completed (${report.claude.error})`);
|
|
1593
|
+
} else if (report.claude) {
|
|
1594
|
+
console.log(`Claude Code: skill ${report.claude.skill.status} at ${report.claude.skill.path}`);
|
|
1595
|
+
console.log(`Claude Code agents: ${summarizeStatuses(report.claude.agents)}`);
|
|
1596
|
+
console.log(`Claude Code commands: ${summarizeStatuses(report.claude.commands)}`);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
if (report.errors.length) {
|
|
1600
|
+
console.log("");
|
|
1601
|
+
console.log("One or more targets need attention:");
|
|
1602
|
+
for (const error of report.errors) console.log(` ${error.target}: ${error.error}`);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
console.log("");
|
|
1606
|
+
console.log("Next:");
|
|
1607
|
+
console.log(` Restart Codex, then use: $${canonicalSkillName}`);
|
|
1608
|
+
console.log(" Restart Claude Code, then run: /goal-prep");
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1218
1611
|
function summarizeStatuses(items) {
|
|
1219
1612
|
const counts = items.reduce((memo, item) => {
|
|
1220
1613
|
memo[item.status] = (memo[item.status] || 0) + 1;
|
|
@@ -12,7 +12,7 @@ if (!globalInstall || process.env.GOALBUDDY_SKIP_POSTINSTALL) {
|
|
|
12
12
|
process.exit(0);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const result = spawnSync(process.execPath, [cliPath
|
|
15
|
+
const result = spawnSync(process.execPath, [cliPath], {
|
|
16
16
|
encoding: "utf8",
|
|
17
17
|
env: process.env,
|
|
18
18
|
stdio: "inherit",
|
|
@@ -23,7 +23,7 @@ if (result.status === 0) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
console.error("");
|
|
26
|
-
console.error("GoalBuddy installed globally, but
|
|
27
|
-
console.error("Run this after Codex
|
|
26
|
+
console.error("GoalBuddy installed globally, but setup did not complete for every target.");
|
|
27
|
+
console.error("Run this after Codex and Claude Code are available:");
|
|
28
28
|
console.error(" goalbuddy");
|
|
29
29
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goalbuddy",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "A /goal operating system for Codex and Claude Code: Scout/Judge/Worker boards with visual board surfaces, receipts, and verification.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"goalbuddy": "internal/cli/goal-maker.mjs",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"goalbuddy/SKILL.md",
|
|
19
19
|
"goalbuddy/agents",
|
|
20
20
|
"goalbuddy/scripts",
|
|
21
|
+
"goalbuddy/extend",
|
|
21
22
|
"goalbuddy/templates"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
@@ -36,6 +37,10 @@
|
|
|
36
37
|
"codex",
|
|
37
38
|
"codex-skill",
|
|
38
39
|
"openai-codex",
|
|
40
|
+
"claude-code",
|
|
41
|
+
"claude-code-plugin",
|
|
42
|
+
"claude-code-skill",
|
|
43
|
+
"anthropic",
|
|
39
44
|
"goalbuddy",
|
|
40
45
|
"goal",
|
|
41
46
|
"task-board",
|