@jaybeeuu/agent-cortex 2.1.0 → 2.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/CHANGELOG.md +411 -0
- package/README.md +33 -13
- package/agents/README.md +22 -10
- package/agents/plan/agent.md +2 -2
- package/agents/ralph/agent.md +126 -88
- package/agents/ralph/claude/frontmatter.json +3 -3
- package/agents/ralph/claude/wake.md +46 -0
- package/agents/ralph/pi/{polling.md → wake.md} +35 -20
- package/agents/ralph-plan/agent.md +23 -11
- package/bin/agent-cortex.mjs +66 -4
- package/bin/installers/claude.mjs +182 -68
- package/bin/installers/pi.mjs +200 -24
- package/extensions/agent-guardrails/README.md +1 -1
- package/extensions/agent-guardrails/guardrails.ts +1 -1
- package/extensions/agent-guardrails/index.ts +1 -1
- package/extensions/agent-modes/discover.test.ts +2 -2
- package/extensions/output-style/README.md +1 -1
- package/extensions/output-style/index.ts +2 -2
- package/extensions/output-style/output-style.ts +2 -2
- package/hooks/claude/scripts/notify.mjs +8 -1
- package/lib/cli.mjs +91 -2
- package/package.json +27 -3
- package/scripts/check-changeset.mjs +4 -0
- package/scripts/check-no-sync.mjs +5 -1
- package/scripts/lib/compose-agent.mjs +69 -9
- package/scripts/lib/fs.mjs +10 -2
- package/scripts/lib/migrate.mjs +736 -0
- package/scripts/lib/release-gates.mjs +21 -1
- package/scripts/lib/skill-frontmatter.mjs +23 -6
- package/scripts/lib/skill-links.mjs +52 -0
- package/scripts/lib/verify-worker-tree.mjs +25 -0
- package/scripts/resolve-publish-command.mjs +4 -0
- package/skills/engineering/design-an-interface/SKILL.md +3 -3
- package/skills/engineering/grill-with-docs/SKILL.md +3 -5
- package/skills/engineering/improve-codebase-architecture/SKILL.md +5 -4
- package/skills/engineering/tdd/SKILL.md +5 -7
- package/skills/engineering/technical-direction/SKILL.md +5 -5
- package/skills/planning/classify-bead/REFERENCE.md +3 -3
- package/skills/planning/classify-bead/SKILL.md +14 -9
- package/skills/planning/classify-bead/scripts/classify-bead.mjs +5 -4
- package/skills/planning/create-task/REFERENCE.md +52 -3
- package/skills/planning/create-task/SKILL.md +70 -26
- package/skills/planning/create-task/scripts/{create-chores.ts → create-chores.mjs} +48 -23
- package/skills/planning/flesh-out-idea/REFERENCE.md +116 -0
- package/skills/planning/flesh-out-idea/SKILL.md +146 -0
- package/skills/planning/prd-to-tasks/REFERENCE.md +54 -5
- package/skills/planning/prd-to-tasks/SKILL.md +22 -16
- package/skills/planning/record-idea/FORMAT.md +66 -17
- package/skills/planning/record-idea/SKILL.md +48 -24
- package/skills/planning/request-refactor-plan/SKILL.md +4 -4
- package/skills/planning/spec-to-tickets/SKILL.md +5 -5
- package/skills/planning/write-a-prd/SKILL.md +8 -9
- package/skills/planning/write-a-ticket/SKILL.md +4 -6
- package/skills/productivity/bd-tool/SKILL.md +15 -10
- package/skills/productivity/grill-me/SKILL.md +5 -0
- package/skills/productivity/init-beads/SKILL.md +3 -3
- package/skills/productivity/migration/REFERENCE.md +227 -0
- package/skills/productivity/migration/SKILL.md +188 -0
- package/skills/productivity/validate-agent-dir/scripts/{validate-agent-dir.ts → validate-agent-dir.mjs} +49 -28
- package/skills/productivity/write-a-skill/SKILL.md +5 -5
- package/skills/review/maintain-agent-docs/SKILL.md +5 -7
- package/skills/review/refactor-skill/SKILL.md +4 -3
- package/skills/review/review-security/SKILL.md +3 -5
- package/skills/style/style-code/SKILL.md +5 -10
- package/skills/style/style-comms/SKILL.md +5 -7
- package/skills/style/style-documentation/SKILL.md +5 -6
- package/skills/style/style-tests/SKILL.md +5 -7
- package/skills/workflow/git-workflow/SKILL.md +52 -26
- package/skills/workflow/hitl-collab/SKILL.md +4 -6
- package/skills/workflow/plan/SKILL.md +41 -30
- package/skills/workflow/ralph/REFERENCE.md +182 -51
- package/skills/workflow/ralph/SKILL.md +89 -50
- package/skills/workflow/run-pipeline-stage/SKILL.md +72 -71
- package/skills/workflow/run-pipeline-stage/playbooks/code.md +11 -0
- package/skills/workflow/run-pipeline-stage/playbooks/document.md +12 -0
- package/skills/workflow/run-pipeline-stage/playbooks/fix.md +12 -0
- package/skills/workflow/run-pipeline-stage/playbooks/review.md +11 -0
- package/skills/workflow/run-pipeline-stage/playbooks/verify.md +13 -0
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +73 -1
- package/token-map.README.md +9 -4
- package/token-map.json +13 -6
- package/.agents/skills/style-versioning/SKILL.md +0 -172
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.github/workflows/ci.yml +0 -302
- package/.junie/guidelines.md +0 -87
- package/.junie/mcp/mcp.json +0 -10
- package/AGENTS.md +0 -153
- package/agents/ralph/claude/polling.md +0 -31
- package/agents-native/ralph.md +0 -203
- package/docs/claude-hooks.md +0 -144
- package/docs/commit-signing.md +0 -231
- package/docs/ideas/auto-model-selection.md +0 -75
- package/docs/ideas/ban-content-assertion-tests.md +0 -75
- package/docs/ideas/bead-progress-notes-for-session-resumption.md +0 -75
- package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +0 -70
- package/docs/ideas/ctx-alias-version-and-update-commands.md +0 -65
- package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +0 -67
- package/docs/ideas/dedicated-review-tests-skill.md +0 -71
- package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +0 -36
- package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +0 -99
- package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +0 -42
- package/docs/ideas/extension-manifests.md +0 -121
- package/docs/ideas/improve-pi-system-prompt.md +0 -43
- package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +0 -95
- package/docs/ideas/model-selector.md +0 -40
- package/docs/ideas/pi-task-memory.md +0 -34
- package/docs/ideas/qa-extension.md +0 -43
- package/docs/ideas/record-idea-implementation-questions.md +0 -68
- package/docs/ideas/record-ideas-as-beads.md +0 -115
- package/docs/ideas/self-flushing-session.md +0 -72
- package/docs/ideas/state-in-beads-not-md.md +0 -69
- package/docs/ideas/subagent-visibility.md +0 -72
- package/docs/ideas/task-session-context.local.md +0 -95
- package/docs/ideas/task-session-context.md +0 -89
- package/docs/ideas/test-data-creation-helpers.md +0 -92
- package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +0 -68
- package/docs/ideas/unified-ci-pipeline.md +0 -28
- package/docs/ideas/unify-the-ticket-and-task-decomposition-workflows.md +0 -71
- package/docs/ideas/worktree-housekeeping-for-closed-beads.md +0 -39
- package/docs/inspirations.md +0 -33
- package/docs/research/disable-model-invocation.md +0 -82
- package/docs/research/gas-city-research.md +0 -256
- package/docs/research/skill-design-research.md +0 -286
- package/docs/research/skill-improvements-analysis.md +0 -117
- package/docs/research/skill-patterns-research.md +0 -24
- package/docs/skills/skill-anatomy.md +0 -200
- package/docs/technical-direction/harness-config.md +0 -177
- package/docs/technical-direction/migration-execution-labels.md +0 -177
- package/docs/user-preferences.md +0 -17
- package/extensions/agent-guardrails/tsconfig.json +0 -12
- package/extensions/agent-modes/tsconfig.json +0 -12
- package/extensions/output-style/tsconfig.json +0 -12
- package/extensions/session-start/tsconfig.json +0 -12
- package/extensions/skill-stats/pnpm-lock.yaml +0 -329
- package/extensions/skill-stats/tsconfig.json +0 -12
- package/extensions/subagent/agents/planner.md +0 -37
- package/extensions/subagent/agents/reviewer.md +0 -35
- package/extensions/subagent/agents/scout.md +0 -50
- package/extensions/subagent/agents/worker.md +0 -24
- package/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/extensions/subagent/prompts/implement.md +0 -10
- package/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/extensions/subagent/tsconfig.json +0 -12
- package/pi/keybindings.json +0 -3
- package/pi/settings.json +0 -34
- package/pnpm-workspace.yaml +0 -14
- package/skills/planning/create-task/scripts/package.json +0 -14
- package/skills/planning/create-task/scripts/pnpm-lock.yaml +0 -342
- package/skills/planning/create-task/scripts/tsconfig.json +0 -12
- package/skills/planning/record-idea/scripts/new-idea.sh +0 -89
- package/skills/productivity/validate-agent-dir/scripts/package.json +0 -15
- package/skills/productivity/validate-agent-dir/scripts/pnpm-lock.yaml +0 -329
- package/skills/productivity/validate-agent-dir/scripts/pnpm-workspace.yaml +0 -2
- package/skills/productivity/validate-agent-dir/scripts/tsconfig.json +0 -19
- package/skills/productivity/validate-agent-dir/scripts/validate-agent-dir.test.ts +0 -317
- package/skills/workflow/run-pipeline-stage/REFERENCE.md +0 -22
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.test.ts +0 -682
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.ts +0 -588
- package/skills/workflow/run-pipeline-stage/scripts/package.json +0 -15
- package/skills/workflow/run-pipeline-stage/scripts/pnpm-lock.yaml +0 -342
- package/skills/workflow/run-pipeline-stage/scripts/pnpm-workspace.yaml +0 -2
- package/skills/workflow/run-pipeline-stage/scripts/tsconfig.json +0 -12
- package/test/classify-bead.test.mjs +0 -277
- package/test/claude-installer.test.mjs +0 -308
- package/test/cli.test.mjs +0 -314
- package/test/create-chores.test.mjs +0 -85
- package/test/fake-bd.test.mjs +0 -72
- package/test/fs.test.mjs +0 -76
- package/test/helpers/fake-bd.mjs +0 -87
- package/test/helpers/fake-claude.mjs +0 -152
- package/test/helpers/fake-pi.mjs +0 -132
- package/test/installer.test.mjs +0 -806
- package/test/notify-hook.test.mjs +0 -63
- package/test/pending-gates.test.mjs +0 -147
- package/test/pi-installer.test.mjs +0 -785
- package/test/pi-register.test.mjs +0 -342
- package/test/pi-sweep.test.mjs +0 -619
- package/test/release-gates.test.mjs +0 -190
- package/test/skill-frontmatter.test.mjs +0 -137
- package/test/verify-worker-tree.test.mjs +0 -120
|
@@ -11,14 +11,17 @@
|
|
|
11
11
|
// agent.md + claude/ harness dirs,
|
|
12
12
|
// {{TOOL:...}}/{{PATH:...}}
|
|
13
13
|
// substituted against token-map.json's
|
|
14
|
-
// claude column)
|
|
15
|
-
// (ralph verbatim)
|
|
14
|
+
// claude column)
|
|
16
15
|
// <output>/skills/<name>/ skill dirs COPIED flat (one dir per
|
|
17
16
|
// skill, as Claude's <name>/SKILL.md
|
|
18
17
|
// plugin scan expects) with .md files
|
|
19
18
|
// token-substituted — no symlinks, so
|
|
20
19
|
// literal {{TOOL:...}}/{{PATH:...}}
|
|
21
|
-
// never reaches Claude
|
|
20
|
+
// never reaches Claude. Cross-skill
|
|
21
|
+
// links are rewritten for the flat
|
|
22
|
+
// layout (scripts/lib/skill-links.mjs),
|
|
23
|
+
// so a grouped-tree handoff still
|
|
24
|
+
// resolves in the plugin.
|
|
22
25
|
// <output>/hooks.json + hooks/<scripts> from hooks/claude/ (support files
|
|
23
26
|
// referenced via ${CLAUDE_PLUGIN_ROOT})
|
|
24
27
|
// <output>/.mcp.json + scripts/ hand-authored extras copied from
|
|
@@ -60,10 +63,33 @@ import { fileURLToPath } from "node:url";
|
|
|
60
63
|
import { homedir } from "node:os";
|
|
61
64
|
import { composeAgent, loadTokenMap, substituteTokens } from "../../scripts/lib/compose-agent.mjs";
|
|
62
65
|
import { isDirectory, isFile } from "../../scripts/lib/fs.mjs";
|
|
66
|
+
import { flattenSkillLinks } from "../../scripts/lib/skill-links.mjs";
|
|
63
67
|
|
|
64
68
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
65
69
|
const PACKAGE_ROOT = join(MODULE_DIR, "..", "..");
|
|
66
70
|
|
|
71
|
+
/** @typedef {import("../../scripts/lib/compose-agent.mjs").TokenMap} TokenMap */
|
|
72
|
+
/** @typedef {(msg: string) => void} Warn */
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Result of writing (or planning to write) a managed file.
|
|
76
|
+
* @typedef {object} ManagedFileResult
|
|
77
|
+
* @property {string} path
|
|
78
|
+
* @property {"would-remove-symlink" | "unchanged" | "would-write" | "removed-symlink" | "written" | "skipped-unmanaged"} action
|
|
79
|
+
* @property {boolean} removedSymlink
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Registration plan/outcome returned by registerClaude.
|
|
84
|
+
* @typedef {object} RegisterClaudeResult
|
|
85
|
+
* @property {string} marketplace
|
|
86
|
+
* @property {string} plugin
|
|
87
|
+
* @property {string[][]} commands
|
|
88
|
+
* @property {boolean} dryRun
|
|
89
|
+
* @property {boolean} registered
|
|
90
|
+
* @property {string[]} skipped
|
|
91
|
+
*/
|
|
92
|
+
|
|
67
93
|
// Home-scoped plugin install root. The parent dir (~/.agent-cortex) is the
|
|
68
94
|
// marketplace root: it carries .claude-plugin/marketplace.json exposing
|
|
69
95
|
// ./claude, so `claude plugin marketplace add ~/.agent-cortex` points Claude
|
|
@@ -98,27 +124,27 @@ const CONTRACT_VERSION = 1;
|
|
|
98
124
|
|
|
99
125
|
const CLAUDE = "claude";
|
|
100
126
|
|
|
101
|
-
const DEFAULT_WARN = (msg) => console.warn(`[claude-installer] ${msg}`);
|
|
127
|
+
const DEFAULT_WARN = (/** @type {string} */ msg) => console.warn(`[claude-installer] ${msg}`);
|
|
102
128
|
|
|
103
|
-
// Agents NOT transformed from the composable sources. `ralph` is authored natively for Claude
|
|
104
|
-
// instead (agents-native/ralph.md) — its event-driven orchestration can't be produced by
|
|
105
|
-
// section composition from the poll-loop sources.
|
|
106
|
-
const DEFER = new Set(["ralph"]);
|
|
107
|
-
|
|
108
|
-
// Ralph-coupled pipeline skills that don't apply to the lean Claude Ralph (its completion
|
|
109
|
-
// handling is event-driven, not the pi orchestrator's). Not copied into the Claude plugin.
|
|
110
|
-
const SKILL_EXCLUDE = new Set(["ralph", "run-pipeline-stage"]);
|
|
111
129
|
|
|
112
130
|
const NAME_PREFIX = "agent-cortex:";
|
|
113
131
|
|
|
132
|
+
/**
|
|
133
|
+
* @param {string} a
|
|
134
|
+
* @param {string} b
|
|
135
|
+
* @returns {number}
|
|
136
|
+
*/
|
|
114
137
|
function byName(a, b) {
|
|
115
138
|
return a.localeCompare(b);
|
|
116
139
|
}
|
|
117
140
|
|
|
118
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Compose every agent from agents/<name>/ — all harnesses share the composable sources.
|
|
143
|
+
* @param {string} root
|
|
144
|
+
* @returns {Promise<{ files: { file: string, content: string }[] }>}
|
|
145
|
+
*/
|
|
119
146
|
async function buildAgents(root) {
|
|
120
147
|
const files = [];
|
|
121
|
-
const skipped = [];
|
|
122
148
|
const entries = (await readdir(join(root, "agents"), { withFileTypes: true })).sort((a, b) =>
|
|
123
149
|
byName(a.name, b.name),
|
|
124
150
|
);
|
|
@@ -128,11 +154,6 @@ async function buildAgents(root) {
|
|
|
128
154
|
const dir = join(root, "agents", entry.name);
|
|
129
155
|
if (!(await isFile(join(dir, "agent.md"))) || !(await isFile(join(dir, "claude", "frontmatter.json")))) continue;
|
|
130
156
|
|
|
131
|
-
if (DEFER.has(entry.name)) {
|
|
132
|
-
skipped.push(entry.name);
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
157
|
const fm = await composeAgent(root, entry.name, CLAUDE);
|
|
137
158
|
const slug = fm.name.startsWith(NAME_PREFIX) ? fm.name.slice(NAME_PREFIX.length) : fm.name;
|
|
138
159
|
const header =
|
|
@@ -144,18 +165,7 @@ async function buildAgents(root) {
|
|
|
144
165
|
`---\n`;
|
|
145
166
|
files.push({ file: `${slug}.md`, content: `${header}\n${fm.body}\n` });
|
|
146
167
|
}
|
|
147
|
-
return { files
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Copy hand-authored Claude-native agents into agents/ verbatim. */
|
|
151
|
-
async function buildNatives(root, nativeSrc) {
|
|
152
|
-
const files = [];
|
|
153
|
-
if (!(await isDirectory(nativeSrc))) return files;
|
|
154
|
-
const names = (await readdir(nativeSrc)).filter((f) => f.endsWith(".md")).sort(byName);
|
|
155
|
-
for (const file of names) {
|
|
156
|
-
files.push({ file, content: await readFile(join(nativeSrc, file), "utf-8") });
|
|
157
|
-
}
|
|
158
|
-
return files;
|
|
168
|
+
return { files };
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
/**
|
|
@@ -164,36 +174,63 @@ async function buildNatives(root, nativeSrc) {
|
|
|
164
174
|
* under skills/), token-substituting .md files against token-map.json's claude
|
|
165
175
|
* column (the same materialisation as bin/installers/pi.mjs: no symlinks, so
|
|
166
176
|
* literal {{TOOL:...}}/{{PATH:...}} never survives; non-.md files verbatim).
|
|
177
|
+
* Flattening drops the group segment an authored cross-skill link relies on, so
|
|
178
|
+
* each .md file's skill links are rewritten to the flat sibling path.
|
|
179
|
+
* @param {string} root
|
|
180
|
+
* @param {string} skillOut
|
|
181
|
+
* @param {{ dryRun: boolean, pluginRoot: string | undefined, tokenMap: TokenMap, warn: Warn }} options
|
|
182
|
+
* @returns {Promise<{ names: string[], md: number, files: number, dir: string }>}
|
|
167
183
|
*/
|
|
168
184
|
async function buildSkills(root, skillOut, { dryRun, pluginRoot, tokenMap, warn }) {
|
|
185
|
+
// Names first: flattening needs the full set to recognise a skill segment in a
|
|
186
|
+
// link, and every name is unique once flattened.
|
|
187
|
+
const sources = [];
|
|
169
188
|
const seen = new Map();
|
|
170
|
-
const transform = (content) =>
|
|
171
|
-
substituteTokens(content, CLAUDE, tokenMap, { dropNullTools: true, pluginRoot, resolveRelativePaths: true, warn });
|
|
172
|
-
|
|
173
|
-
const names = [];
|
|
174
|
-
let md = 0;
|
|
175
|
-
let files = 0;
|
|
176
189
|
for (const group of (await readdir(join(root, "skills"))).sort(byName)) {
|
|
177
190
|
const groupDir = join(root, "skills", group);
|
|
178
191
|
if (!(await isDirectory(groupDir))) continue;
|
|
179
192
|
for (const name of (await readdir(groupDir)).sort(byName)) {
|
|
180
193
|
const skillDir = join(groupDir, name);
|
|
181
194
|
if (!(await isFile(join(skillDir, "SKILL.md")))) continue;
|
|
182
|
-
if (SKILL_EXCLUDE.has(name)) continue;
|
|
183
195
|
if (seen.has(name)) {
|
|
184
196
|
throw new Error(`duplicate skill name "${name}" (${seen.get(name)} and ${group}) — names must be unique when flattened`);
|
|
185
197
|
}
|
|
186
198
|
seen.set(name, group);
|
|
187
|
-
|
|
188
|
-
const stats = await copyTree(skillDir, join(skillOut, name), transform, dryRun);
|
|
189
|
-
md += stats.md;
|
|
190
|
-
files += stats.files;
|
|
199
|
+
sources.push({ name, dir: skillDir });
|
|
191
200
|
}
|
|
192
201
|
}
|
|
202
|
+
|
|
203
|
+
const names = sources.map((source) => source.name);
|
|
204
|
+
const nameSet = new Set(names);
|
|
205
|
+
let md = 0;
|
|
206
|
+
let files = 0;
|
|
207
|
+
for (const source of sources) {
|
|
208
|
+
/** @param {string} content @returns {string} */
|
|
209
|
+
const transform = (content) =>
|
|
210
|
+
flattenSkillLinks(
|
|
211
|
+
substituteTokens(content, CLAUDE, tokenMap, {
|
|
212
|
+
dropNullTools: true,
|
|
213
|
+
pluginRoot,
|
|
214
|
+
resolveRelativePaths: true,
|
|
215
|
+
warn,
|
|
216
|
+
}),
|
|
217
|
+
nameSet,
|
|
218
|
+
);
|
|
219
|
+
const stats = await copyTree(source.dir, join(skillOut, source.name), transform, dryRun);
|
|
220
|
+
md += stats.md;
|
|
221
|
+
files += stats.files;
|
|
222
|
+
}
|
|
193
223
|
return { names, md, files, dir: skillOut };
|
|
194
224
|
}
|
|
195
225
|
|
|
196
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* Recursively copy a tree; transform .md file contents, copy everything else verbatim.
|
|
228
|
+
* @param {string} src
|
|
229
|
+
* @param {string} dest
|
|
230
|
+
* @param {(content: string) => string} transform
|
|
231
|
+
* @param {boolean} dryRun
|
|
232
|
+
* @returns {Promise<{ md: number, files: number }>}
|
|
233
|
+
*/
|
|
197
234
|
async function copyTree(src, dest, transform, dryRun) {
|
|
198
235
|
let md = 0;
|
|
199
236
|
let files = 0;
|
|
@@ -216,7 +253,11 @@ async function copyTree(src, dest, transform, dryRun) {
|
|
|
216
253
|
return { md, files };
|
|
217
254
|
}
|
|
218
255
|
|
|
219
|
-
/**
|
|
256
|
+
/**
|
|
257
|
+
* plugin.json manifest; version tracks the package so it can never go stale.
|
|
258
|
+
* @param {string} root
|
|
259
|
+
* @returns {Promise<string>}
|
|
260
|
+
*/
|
|
220
261
|
async function buildPluginJson(root) {
|
|
221
262
|
const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf-8"));
|
|
222
263
|
return (
|
|
@@ -242,6 +283,10 @@ async function buildPluginJson(root) {
|
|
|
242
283
|
* install root ~/.agent-cortex doubles as the marketplace root), so `claude
|
|
243
284
|
* plugin marketplace add ~/.agent-cortex` resolves ./claude relative to the
|
|
244
285
|
* manifest's directory.
|
|
286
|
+
* @param {string} root
|
|
287
|
+
* @param {string} outputName
|
|
288
|
+
* @returns {Promise<{ name: string, owner: { name: string }, metadata: { description: string },
|
|
289
|
+
* plugins: { name: string, source: string, description: string }[] }>}
|
|
245
290
|
*/
|
|
246
291
|
async function buildMarketplaceManifest(root, outputName) {
|
|
247
292
|
const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf-8"));
|
|
@@ -263,11 +308,19 @@ async function buildMarketplaceManifest(root, outputName) {
|
|
|
263
308
|
* Hook support files (hooks/claude/ except hooks.json) bundled into
|
|
264
309
|
* hooks/ so hook commands can reference them via ${CLAUDE_PLUGIN_ROOT}.
|
|
265
310
|
* hooks.json itself is special-cased to the plugin root (hooks.json).
|
|
311
|
+
* @param {string} root
|
|
312
|
+
* @returns {Promise<{ rel: string, content: string }[]>}
|
|
266
313
|
*/
|
|
267
314
|
async function buildHookFiles(root) {
|
|
315
|
+
/** @type {{ rel: string, content: string }[]} */
|
|
268
316
|
const files = [];
|
|
269
317
|
const srcDir = join(root, "hooks", "claude");
|
|
270
318
|
if (!(await isDirectory(srcDir))) return files;
|
|
319
|
+
/**
|
|
320
|
+
* @param {string} dir
|
|
321
|
+
* @param {string} rel
|
|
322
|
+
* @returns {Promise<void>}
|
|
323
|
+
*/
|
|
271
324
|
const walk = async (dir, rel) => {
|
|
272
325
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
273
326
|
entries.sort((a, b) => byName(a.name, b.name));
|
|
@@ -289,8 +342,11 @@ async function buildHookFiles(root) {
|
|
|
289
342
|
* at read time (not path-referenced) so the write-phase cleanup of the
|
|
290
343
|
* output dir can never destroy the sources (outputs normally go to
|
|
291
344
|
* ~/.agent-cortex/claude or a tmp dir, never into claude-extras/).
|
|
345
|
+
* @param {string} root
|
|
346
|
+
* @returns {Promise<{ rel: string, content: string | Buffer, mode: number }[]>}
|
|
292
347
|
*/
|
|
293
348
|
async function buildHandAuthored(root) {
|
|
349
|
+
/** @type {{ rel: string, content: string | Buffer, mode: number }[]} */
|
|
294
350
|
const items = [];
|
|
295
351
|
const srcExtras = join(root, "claude-extras");
|
|
296
352
|
if (await isFile(join(srcExtras, ".mcp.json"))) {
|
|
@@ -320,6 +376,8 @@ async function buildHandAuthored(root) {
|
|
|
320
376
|
* - `enabledPlugins` and `extraKnownMarketplaces` are CLI-owned: they are
|
|
321
377
|
* rewritten from the template outright. `permissions`, `hooks`, `env`,
|
|
322
378
|
* `statusLine` and every other key are never touched.
|
|
379
|
+
* @param {{ root: string, target: string, dryRun: boolean, warn: Warn }} options
|
|
380
|
+
* @returns {Promise<ManagedFileResult | null>}
|
|
323
381
|
*/
|
|
324
382
|
async function installSettings({ root, target, dryRun, warn }) {
|
|
325
383
|
const template = await readTemplateJson(join(root, SETTINGS_TEMPLATE), warn);
|
|
@@ -339,8 +397,12 @@ async function installSettings({ root, target, dryRun, warn }) {
|
|
|
339
397
|
* Merge the committed template over the live file. Template keys fill in
|
|
340
398
|
* defaults, live values win (deeply), and keys the template does not know about
|
|
341
399
|
* are preserved — re-installing never discards personal or Claude-written config.
|
|
400
|
+
* @param {Record<string, any>} template
|
|
401
|
+
* @param {Record<string, any>} existing
|
|
402
|
+
* @returns {Record<string, any>}
|
|
342
403
|
*/
|
|
343
404
|
function mergeSettings(template, existing) {
|
|
405
|
+
/** @type {Record<string, any>} */
|
|
344
406
|
const merged = {};
|
|
345
407
|
for (const key of Object.keys(template)) {
|
|
346
408
|
const templateValue = template[key];
|
|
@@ -362,6 +424,8 @@ function mergeSettings(template, existing) {
|
|
|
362
424
|
* a sibling temp file and renaming it over the target — rename replaces the link
|
|
363
425
|
* itself, so a committed template is never written through.
|
|
364
426
|
* Returns the action taken so the CLI can report the plan (dry-run included).
|
|
427
|
+
* @param {{ target: string, content: string, dryRun: boolean }} options
|
|
428
|
+
* @returns {Promise<ManagedFileResult>}
|
|
365
429
|
*/
|
|
366
430
|
async function writeManagedFile({ target, content, dryRun }) {
|
|
367
431
|
const symlink = await isSymlink(target);
|
|
@@ -399,6 +463,8 @@ async function writeManagedFile({ target, content, dryRun }) {
|
|
|
399
463
|
* text between them and every other line of the user's memory file survives. A
|
|
400
464
|
* file with exactly one marker (hand-edited or truncated) is left untouched with
|
|
401
465
|
* a warning, never guessed at.
|
|
466
|
+
* @param {{ root: string, target: string, dryRun: boolean, warn: Warn }} options
|
|
467
|
+
* @returns {Promise<ManagedFileResult | null>}
|
|
402
468
|
*/
|
|
403
469
|
async function installInstructions({ root, target, dryRun, warn }) {
|
|
404
470
|
const block = await readFileMaybe(join(root, INSTRUCTIONS_SOURCE));
|
|
@@ -418,6 +484,9 @@ async function installInstructions({ root, target, dryRun, warn }) {
|
|
|
418
484
|
* Return `existing` with the managed section replaced by `block`, or null when
|
|
419
485
|
* the file carries unmatched markers. A file without markers gets the section
|
|
420
486
|
* appended, so personal memory text is never disturbed.
|
|
487
|
+
* @param {string | null} existing
|
|
488
|
+
* @param {string} block
|
|
489
|
+
* @returns {string | null}
|
|
421
490
|
*/
|
|
422
491
|
function mergeManagedSection(existing, block) {
|
|
423
492
|
const managed = `${INSTRUCTIONS_BEGIN}\n${block.trimEnd()}\n${INSTRUCTIONS_END}`;
|
|
@@ -436,6 +505,11 @@ function mergeManagedSection(existing, block) {
|
|
|
436
505
|
return `${[before, managed, after].filter((part) => part !== "").join("\n\n")}\n`;
|
|
437
506
|
}
|
|
438
507
|
|
|
508
|
+
/**
|
|
509
|
+
* @param {string} filePath
|
|
510
|
+
* @param {Warn} warn
|
|
511
|
+
* @returns {Promise<Record<string, any> | null>}
|
|
512
|
+
*/
|
|
439
513
|
async function readTemplateJson(filePath, warn) {
|
|
440
514
|
const raw = await readFileMaybe(filePath);
|
|
441
515
|
if (raw === null) {
|
|
@@ -447,6 +521,11 @@ async function readTemplateJson(filePath, warn) {
|
|
|
447
521
|
return parsed;
|
|
448
522
|
}
|
|
449
523
|
|
|
524
|
+
/**
|
|
525
|
+
* @param {string} filePath
|
|
526
|
+
* @param {Warn} warn
|
|
527
|
+
* @returns {Promise<Record<string, any>>}
|
|
528
|
+
*/
|
|
450
529
|
async function readJsonObject(filePath, warn) {
|
|
451
530
|
const raw = await readFileMaybe(filePath);
|
|
452
531
|
if (raw === null) return {};
|
|
@@ -458,6 +537,10 @@ async function readJsonObject(filePath, warn) {
|
|
|
458
537
|
return parsed;
|
|
459
538
|
}
|
|
460
539
|
|
|
540
|
+
/**
|
|
541
|
+
* @param {string} raw
|
|
542
|
+
* @returns {Record<string, any> | null}
|
|
543
|
+
*/
|
|
461
544
|
function tryParseJsonObject(raw) {
|
|
462
545
|
try {
|
|
463
546
|
const parsed = JSON.parse(raw);
|
|
@@ -467,19 +550,31 @@ function tryParseJsonObject(raw) {
|
|
|
467
550
|
}
|
|
468
551
|
}
|
|
469
552
|
|
|
553
|
+
/**
|
|
554
|
+
* @param {unknown} value
|
|
555
|
+
* @returns {value is Record<string, any>}
|
|
556
|
+
*/
|
|
470
557
|
function isPlainObject(value) {
|
|
471
558
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
472
559
|
}
|
|
473
560
|
|
|
561
|
+
/**
|
|
562
|
+
* @param {string} filePath
|
|
563
|
+
* @returns {Promise<string | null>}
|
|
564
|
+
*/
|
|
474
565
|
async function readFileMaybe(filePath) {
|
|
475
566
|
try {
|
|
476
567
|
return await readFile(filePath, "utf-8");
|
|
477
568
|
} catch (err) {
|
|
478
|
-
if (err.code === "ENOENT") return null;
|
|
569
|
+
if (/** @type {NodeJS.ErrnoException} */ (err).code === "ENOENT") return null;
|
|
479
570
|
throw err;
|
|
480
571
|
}
|
|
481
572
|
}
|
|
482
573
|
|
|
574
|
+
/**
|
|
575
|
+
* @param {string} filePath
|
|
576
|
+
* @returns {Promise<boolean>}
|
|
577
|
+
*/
|
|
483
578
|
async function isSymlink(filePath) {
|
|
484
579
|
try {
|
|
485
580
|
return (await lstat(filePath)).isSymbolicLink();
|
|
@@ -509,13 +604,13 @@ async function isSymlink(filePath) {
|
|
|
509
604
|
* explicitly materialises the section even
|
|
510
605
|
* with --output — the test/CI seam.
|
|
511
606
|
* @param {(msg: string) => void} [options.warn] Warning sink, also collected in `warnings`.
|
|
512
|
-
* @returns {{ output: string, marketplaceRoot: string|null, manifestPath: string|null,
|
|
513
|
-
* marketplaceManifest: object|null, agents: string[],
|
|
607
|
+
* @returns {Promise<{ output: string, marketplaceRoot: string|null, manifestPath: string|null,
|
|
608
|
+
* marketplaceManifest: object|null, agents: string[],
|
|
514
609
|
* skills: {names: string[], md: number, files: number, dir: string},
|
|
515
610
|
* hooks: boolean, hookFiles: string[], handAuthored: string[],
|
|
516
611
|
* settings: {path: string, action: string, removedSymlink: boolean}|null,
|
|
517
612
|
* instructions: {path: string, action: string, removedSymlink: boolean}|null,
|
|
518
|
-
* warnings: string[], dryRun: boolean }}
|
|
613
|
+
* pluginVersion: string, warnings: string[], dryRun: boolean }>}
|
|
519
614
|
*/
|
|
520
615
|
export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = false, pluginRoot, settingsPath, instructionsPath, warn = DEFAULT_WARN } = {}) {
|
|
521
616
|
const tokenMap = await loadTokenMap(root);
|
|
@@ -525,8 +620,8 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
525
620
|
);
|
|
526
621
|
}
|
|
527
622
|
|
|
528
|
-
const warnings = [];
|
|
529
|
-
const warnSink = (msg) => {
|
|
623
|
+
const warnings = /** @type {string[]} */ ([]);
|
|
624
|
+
const warnSink = (/** @type {string} */ msg) => {
|
|
530
625
|
warnings.push(msg);
|
|
531
626
|
warn(msg);
|
|
532
627
|
};
|
|
@@ -536,9 +631,9 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
536
631
|
// --output is generate-only (tests/CI), matching its no-registration semantics.
|
|
537
632
|
const isDefaultInstall = output === undefined;
|
|
538
633
|
const marketplaceRoot = isDefaultInstall ? dirname(out) : null;
|
|
539
|
-
const manifestPath = isDefaultInstall ? join(marketplaceRoot, ".claude-plugin", "marketplace.json") : null;
|
|
634
|
+
const manifestPath = isDefaultInstall && marketplaceRoot !== null ? join(marketplaceRoot, ".claude-plugin", "marketplace.json") : null;
|
|
540
635
|
|
|
541
|
-
const rootPlugin = pluginRoot ?? tokenMap.paths.plugin_root?.[CLAUDE];
|
|
636
|
+
const rootPlugin = pluginRoot ?? /** @type {string | undefined} */ (tokenMap.paths.plugin_root?.[CLAUDE]);
|
|
542
637
|
|
|
543
638
|
// Read hand-authored extras from the canonical claude-extras/ dir BEFORE
|
|
544
639
|
// the cleanup below (defensive: the output is normally ~/.agent-cortex/claude
|
|
@@ -574,7 +669,6 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
574
669
|
: null;
|
|
575
670
|
|
|
576
671
|
const agents = await buildAgents(root);
|
|
577
|
-
const natives = await buildNatives(root, join(root, "agents-native"));
|
|
578
672
|
const skillOut = join(out, "skills");
|
|
579
673
|
const skills = await buildSkills(root, skillOut, { dryRun, pluginRoot: rootPlugin, tokenMap, warn: warnSink });
|
|
580
674
|
const pluginJson = await buildPluginJson(root);
|
|
@@ -590,7 +684,6 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
590
684
|
marketplaceManifest,
|
|
591
685
|
pluginVersion: JSON.parse(pluginJson).version,
|
|
592
686
|
agents: agents.files.map((f) => f.file.replace(/\.md$/, "")),
|
|
593
|
-
natives: natives.map((f) => f.file.replace(/\.md$/, "")),
|
|
594
687
|
skills: { names: skills.names, md: skills.md, files: skills.files, dir: skillOut },
|
|
595
688
|
hooks: hooksJson !== null,
|
|
596
689
|
hookFiles: hookFiles.map((f) => f.rel),
|
|
@@ -601,12 +694,9 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
601
694
|
dryRun,
|
|
602
695
|
};
|
|
603
696
|
|
|
604
|
-
if (agents.skipped.length) console.log(`Skipped composable source(s): ${agents.skipped.join(", ")}`);
|
|
605
|
-
|
|
606
697
|
if (dryRun) {
|
|
607
698
|
console.log(`DRY-RUN — would generate into ${out}, no files written:`);
|
|
608
699
|
console.log(` agents: ${summary.agents.join(", ")}`);
|
|
609
|
-
if (summary.natives.length) console.log(` native agents: ${summary.natives.join(", ")}`);
|
|
610
700
|
console.log(` skills: ${summary.skills.names.length} copied → ${skillOut}/<name>/ (token-substituted)`);
|
|
611
701
|
console.log(` .claude-plugin/plugin.json (version ${JSON.parse(pluginJson).version})`);
|
|
612
702
|
if (isDefaultInstall && manifestPath) {
|
|
@@ -629,11 +719,10 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
629
719
|
console.log(`Materialised CLAUDE.md section → ${instructions.path} (${instructions.action})`);
|
|
630
720
|
const agentOut = join(out, "agents");
|
|
631
721
|
await mkdir(agentOut, { recursive: true });
|
|
632
|
-
for (const { file, content } of
|
|
722
|
+
for (const { file, content } of agents.files) {
|
|
633
723
|
await writeFile(join(agentOut, file), content);
|
|
634
724
|
}
|
|
635
725
|
console.log(`Generated ${summary.agents.length} Claude agent(s): ${summary.agents.join(", ")}`);
|
|
636
|
-
if (summary.natives.length) console.log(`Copied ${summary.natives.length} native Claude agent(s): ${summary.natives.join(", ")}`);
|
|
637
726
|
|
|
638
727
|
await mkdir(skillOut, { recursive: true });
|
|
639
728
|
console.log(
|
|
@@ -651,7 +740,7 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
651
740
|
console.log(`Wrote ${manifestPath} (plugin agent-cortex @ ./${basename(out)})`);
|
|
652
741
|
}
|
|
653
742
|
|
|
654
|
-
if (
|
|
743
|
+
if (hooksJson !== null) {
|
|
655
744
|
await writeFile(join(out, "hooks.json"), hooksJson);
|
|
656
745
|
console.log(`Wrote ${join(out, "hooks.json")} (from ${hooksSrc})`);
|
|
657
746
|
|
|
@@ -686,8 +775,13 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
686
775
|
// The `claude` CLI on PATH. Overridable in tests via the claudeBin option.
|
|
687
776
|
const DEFAULT_CLAUDE_BIN = "claude";
|
|
688
777
|
|
|
689
|
-
/**
|
|
690
|
-
*
|
|
778
|
+
/**
|
|
779
|
+
* Validate a marketplace manifest object; returns the marketplace and plugin
|
|
780
|
+
* names that `claude plugin` commands address.
|
|
781
|
+
* @param {any} manifest
|
|
782
|
+
* @param {string} label
|
|
783
|
+
* @returns {{ marketplace: string, plugin: string }}
|
|
784
|
+
*/
|
|
691
785
|
function parseMarketplaceManifest(manifest, label) {
|
|
692
786
|
const marketplace = manifest.name;
|
|
693
787
|
const plugin = manifest.plugins?.[0]?.name;
|
|
@@ -702,6 +796,8 @@ function parseMarketplaceManifest(manifest, label) {
|
|
|
702
796
|
/**
|
|
703
797
|
* Read the marketplace manifest that exposes the plugin at install root
|
|
704
798
|
* (<root>/.claude-plugin/marketplace.json).
|
|
799
|
+
* @param {string} root
|
|
800
|
+
* @returns {Promise<{ manifest: any, path: string }>}
|
|
705
801
|
*/
|
|
706
802
|
async function readMarketplaceManifest(root) {
|
|
707
803
|
const manifestPath = join(root, ".claude-plugin", "marketplace.json");
|
|
@@ -710,7 +806,7 @@ async function readMarketplaceManifest(root) {
|
|
|
710
806
|
manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
711
807
|
} catch (err) {
|
|
712
808
|
throw new Error(
|
|
713
|
-
`no Claude marketplace manifest at ${manifestPath} (${err.message}) — ` +
|
|
809
|
+
`no Claude marketplace manifest at ${manifestPath} (${/** @type {Error} */ (err).message}) — ` +
|
|
714
810
|
`"agent-cortex install claude" registers the plugin by pointing Claude Code at the install root`,
|
|
715
811
|
);
|
|
716
812
|
}
|
|
@@ -722,6 +818,9 @@ async function readMarketplaceManifest(root) {
|
|
|
722
818
|
* where <source> is the marketplace manifest's plugin source) — the signature a
|
|
723
819
|
* directory-source marketplace exposes to `claude plugin`. Read when the version
|
|
724
820
|
* is not passed explicitly; undefined when it cannot be determined.
|
|
821
|
+
* @param {string} root
|
|
822
|
+
* @param {any} manifest
|
|
823
|
+
* @returns {Promise<string | undefined>}
|
|
725
824
|
*/
|
|
726
825
|
async function readMaterialisedPluginVersion(root, manifest) {
|
|
727
826
|
const source = manifest.plugins?.[0]?.source;
|
|
@@ -734,7 +833,10 @@ async function readMaterialisedPluginVersion(root, manifest) {
|
|
|
734
833
|
}
|
|
735
834
|
}
|
|
736
835
|
|
|
737
|
-
/** Run one claude plugin command, inheriting its output; rejects on failure.
|
|
836
|
+
/** Run one claude plugin command, inheriting its output; rejects on failure.
|
|
837
|
+
* @param {string} claudeBin
|
|
838
|
+
* @param {string[]} args
|
|
839
|
+
* @returns {Promise<void>} */
|
|
738
840
|
function runClaudeCommand(claudeBin, args) {
|
|
739
841
|
const label = `${claudeBin} ${args.join(" ")}`;
|
|
740
842
|
return new Promise((resolve, reject) => {
|
|
@@ -753,6 +855,8 @@ function runClaudeCommand(claudeBin, args) {
|
|
|
753
855
|
* `claude plugin --help` with output suppressed. Resolves `{ ok: true }` when
|
|
754
856
|
* it exits 0, otherwise `{ ok: false, message }` naming the failure (missing
|
|
755
857
|
* binary vs missing subcommand) so registration can give targeted guidance.
|
|
858
|
+
* @param {string} claudeBin
|
|
859
|
+
* @returns {Promise<{ ok: true } | { ok: false, message: string }>}
|
|
756
860
|
*/
|
|
757
861
|
function probePluginSupport(claudeBin) {
|
|
758
862
|
return new Promise((resolve) => {
|
|
@@ -776,7 +880,12 @@ function probePluginSupport(claudeBin) {
|
|
|
776
880
|
});
|
|
777
881
|
}
|
|
778
882
|
|
|
779
|
-
/**
|
|
883
|
+
/**
|
|
884
|
+
* Run a claude plugin command capturing stdout; null when the command fails.
|
|
885
|
+
* @param {string} claudeBin
|
|
886
|
+
* @param {string[]} args
|
|
887
|
+
* @returns {Promise<string | null>}
|
|
888
|
+
*/
|
|
780
889
|
function queryClaudeCommand(claudeBin, args) {
|
|
781
890
|
const label = `${claudeBin} ${args.join(" ")}`;
|
|
782
891
|
return new Promise((resolve, reject) => {
|
|
@@ -796,6 +905,9 @@ function queryClaudeCommand(claudeBin, args) {
|
|
|
796
905
|
* Whether `claude plugin marketplace list` already registers `marketplace`.
|
|
797
906
|
* Unparseable output counts as not-registered: the fallback `marketplace add`
|
|
798
907
|
* is itself idempotent (the CLI reports "already on disk", exit 0).
|
|
908
|
+
* @param {string} claudeBin
|
|
909
|
+
* @param {string} marketplace
|
|
910
|
+
* @returns {Promise<boolean>}
|
|
799
911
|
*/
|
|
800
912
|
async function marketplaceRegistered(claudeBin, marketplace) {
|
|
801
913
|
const out = await queryClaudeCommand(claudeBin, ["plugin", "marketplace", "list", "--json"]);
|
|
@@ -812,6 +924,9 @@ async function marketplaceRegistered(claudeBin, marketplace) {
|
|
|
812
924
|
* Installed version of `pluginId` per `claude plugin list --json`; null when
|
|
813
925
|
* not installed or the output is unreadable (the caller falls back to the
|
|
814
926
|
* idempotent `plugin install`).
|
|
927
|
+
* @param {string} claudeBin
|
|
928
|
+
* @param {string} pluginId
|
|
929
|
+
* @returns {Promise<string | null>}
|
|
815
930
|
*/
|
|
816
931
|
async function installedPluginVersion(claudeBin, pluginId) {
|
|
817
932
|
const out = await queryClaudeCommand(claudeBin, ["plugin", "list", "--json"]);
|
|
@@ -861,8 +976,7 @@ async function installedPluginVersion(claudeBin, pluginId) {
|
|
|
861
976
|
* @param {string} [options.pluginVersion] Materialised plugin version (default: read from plugin.json)
|
|
862
977
|
* @param {boolean} [options.requireRegister] Exit non-zero when registration cannot run
|
|
863
978
|
* @param {(msg: string) => void} [options.warn] Warning sink (default: console.warn)
|
|
864
|
-
* @returns {
|
|
865
|
-
* dryRun: boolean, registered: boolean, skipped: string[] }}
|
|
979
|
+
* @returns {Promise<RegisterClaudeResult>}
|
|
866
980
|
*/
|
|
867
981
|
export async function registerClaude({
|
|
868
982
|
root = PACKAGE_ROOT,
|
|
@@ -880,7 +994,7 @@ export async function registerClaude({
|
|
|
880
994
|
const version = pluginVersion ?? (await readMaterialisedPluginVersion(root, manifestObj));
|
|
881
995
|
const home = resolve(root);
|
|
882
996
|
|
|
883
|
-
const summary = { marketplace, plugin, commands: [], dryRun, registered: true, skipped: [] };
|
|
997
|
+
const summary = /** @type {RegisterClaudeResult} */ ({ marketplace, plugin, commands: [], dryRun, registered: true, skipped: [] });
|
|
884
998
|
console.log(`Registering plugin "${pluginId}" with Claude Code (via ${claudeBin})…`);
|
|
885
999
|
|
|
886
1000
|
const manualCommands = [
|