auriga-cli 1.18.5 → 1.20.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/README.md +15 -23
- package/README.zh-CN.md +14 -23
- package/dist/api-types.d.ts +11 -25
- package/dist/apply-handlers.js +11 -9
- package/dist/catalog.d.ts +3 -19
- package/dist/catalog.json +21 -28
- package/dist/cli.js +49 -6
- package/dist/guide.js +10 -9
- package/dist/help.js +1 -1
- package/dist/hooks.js +6 -1
- package/dist/plugins.js +182 -7
- package/dist/scan-catalog.js +11 -87
- package/dist/server.js +1 -1
- package/dist/skills.js +0 -3
- package/dist/state.d.ts +15 -34
- package/dist/state.js +85 -399
- package/dist/utils.d.ts +1 -0
- package/dist/workflow.js +32 -4
- package/package.json +3 -3
package/dist/guide.js
CHANGED
|
@@ -20,7 +20,7 @@ export function renderGuide(opts) {
|
|
|
20
20
|
return `${h(`# auriga-cli bootstrap SOP (v${opts.version})`)}
|
|
21
21
|
|
|
22
22
|
This guide walks an Agent through installing the auriga harness
|
|
23
|
-
(CLAUDE.md + skills + plugins
|
|
23
|
+
(CLAUDE.md + skills + plugins, plus any legacy hooks) into the current repository.
|
|
24
24
|
|
|
25
25
|
Run each step in order. If any step fails with exit 1, stop and report.
|
|
26
26
|
If exit 2, see stderr for per-category status and follow the "Retry"
|
|
@@ -62,8 +62,8 @@ Per-type detail (flags + only that category's catalog slice):
|
|
|
62
62
|
|
|
63
63
|
${h("## Step 3 — Install")}
|
|
64
64
|
|
|
65
|
-
Preset — the full default-on set (workflow + skills + plugins
|
|
66
|
-
recommended skills are NOT included):
|
|
65
|
+
Preset — the full default-on set (workflow + skills + default plugins;
|
|
66
|
+
the legacy hooks category is currently empty; recommended skills are NOT included):
|
|
67
67
|
${cmd("npx -y auriga-cli install --all")}
|
|
68
68
|
|
|
69
69
|
Targeted — single category, picking from the catalog surfaced in Step 2:
|
|
@@ -72,10 +72,10 @@ Targeted — single category, picking from the catalog surfaced in Step 2:
|
|
|
72
72
|
${cmd("npx -y auriga-cli install plugins --plugin skill-creator codex --scope user")}
|
|
73
73
|
${cmd("npx -y auriga-cli install plugins --agent codex --plugin session-instructions-loader")}
|
|
74
74
|
|
|
75
|
-
Opt-in
|
|
76
|
-
set because they have side effects
|
|
77
|
-
|
|
78
|
-
${cmd("npx -y auriga-cli install
|
|
75
|
+
Opt-in plugins (\`defaultOn: false\`) are NOT in the default \`install --all\`
|
|
76
|
+
set because they have side effects or platform-specific behavior. For example,
|
|
77
|
+
the macOS notification plugin is explicit opt-in:
|
|
78
|
+
${cmd("npx -y auriga-cli install plugins --plugin auriga-notify")}
|
|
79
79
|
|
|
80
80
|
Opt-in recommended skills (cross-model delegation helpers —
|
|
81
81
|
claude-code-agent, codex-agent):
|
|
@@ -94,7 +94,7 @@ Exit codes:
|
|
|
94
94
|
${h("## Step 4 — Reload session (REQUIRED when installed non-interactively)")}
|
|
95
95
|
|
|
96
96
|
${warn("⚠")} CLAUDE.md, .agents/skills/, .claude/plugins.json, Codex plugin
|
|
97
|
-
config, and hook registrations are loaded at session startup. If you ran
|
|
97
|
+
config, and hook/plugin registrations are loaded at session startup. If you ran
|
|
98
98
|
\`npx -y auriga-cli install\` inside an existing Claude Code or Codex session
|
|
99
99
|
(e.g., \`claude -p\` / \`claude -p --worktree\` / \`codex exec\`), the current session
|
|
100
100
|
will NOT see the new harness.
|
|
@@ -112,7 +112,8 @@ Expected artifacts:
|
|
|
112
112
|
- .agents/skills/<name>/ (one per installed skill)
|
|
113
113
|
- .claude/plugins.json
|
|
114
114
|
- ~/.codex/config.toml (Codex plugin enablement, if Codex plugins selected)
|
|
115
|
-
- .claude/settings.json (updated hook registrations, if
|
|
115
|
+
- .claude/settings.json (updated hook/plugin registrations, if selected)
|
|
116
|
+
- .claude/auriga-notify/ (project notify config, if auriga-notify selected)
|
|
116
117
|
|
|
117
118
|
${h("## Troubleshooting")}
|
|
118
119
|
|
package/dist/help.js
CHANGED
|
@@ -125,7 +125,7 @@ USAGE
|
|
|
125
125
|
|
|
126
126
|
FLAGS
|
|
127
127
|
--plugin <names...> space-separated; '*' = all
|
|
128
|
-
omit → install every plugin
|
|
128
|
+
omit → install every plugin with defaultOn != false
|
|
129
129
|
--agent <...> target runtime: claude, codex, or both
|
|
130
130
|
default claude; codex enablement is user-level
|
|
131
131
|
--scope <project|user> default project
|
package/dist/hooks.js
CHANGED
|
@@ -687,7 +687,12 @@ export async function installHooks(packageRoot, opts) {
|
|
|
687
687
|
const config = loadHooksConfig(packageRoot);
|
|
688
688
|
const compatible = config.hooks.filter((h) => h.runtimePlatforms.includes(process.platform));
|
|
689
689
|
if (compatible.length === 0) {
|
|
690
|
-
|
|
690
|
+
if (config.hooks.length === 0) {
|
|
691
|
+
log.warn("No legacy hooks are defined. The notify hook moved to the opt-in auriga-notify plugin.");
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
log.warn(`No hooks available for your platform (${process.platform}). Skipping.`);
|
|
695
|
+
}
|
|
691
696
|
return;
|
|
692
697
|
}
|
|
693
698
|
// Non-interactive explicit `--hook <name>` has stronger intent than
|
package/dist/plugins.js
CHANGED
|
@@ -15,6 +15,14 @@ import { atomicWriteFile, exec, execAsync, fetchExtraContent, log, withEsc } fro
|
|
|
15
15
|
// `./marketplace.js` so Claude and Codex sides share one validator.
|
|
16
16
|
const PLUGIN_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
17
17
|
const PLUGIN_PACKAGE_RE = /^[A-Za-z0-9][A-Za-z0-9._@/-]{0,255}$/;
|
|
18
|
+
const MIGRATED_WORKFLOW_SKILLS = [
|
|
19
|
+
"incremental-impl",
|
|
20
|
+
"test-designer",
|
|
21
|
+
"session-compound",
|
|
22
|
+
];
|
|
23
|
+
const NOTIFY_PLUGIN_NAME = "auriga-notify";
|
|
24
|
+
const WORKFLOW_SKILLS_PLUGIN_NAME = "auriga-workflow-skills";
|
|
25
|
+
const LEGACY_NOTIFY_MARKER = "auriga:notify";
|
|
18
26
|
export function validatePluginsConfig(raw) {
|
|
19
27
|
if (!raw || typeof raw !== "object") {
|
|
20
28
|
throw new Error("plugins.json: root must be an object");
|
|
@@ -37,6 +45,9 @@ export function validatePluginsConfig(raw) {
|
|
|
37
45
|
if (plugin.marketplace !== undefined) {
|
|
38
46
|
validateMarketplaceField(`plugins.json: plugins[${i}]`, plugin.marketplace);
|
|
39
47
|
}
|
|
48
|
+
if (plugin.defaultOn !== undefined && typeof plugin.defaultOn !== "boolean") {
|
|
49
|
+
throw new Error(`plugins.json: plugins[${i}].defaultOn must be a boolean`);
|
|
50
|
+
}
|
|
40
51
|
});
|
|
41
52
|
}
|
|
42
53
|
function getInstalledPlugins() {
|
|
@@ -60,12 +71,14 @@ function getInstalledPlugins() {
|
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
73
|
/**
|
|
63
|
-
* Non-interactive selection resolver for plugins.
|
|
64
|
-
*
|
|
65
|
-
* filter. CLI parser validates names up-front.
|
|
74
|
+
* Non-interactive selection resolver for plugins.
|
|
75
|
+
* `undefined` = default-on set; `["*"]` = full set; explicit names =
|
|
76
|
+
* exact filter. CLI parser validates names up-front.
|
|
66
77
|
*/
|
|
67
78
|
function resolvePluginSelection(all, selected) {
|
|
68
|
-
if (!selected
|
|
79
|
+
if (!selected)
|
|
80
|
+
return all.filter((p) => p.defaultOn !== false);
|
|
81
|
+
if (selected.length === 1 && selected[0] === "*")
|
|
69
82
|
return all;
|
|
70
83
|
const byName = new Map(all.map((p) => [p.name, p]));
|
|
71
84
|
const missing = selected.filter((name) => !byName.has(name));
|
|
@@ -121,6 +134,164 @@ function codexHome() {
|
|
|
121
134
|
function shellQuote(value) {
|
|
122
135
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
123
136
|
}
|
|
137
|
+
function installTargetCwd(opts) {
|
|
138
|
+
return path.resolve(opts.cwd ?? process.cwd());
|
|
139
|
+
}
|
|
140
|
+
function emitMigrationLog(opts, line) {
|
|
141
|
+
opts.onLog?.(line, "stdout");
|
|
142
|
+
}
|
|
143
|
+
function runtimeSkillRoot(runtime) {
|
|
144
|
+
return runtime === "claude" ? ".claude" : ".agents";
|
|
145
|
+
}
|
|
146
|
+
function legacySkillDir(opts, runtime, name) {
|
|
147
|
+
const cwd = installTargetCwd(opts);
|
|
148
|
+
const scope = opts.scope ?? "project";
|
|
149
|
+
const baseDir = scope === "user" ? os.homedir() : cwd;
|
|
150
|
+
return path.join(baseDir, runtimeSkillRoot(runtime), "skills", name);
|
|
151
|
+
}
|
|
152
|
+
function isWorkflowPluginDevSymlink(skillPath, cwd, name) {
|
|
153
|
+
const stat = fs.lstatSync(skillPath, { throwIfNoEntry: false });
|
|
154
|
+
if (!stat?.isSymbolicLink())
|
|
155
|
+
return false;
|
|
156
|
+
const target = fs.readlinkSync(skillPath);
|
|
157
|
+
const resolved = path.resolve(path.dirname(skillPath), target);
|
|
158
|
+
const expected = path.resolve(cwd, "plugins", "auriga-workflow-skills", "skills", name);
|
|
159
|
+
return resolved === expected;
|
|
160
|
+
}
|
|
161
|
+
function removeMigratedSkillFromLock(cwd, name, opts) {
|
|
162
|
+
const lockPath = path.join(cwd, "skills-lock.json");
|
|
163
|
+
if (!fs.existsSync(lockPath))
|
|
164
|
+
return;
|
|
165
|
+
const raw = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
166
|
+
if (!raw.skills || typeof raw.skills !== "object" || !(name in raw.skills))
|
|
167
|
+
return;
|
|
168
|
+
const nextSkills = { ...raw.skills };
|
|
169
|
+
delete nextSkills[name];
|
|
170
|
+
atomicWriteFile(lockPath, JSON.stringify({ ...raw, skills: nextSkills }, null, 2) + "\n");
|
|
171
|
+
emitMigrationLog(opts, `removed ${name} from skills-lock.json`);
|
|
172
|
+
}
|
|
173
|
+
function cleanupMigratedWorkflowSkillInstalls(opts, runtimes) {
|
|
174
|
+
const cwd = installTargetCwd(opts);
|
|
175
|
+
const scope = opts.scope ?? "project";
|
|
176
|
+
for (const name of MIGRATED_WORKFLOW_SKILLS) {
|
|
177
|
+
for (const runtime of runtimes) {
|
|
178
|
+
const dir = legacySkillDir(opts, runtime, name);
|
|
179
|
+
const stat = fs.lstatSync(dir, { throwIfNoEntry: false });
|
|
180
|
+
if (!stat) {
|
|
181
|
+
emitMigrationLog(opts, `${runtimeSkillRoot(runtime)}/skills/${name} not present`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (scope === "project" && isWorkflowPluginDevSymlink(dir, cwd, name)) {
|
|
185
|
+
emitMigrationLog(opts, `preserved ${runtimeSkillRoot(runtime)}/skills/${name} development symlink`);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
189
|
+
emitMigrationLog(opts, `removed ${runtimeSkillRoot(runtime)}/skills/${name}`);
|
|
190
|
+
}
|
|
191
|
+
if (scope === "project")
|
|
192
|
+
removeMigratedSkillFromLock(cwd, name, opts);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function copyIfPresentWithoutOverwrite(src, dest) {
|
|
196
|
+
if (!fs.existsSync(src) || fs.existsSync(dest))
|
|
197
|
+
return false;
|
|
198
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
199
|
+
fs.copyFileSync(src, dest);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
function removeMarkerFromSettings(settings, marker) {
|
|
203
|
+
const next = JSON.parse(JSON.stringify(settings ?? {}));
|
|
204
|
+
if (!next.hooks || typeof next.hooks !== "object" || Array.isArray(next.hooks)) {
|
|
205
|
+
return { settings: next, removed: 0 };
|
|
206
|
+
}
|
|
207
|
+
let removed = 0;
|
|
208
|
+
for (const event of Object.keys(next.hooks)) {
|
|
209
|
+
const groups = next.hooks[event];
|
|
210
|
+
if (!Array.isArray(groups))
|
|
211
|
+
continue;
|
|
212
|
+
const nextGroups = [];
|
|
213
|
+
for (const group of groups) {
|
|
214
|
+
if (!Array.isArray(group.hooks)) {
|
|
215
|
+
nextGroups.push(group);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const hooks = group.hooks.filter((action) => {
|
|
219
|
+
if (action?._marker === marker) {
|
|
220
|
+
removed += 1;
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
});
|
|
225
|
+
if (hooks.length > 0)
|
|
226
|
+
nextGroups.push({ ...group, hooks });
|
|
227
|
+
}
|
|
228
|
+
if (nextGroups.length > 0)
|
|
229
|
+
next.hooks[event] = nextGroups;
|
|
230
|
+
else
|
|
231
|
+
delete next.hooks[event];
|
|
232
|
+
}
|
|
233
|
+
return { settings: next, removed };
|
|
234
|
+
}
|
|
235
|
+
function cleanLegacyNotifySettings(settingsPaths, opts) {
|
|
236
|
+
let allReadable = true;
|
|
237
|
+
for (const settingsPath of settingsPaths) {
|
|
238
|
+
if (!fs.existsSync(settingsPath))
|
|
239
|
+
continue;
|
|
240
|
+
let parsed;
|
|
241
|
+
try {
|
|
242
|
+
parsed = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
emitMigrationLog(opts, `skipped unreadable legacy notify settings: ${settingsPath}`);
|
|
246
|
+
allReadable = false;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const result = removeMarkerFromSettings(parsed, LEGACY_NOTIFY_MARKER);
|
|
250
|
+
if (result.removed === 0)
|
|
251
|
+
continue;
|
|
252
|
+
atomicWriteFile(settingsPath, JSON.stringify(result.settings, null, 2) + "\n");
|
|
253
|
+
emitMigrationLog(opts, `removed ${result.removed} legacy notify settings entries from ${settingsPath}`);
|
|
254
|
+
}
|
|
255
|
+
return allReadable;
|
|
256
|
+
}
|
|
257
|
+
function migrateLegacyNotifyConfig(opts) {
|
|
258
|
+
const scope = opts.scope ?? "project";
|
|
259
|
+
const cwd = installTargetCwd(opts);
|
|
260
|
+
const home = os.homedir();
|
|
261
|
+
const legacyBase = scope === "user" ? home : cwd;
|
|
262
|
+
const legacyDir = path.join(legacyBase, ".claude", "hooks", "notify");
|
|
263
|
+
const destDir = scope === "user"
|
|
264
|
+
? path.join(home, ".config", "auriga-cli", "notify")
|
|
265
|
+
: path.join(cwd, ".claude", "auriga-notify");
|
|
266
|
+
const copiedConfig = copyIfPresentWithoutOverwrite(path.join(legacyDir, "config.json"), path.join(destDir, "config.json"));
|
|
267
|
+
const copiedIcon = copyIfPresentWithoutOverwrite(path.join(legacyDir, "icon.png"), path.join(destDir, "icon.png"));
|
|
268
|
+
if (copiedConfig)
|
|
269
|
+
emitMigrationLog(opts, `migrated legacy notify config to ${path.join(destDir, "config.json")}`);
|
|
270
|
+
if (copiedIcon)
|
|
271
|
+
emitMigrationLog(opts, `migrated legacy notify icon to ${path.join(destDir, "icon.png")}`);
|
|
272
|
+
const settingsPaths = scope === "user"
|
|
273
|
+
? [path.join(home, ".claude", "settings.json")]
|
|
274
|
+
: [
|
|
275
|
+
path.join(cwd, ".claude", "settings.json"),
|
|
276
|
+
path.join(cwd, ".claude", "settings.local.json"),
|
|
277
|
+
];
|
|
278
|
+
const settingsCleaned = cleanLegacyNotifySettings(settingsPaths, opts);
|
|
279
|
+
if (settingsCleaned && fs.existsSync(legacyDir)) {
|
|
280
|
+
fs.rmSync(legacyDir, { recursive: true, force: true });
|
|
281
|
+
emitMigrationLog(opts, `removed legacy notify hook directory ${legacyDir}`);
|
|
282
|
+
}
|
|
283
|
+
else if (!settingsCleaned && fs.existsSync(legacyDir)) {
|
|
284
|
+
emitMigrationLog(opts, `kept legacy notify hook directory because settings cleanup was incomplete: ${legacyDir}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function runPostInstallMigration(pluginName, opts, runtimes) {
|
|
288
|
+
if (pluginName === WORKFLOW_SKILLS_PLUGIN_NAME) {
|
|
289
|
+
cleanupMigratedWorkflowSkillInstalls(opts, runtimes);
|
|
290
|
+
}
|
|
291
|
+
if (pluginName === NOTIFY_PLUGIN_NAME) {
|
|
292
|
+
migrateLegacyNotifyConfig(opts);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
124
295
|
function codexMarketplaceAddCommand(packageRoot) {
|
|
125
296
|
if (process.env.DEV === "1") {
|
|
126
297
|
return `codex plugin marketplace add ${shellQuote(packageRoot)}`;
|
|
@@ -401,6 +572,7 @@ async function installCodexPlugins(packageRoot, opts) {
|
|
|
401
572
|
enableCodexPluginConfig(path.join(codexHome(), "config.toml"), pluginKeys, needsPluginHooks);
|
|
402
573
|
for (const plugin of [...localSelected, ...externalSelected]) {
|
|
403
574
|
log.ok(`${plugin.name} enabled for Codex`);
|
|
575
|
+
runPostInstallMigration(plugin.name, opts, ["codex"]);
|
|
404
576
|
}
|
|
405
577
|
}
|
|
406
578
|
if (failures.length > 0 && !opts.interactive) {
|
|
@@ -490,10 +662,11 @@ export async function installPlugins(packageRoot, opts) {
|
|
|
490
662
|
choices: config.plugins.map((p) => {
|
|
491
663
|
const scopes = installed.get(p.package);
|
|
492
664
|
const suffix = scopes ? ` (installed: ${scopes.join(", ")})` : "";
|
|
665
|
+
const installedEverywhere = scopes?.includes("user") && scopes?.includes("project");
|
|
493
666
|
return {
|
|
494
667
|
name: `${p.name} — ${p.description}${suffix}`,
|
|
495
668
|
value: p,
|
|
496
|
-
checked:
|
|
669
|
+
checked: p.defaultOn !== false && !installedEverywhere,
|
|
497
670
|
};
|
|
498
671
|
}),
|
|
499
672
|
}))
|
|
@@ -572,14 +745,16 @@ export async function installPlugins(packageRoot, opts) {
|
|
|
572
745
|
console.log(`\nInstalling ${plugin.name}...`);
|
|
573
746
|
try {
|
|
574
747
|
const cmd = `claude plugins install ${plugin.package} --scope ${scope}`;
|
|
748
|
+
const cmdOpts = { cwd: installTargetCwd(opts) };
|
|
575
749
|
if (opts.onLog) {
|
|
576
750
|
opts.onLog(`▸ ${cmd}`, "stdout");
|
|
577
|
-
await execAsync(cmd, { onLine: opts.onLog });
|
|
751
|
+
await execAsync(cmd, { ...cmdOpts, onLine: opts.onLog });
|
|
578
752
|
}
|
|
579
753
|
else {
|
|
580
|
-
exec(cmd, { inherit: true });
|
|
754
|
+
exec(cmd, { ...cmdOpts, inherit: true });
|
|
581
755
|
}
|
|
582
756
|
log.ok(`${plugin.name} installed`);
|
|
757
|
+
runPostInstallMigration(plugin.name, { ...opts, scope }, ["claude"]);
|
|
583
758
|
}
|
|
584
759
|
catch {
|
|
585
760
|
log.error(`Failed to install: ${plugin.name}`);
|
package/dist/scan-catalog.js
CHANGED
|
@@ -1,53 +1,20 @@
|
|
|
1
1
|
// Build the scan-time Catalog (the shape src/state.ts consumes) from the
|
|
2
|
-
// build-time `dist/catalog.json`.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// `packageRoot/.agents/skills/<name>/SKILL.md` succeeds in dev (where
|
|
7
|
-
// packageRoot === repoRoot) but silently returns empty for npm-installed
|
|
8
|
-
// users, leaving the scanner unable to surface real update signals.
|
|
9
|
-
//
|
|
10
|
-
// Anything the scanner needs beyond what's already in catalog.json must
|
|
11
|
-
// first be baked at build time in `src/build/generate-catalog.ts`.
|
|
12
|
-
//
|
|
13
|
-
// Scope of the current bake (covered fields):
|
|
14
|
-
// - workflowVersion — from CLAUDE.md header
|
|
15
|
-
// - plugin agents map — from .claude/plugins.json ∪ .agents/plugins/install.json
|
|
16
|
-
// - plugin expectedVersion — from plugins/<name>/.claude-plugin/plugin.json
|
|
17
|
-
// - plugin external flag — derived (no in-tree manifest = external)
|
|
18
|
-
//
|
|
19
|
-
// Out of scope for v1.18.4 (follow-up PRs):
|
|
20
|
-
// - hook expectedEvent / expectedMatcher / expectedIf (from .claude/hooks/hooks.json)
|
|
21
|
-
// - apply-time installer config (the install path reads .claude/plugins.json
|
|
22
|
-
// directly — that needs runWebUi → fetchContentRoot rewire, not bake).
|
|
23
|
-
import { readFile } from "node:fs/promises";
|
|
24
|
-
import path from "node:path";
|
|
2
|
+
// build-time `dist/catalog.json`. Thin adapter — reads dist/catalog.json
|
|
3
|
+
// only. v1.19.0 dropped all version / hash / event comparison from the
|
|
4
|
+
// scanner, so the build-time catalog is reduced to {description, agents?,
|
|
5
|
+
// external?} per entry; runtime reads outside dist/ are no longer needed.
|
|
25
6
|
import { loadCatalog } from "./catalog.js";
|
|
26
|
-
async function tryReadFile(p) {
|
|
27
|
-
try {
|
|
28
|
-
return await readFile(p, "utf8");
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
7
|
export async function buildScanCatalog(packageRoot) {
|
|
35
8
|
const dist = loadCatalog(packageRoot);
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
// repo HEAD. Our catalog snapshot would only know "what auriga-cli
|
|
42
|
-
// shipped at this CLI release" — at best a stale proxy that mis-reports
|
|
43
|
-
// legitimate user-side updates as drift. Setting expectedHash to "" puts
|
|
44
|
-
// classifySkillByFile into wildcard mode: row reports installed if
|
|
45
|
-
// SKILL.md exists, not-installed otherwise; never update-available.
|
|
9
|
+
// v1.19.0 dropped update-available status. The scanner is now presence-
|
|
10
|
+
// only: skills / hooks / plugins / workflow all report installed iff their
|
|
11
|
+
// truth source exists, not-installed otherwise. No version / hash / event
|
|
12
|
+
// comparison happens, so the build-time catalog is reduced to the bare
|
|
13
|
+
// {description, agents?, external?} shape per entry.
|
|
46
14
|
const skills = {};
|
|
47
15
|
for (const entry of dist.workflowSkills) {
|
|
48
16
|
skills[entry.name] = {
|
|
49
17
|
description: entry.description,
|
|
50
|
-
expectedHash: "",
|
|
51
18
|
isWorkflow: true,
|
|
52
19
|
};
|
|
53
20
|
}
|
|
@@ -55,16 +22,8 @@ export async function buildScanCatalog(packageRoot) {
|
|
|
55
22
|
for (const entry of dist.recommendedSkills) {
|
|
56
23
|
recommendedSkills[entry.name] = {
|
|
57
24
|
description: entry.description,
|
|
58
|
-
expectedHash: "",
|
|
59
25
|
};
|
|
60
26
|
}
|
|
61
|
-
// Plugins: agents + expectedVersion + external all come from
|
|
62
|
-
// dist/catalog.json now (baked in src/build/generate-catalog.ts). The
|
|
63
|
-
// previous version of this module read .claude/plugins.json +
|
|
64
|
-
// .agents/plugins/install.json at runtime — those files are NOT in the
|
|
65
|
-
// npm tarball, so for installed users every plugin defaulted to a
|
|
66
|
-
// ["claude"] agent classification (root cause of dual-Agent plugin
|
|
67
|
-
// mis-classification in v1.18.x).
|
|
68
27
|
const plugins = {};
|
|
69
28
|
for (const entry of dist.plugins) {
|
|
70
29
|
const agents = Array.isArray(entry.agents) && entry.agents.length > 0
|
|
@@ -73,47 +32,12 @@ export async function buildScanCatalog(packageRoot) {
|
|
|
73
32
|
plugins[entry.name] = {
|
|
74
33
|
description: entry.description,
|
|
75
34
|
agents,
|
|
76
|
-
...(typeof entry.expectedVersion === "string" && entry.expectedVersion.length > 0
|
|
77
|
-
? { expectedVersion: entry.expectedVersion }
|
|
78
|
-
: {}),
|
|
79
35
|
...(entry.external === true ? { external: true } : {}),
|
|
80
36
|
};
|
|
81
37
|
}
|
|
82
|
-
// Hooks: TODO follow-up — bake expectedEvent / expectedMatcher / expectedIf
|
|
83
|
-
// into dist/catalog.json the same way agents are baked. Currently the
|
|
84
|
-
// runtime read of packageRoot/.claude/hooks/hooks.json works in dev
|
|
85
|
-
// (packageRoot === repoRoot) but fails silently for npm-installed users
|
|
86
|
-
// — `package.json` `files` allowlist doesn't ship `.claude/`. So hook
|
|
87
|
-
// drift detection is correct in dev and degraded (always "installed" if
|
|
88
|
-
// marker present) in production. Follow-up bake closes the dev/prod gap.
|
|
89
|
-
const hooksJsonPath = path.join(packageRoot, ".claude", "hooks", "hooks.json");
|
|
90
|
-
const hooksJsonRaw = await tryReadFile(hooksJsonPath);
|
|
91
|
-
const hooksJson = hooksJsonRaw ? JSON.parse(hooksJsonRaw) : {};
|
|
92
|
-
const settingsEventsByName = new Map();
|
|
93
|
-
for (const h of hooksJson.hooks ?? []) {
|
|
94
|
-
if (typeof h.name === "string" && h.settingsEvents?.length) {
|
|
95
|
-
settingsEventsByName.set(h.name, h.settingsEvents[0]);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
38
|
const hooks = {};
|
|
99
39
|
for (const entry of dist.hooks) {
|
|
100
|
-
|
|
101
|
-
hooks[entry.name] = {
|
|
102
|
-
description: entry.description,
|
|
103
|
-
// Empty expectedHash flips state.ts into wildcard mode — drift judged
|
|
104
|
-
// purely from the structured expected* fields below, not from a
|
|
105
|
-
// settings-entry content hash.
|
|
106
|
-
expectedHash: "",
|
|
107
|
-
...(typeof ev?.event === "string" ? { expectedEvent: ev.event } : {}),
|
|
108
|
-
...(typeof ev?.matcher === "string" ? { expectedMatcher: ev.matcher } : {}),
|
|
109
|
-
...(typeof ev?.if === "string" ? { expectedIf: ev.if } : {}),
|
|
110
|
-
};
|
|
40
|
+
hooks[entry.name] = { description: entry.description };
|
|
111
41
|
}
|
|
112
|
-
return {
|
|
113
|
-
workflowVersion,
|
|
114
|
-
skills,
|
|
115
|
-
recommendedSkills,
|
|
116
|
-
plugins,
|
|
117
|
-
hooks,
|
|
118
|
-
};
|
|
42
|
+
return { skills, recommendedSkills, plugins, hooks };
|
|
119
43
|
}
|
package/dist/server.js
CHANGED
|
@@ -171,7 +171,7 @@ const VALID_CATEGORIES = new Set([
|
|
|
171
171
|
"plugin",
|
|
172
172
|
"hook",
|
|
173
173
|
]);
|
|
174
|
-
const VALID_ACTIONS = new Set(["install", "
|
|
174
|
+
const VALID_ACTIONS = new Set(["install", "uninstall"]);
|
|
175
175
|
const VALID_SCOPES = new Set(["project", "user"]);
|
|
176
176
|
const VALID_LANGS = new Set(["en", "zh-CN"]);
|
|
177
177
|
function parseApplyRequest(raw) {
|
package/dist/skills.js
CHANGED
|
@@ -8,12 +8,9 @@ import { atomicWriteFile, exec, execAsync, log, withEsc } from "./utils.js";
|
|
|
8
8
|
// installRecommendedSkills as an opt-in utility.
|
|
9
9
|
export const WORKFLOW_SKILLS = [
|
|
10
10
|
"brainstorming",
|
|
11
|
-
"incremental-impl",
|
|
12
11
|
"planning-with-files",
|
|
13
12
|
"playwright-cli",
|
|
14
|
-
"session-compound",
|
|
15
13
|
"systematic-debugging",
|
|
16
|
-
"test-designer",
|
|
17
14
|
"test-driven-development",
|
|
18
15
|
"verification-before-completion",
|
|
19
16
|
];
|
package/dist/state.d.ts
CHANGED
|
@@ -1,57 +1,37 @@
|
|
|
1
1
|
import type { PluginState, ScanScope, StateReport } from "./api-types.js";
|
|
2
2
|
export interface Catalog {
|
|
3
|
-
workflowVersion: string;
|
|
4
3
|
skills: Record<string, {
|
|
5
4
|
description: string;
|
|
6
|
-
expectedHash: string;
|
|
7
5
|
isWorkflow: boolean;
|
|
8
6
|
}>;
|
|
9
7
|
recommendedSkills: Record<string, {
|
|
10
8
|
description: string;
|
|
11
|
-
expectedHash: string;
|
|
12
9
|
}>;
|
|
13
10
|
plugins: Record<string, {
|
|
14
11
|
description: string;
|
|
15
12
|
/** Agents this plugin can install into. Length 1 or 2. */
|
|
16
13
|
agents: ("claude" | "codex")[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* an EXTERNAL badge so users know to defer to the upstream tool. */
|
|
14
|
+
/** True for plugins whose source lives in an upstream marketplace
|
|
15
|
+
* (skill-creator / claude-md-management / codex). Drives the UI's
|
|
16
|
+
* EXTERNAL badge — upgrades go through `claude plugins update`, not us.
|
|
17
|
+
* Pure UI hint since v1.19.0 (used to also gate update-available
|
|
18
|
+
* reporting; that surface was removed). */
|
|
23
19
|
external?: boolean;
|
|
24
20
|
}>;
|
|
25
21
|
hooks: Record<string, {
|
|
26
22
|
description: string;
|
|
27
|
-
/** Coarse drift signal. The current production scan-catalog still
|
|
28
|
-
* populates this with sha256(index.mjs) for back-compat with the v0.x
|
|
29
|
-
* scanner that hashed the user's installed script. The new
|
|
30
|
-
* settings.json-based scanner ignores it for drift comparison unless
|
|
31
|
-
* the catalog also exposes the structured expected* fields below. */
|
|
32
|
-
expectedHash: string;
|
|
33
|
-
/** Settings.json event name (e.g. "PostToolUse", "Notification"). When
|
|
34
|
-
* set, the scanner flags drift if the on-disk settings entry registers
|
|
35
|
-
* under a different event. Optional; left undefined the scanner trusts
|
|
36
|
-
* whatever event the marker was found under. */
|
|
37
|
-
expectedEvent?: string;
|
|
38
|
-
/** Settings.json `matcher` field (e.g. "Write|Edit" for PostToolUse).
|
|
39
|
-
* Empty string means "no matcher" (e.g. Notification hooks). When set,
|
|
40
|
-
* the scanner flags drift if the on-disk value differs. */
|
|
41
|
-
expectedMatcher?: string;
|
|
42
|
-
/** Settings.json `if` field (Claude-Code-specific filter expression).
|
|
43
|
-
* Same drift semantics as expectedMatcher. */
|
|
44
|
-
expectedIf?: string;
|
|
45
23
|
}>;
|
|
46
24
|
}
|
|
47
25
|
export interface ScanOptions {
|
|
48
26
|
/** Run `claude plugins list` for the given scope. The scope argument is
|
|
49
27
|
* required so server.ts can pass --user / --project through to the CLI
|
|
50
28
|
* per opts.scopes.plugins. Implementations may accept a zero-arg legacy
|
|
51
|
-
* form for back-compat but MUST honor a scope argument when given.
|
|
29
|
+
* form for back-compat but MUST honor a scope argument when given.
|
|
30
|
+
*
|
|
31
|
+
* Returns just the installed records — v1.19.0 dropped the parallel
|
|
32
|
+
* `--available` fetch since the scanner no longer compares versions. */
|
|
52
33
|
execPluginList?: (scope: ScanScope) => Promise<{
|
|
53
34
|
installed: any[];
|
|
54
|
-
available: any[];
|
|
55
35
|
}>;
|
|
56
36
|
readCodexConfig?: () => Promise<string | null>;
|
|
57
37
|
readCodexPluginsDir?: () => Promise<Map<string, string>>;
|
|
@@ -73,13 +53,14 @@ export interface ScanOptions {
|
|
|
73
53
|
export declare function scanState(projectRoot: string, catalog: Catalog, opts?: ScanOptions): Promise<StateReport>;
|
|
74
54
|
export declare function mergePluginsById(records: PluginState[]): PluginState[];
|
|
75
55
|
/** Default: run `claude plugins list --json` (no scope flag — the CLI
|
|
76
|
-
* doesn't expose one)
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
56
|
+
* doesn't expose one), then filter the installed records to the requested
|
|
57
|
+
* scope (and current projectRoot for project-scope) client-side. Server.ts
|
|
58
|
+
* decides whether to pass this function based on `which claude`.
|
|
59
|
+
*
|
|
60
|
+
* v1.19.0 dropped the parallel `--available` fetch — the scanner no longer
|
|
61
|
+
* compares versions, so there's no use for the upstream-live ref. */
|
|
80
62
|
export declare function defaultExecPluginList(scope?: ScanScope, projectRoot?: string): Promise<{
|
|
81
63
|
installed: any[];
|
|
82
|
-
available: any[];
|
|
83
64
|
}>;
|
|
84
65
|
export declare function defaultReadCodexConfig(): Promise<string | null>;
|
|
85
66
|
/** Walk `~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/`, returning
|