auriga-cli 1.15.2 → 1.16.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 +12 -0
- package/README.zh-CN.md +12 -0
- package/dist/api-types.d.ts +115 -0
- package/dist/api-types.js +4 -0
- package/dist/apply-handlers.d.ts +17 -0
- package/dist/apply-handlers.js +186 -0
- package/dist/catalog.json +1 -1
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +220 -0
- package/dist/help.js +2 -0
- package/dist/hooks.d.ts +30 -0
- package/dist/hooks.js +89 -0
- package/dist/plugins.d.ts +29 -0
- package/dist/plugins.js +137 -6
- package/dist/scan-catalog.d.ts +2 -0
- package/dist/scan-catalog.js +138 -0
- package/dist/server.d.ts +71 -0
- package/dist/server.js +759 -0
- package/dist/skills.d.ts +29 -0
- package/dist/skills.js +145 -2
- package/dist/state.d.ts +63 -0
- package/dist/state.js +623 -0
- package/dist/ui-fetch.d.ts +29 -0
- package/dist/ui-fetch.js +267 -0
- package/dist/utils.d.ts +22 -0
- package/dist/utils.js +58 -1
- package/dist/workflow.d.ts +22 -0
- package/dist/workflow.js +63 -0
- package/package.json +5 -3
package/dist/skills.d.ts
CHANGED
|
@@ -8,3 +8,32 @@ export declare function planSkillInstallCommands(selected: string[], lock: Skill
|
|
|
8
8
|
}[];
|
|
9
9
|
export declare function installSkills(packageRoot: string, opts: InstallOpts): Promise<void>;
|
|
10
10
|
export declare function installRecommendedSkills(packageRoot: string, opts: InstallOpts): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Uninstall a single skill by name. Strategy:
|
|
13
|
+
* 1. Try `npx -y skills remove <name>` (the canonical path; future-proof
|
|
14
|
+
* against upstream-internal cleanup we don't know about).
|
|
15
|
+
* 2. If the CLI doesn't support `remove`, fall back to manual cleanup:
|
|
16
|
+
* - rm `<cwd>/.claude/skills/<name>` (Claude Code view)
|
|
17
|
+
* - rm `<cwd>/.agents/skills/<name>` (Codex / other agents view)
|
|
18
|
+
* - remove the entry from `<cwd>/skills-lock.json` if present
|
|
19
|
+
*
|
|
20
|
+
* Idempotent: missing files / unknown skill names are no-ops, NOT errors.
|
|
21
|
+
* A repeated uninstall must succeed silently so the SSE caller can replay
|
|
22
|
+
* the request without special-casing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function uninstallSkill(name: string, opts: {
|
|
25
|
+
cwd: string;
|
|
26
|
+
scope?: "project" | "user";
|
|
27
|
+
onLog?: (line: string) => void;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Manual fallback used when `npx skills remove` is unavailable. Exported
|
|
31
|
+
* for test coverage (the exec-success path doesn't exercise it).
|
|
32
|
+
*
|
|
33
|
+
* Steps (each idempotent):
|
|
34
|
+
* - rm-rf `<cwd>/.claude/skills/<name>` if present
|
|
35
|
+
* - rm-rf `<cwd>/.agents/skills/<name>` if present
|
|
36
|
+
* - update `<cwd>/skills-lock.json` (drop the `.skills[name]` key,
|
|
37
|
+
* atomic write) if present and the key exists
|
|
38
|
+
*/
|
|
39
|
+
export declare function uninstallSkillManual(name: string, cwd: string, onLog?: (line: string) => void, scope?: "project" | "user"): Promise<void>;
|
package/dist/skills.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { checkbox, select } from "@inquirer/prompts";
|
|
4
|
-
import { exec, log, withEsc } from "./utils.js";
|
|
5
|
+
import { atomicWriteFile, exec, execAsync, log, withEsc } from "./utils.js";
|
|
5
6
|
// Curated default-on set: skills that the workflow in the root CLAUDE.md
|
|
6
7
|
// directly references. Anything else in skills-lock.json is surfaced via
|
|
7
8
|
// installRecommendedSkills as an opt-in utility.
|
|
@@ -105,7 +106,16 @@ async function installSelected(entries, defaultChecked, opts) {
|
|
|
105
106
|
for (const batch of batches) {
|
|
106
107
|
console.log(`\nInstalling ${batch.skills.join(", ")} from ${batch.source}...`);
|
|
107
108
|
try {
|
|
108
|
-
|
|
109
|
+
if (opts.onLog) {
|
|
110
|
+
// Web UI / non-TTY path — stream stdout/stderr through the per-line
|
|
111
|
+
// callback so SSE subscribers see install progress in real time
|
|
112
|
+
// (spec §6.4).
|
|
113
|
+
opts.onLog(`▸ ${batch.command}`, "stdout");
|
|
114
|
+
await execAsync(batch.command, { onLine: opts.onLog });
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
exec(batch.command, { inherit: true });
|
|
118
|
+
}
|
|
109
119
|
for (const name of batch.skills)
|
|
110
120
|
log.ok(`${name}: installed`);
|
|
111
121
|
}
|
|
@@ -141,3 +151,136 @@ export async function installRecommendedSkills(packageRoot, opts) {
|
|
|
141
151
|
const entries = Object.entries(lock.skills).filter(([name]) => !WORKFLOW_SKILLS.includes(name));
|
|
142
152
|
await installSelected(entries, false, opts);
|
|
143
153
|
}
|
|
154
|
+
// --- Uninstall ----------------------------------------------------------------
|
|
155
|
+
/**
|
|
156
|
+
* Detect "unknown subcommand" style failures from the upstream
|
|
157
|
+
* `npx skills` CLI so we can fall back to the manual cleanup path.
|
|
158
|
+
*
|
|
159
|
+
* Substring match instead of a strict regex: the upstream tool's error
|
|
160
|
+
* wording varies across versions (`Unknown command remove`,
|
|
161
|
+
* `unrecognized command 'remove'`, `error: invalid argument 'remove'`).
|
|
162
|
+
* We keep the filter broad so a CLI rename doesn't lock the fallback
|
|
163
|
+
* shut on the next minor release; safer to fall back on a recognized
|
|
164
|
+
* not-supported signal than to mask a different failure mode.
|
|
165
|
+
*
|
|
166
|
+
* If the upstream CLI ever stops emitting the substring `remove` in its
|
|
167
|
+
* "unsupported subcommand" path, this check will incorrectly propagate
|
|
168
|
+
* the error instead of falling back — at that point the user gets a
|
|
169
|
+
* loud failure (good), and we tighten the matcher.
|
|
170
|
+
*/
|
|
171
|
+
function isSkillsRemoveUnsupported(err) {
|
|
172
|
+
const text = err instanceof Error
|
|
173
|
+
? `${err.message}\n${err.stderr ?? ""}`
|
|
174
|
+
: String(err);
|
|
175
|
+
return /unknown command|unrecognized command|invalid argument|unknown option|unsupported/i.test(text)
|
|
176
|
+
&& /remove/i.test(text);
|
|
177
|
+
}
|
|
178
|
+
const SKILL_NAME_RE_STRICT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
179
|
+
/**
|
|
180
|
+
* Uninstall a single skill by name. Strategy:
|
|
181
|
+
* 1. Try `npx -y skills remove <name>` (the canonical path; future-proof
|
|
182
|
+
* against upstream-internal cleanup we don't know about).
|
|
183
|
+
* 2. If the CLI doesn't support `remove`, fall back to manual cleanup:
|
|
184
|
+
* - rm `<cwd>/.claude/skills/<name>` (Claude Code view)
|
|
185
|
+
* - rm `<cwd>/.agents/skills/<name>` (Codex / other agents view)
|
|
186
|
+
* - remove the entry from `<cwd>/skills-lock.json` if present
|
|
187
|
+
*
|
|
188
|
+
* Idempotent: missing files / unknown skill names are no-ops, NOT errors.
|
|
189
|
+
* A repeated uninstall must succeed silently so the SSE caller can replay
|
|
190
|
+
* the request without special-casing.
|
|
191
|
+
*/
|
|
192
|
+
export async function uninstallSkill(name, opts) {
|
|
193
|
+
// Defensive: the CLI parser pre-validates skill names against the
|
|
194
|
+
// catalog before dispatch, but the server route doesn't, and we
|
|
195
|
+
// interpolate `name` into a shell command + filesystem path. Reject
|
|
196
|
+
// anything that didn't pass the install-side regex so a malformed
|
|
197
|
+
// input can't escape via either vector.
|
|
198
|
+
if (!SKILL_NAME_RE_STRICT.test(name)) {
|
|
199
|
+
throw new Error(`uninstallSkill: invalid skill name ${JSON.stringify(name)}`);
|
|
200
|
+
}
|
|
201
|
+
const cwd = path.resolve(opts.cwd);
|
|
202
|
+
const scope = opts.scope ?? "project";
|
|
203
|
+
// Install side maps outer `user` → internal `-g` (global). Mirror that
|
|
204
|
+
// on remove so user-scope skills aren't silently no-op'd.
|
|
205
|
+
const globalFlag = scope === "user" ? " -g" : "";
|
|
206
|
+
const emit = (line) => {
|
|
207
|
+
opts.onLog?.(line);
|
|
208
|
+
};
|
|
209
|
+
try {
|
|
210
|
+
exec(`npx -y skills remove ${name}${globalFlag}`, { cwd });
|
|
211
|
+
log.ok(`${name}: removed via skills CLI`);
|
|
212
|
+
emit(`removed ${name} via skills CLI`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
if (!isSkillsRemoveUnsupported(err)) {
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
log.warn(`skills CLI doesn't support 'remove'; falling back to manual cleanup`);
|
|
220
|
+
emit(`skills CLI doesn't support 'remove'; falling back to manual cleanup`);
|
|
221
|
+
}
|
|
222
|
+
await uninstallSkillManual(name, cwd, emit, scope);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Manual fallback used when `npx skills remove` is unavailable. Exported
|
|
226
|
+
* for test coverage (the exec-success path doesn't exercise it).
|
|
227
|
+
*
|
|
228
|
+
* Steps (each idempotent):
|
|
229
|
+
* - rm-rf `<cwd>/.claude/skills/<name>` if present
|
|
230
|
+
* - rm-rf `<cwd>/.agents/skills/<name>` if present
|
|
231
|
+
* - update `<cwd>/skills-lock.json` (drop the `.skills[name]` key,
|
|
232
|
+
* atomic write) if present and the key exists
|
|
233
|
+
*/
|
|
234
|
+
export async function uninstallSkillManual(name, cwd, onLog, scope = "project") {
|
|
235
|
+
if (!SKILL_NAME_RE_STRICT.test(name)) {
|
|
236
|
+
throw new Error(`uninstallSkillManual: invalid skill name ${JSON.stringify(name)}`);
|
|
237
|
+
}
|
|
238
|
+
const emit = (line) => { onLog?.(line); };
|
|
239
|
+
// User scope cleans `~/.claude/skills/<name>` + `~/.agents/skills/<name>`.
|
|
240
|
+
// Project scope cleans `<cwd>/.claude/...` + `<cwd>/.agents/...`.
|
|
241
|
+
// The lockfile is per-project; user-scope uninstall never mutates it.
|
|
242
|
+
const baseDir = scope === "user" ? os.homedir() : cwd;
|
|
243
|
+
const claudeDir = path.join(baseDir, ".claude", "skills", name);
|
|
244
|
+
const agentsDir = path.join(baseDir, ".agents", "skills", name);
|
|
245
|
+
for (const [label, dir] of [
|
|
246
|
+
[".claude/skills", claudeDir],
|
|
247
|
+
[".agents/skills", agentsDir],
|
|
248
|
+
]) {
|
|
249
|
+
if (fs.existsSync(dir) || fs.lstatSync(dir, { throwIfNoEntry: false })) {
|
|
250
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
251
|
+
log.ok(`${label}/${name} removed`);
|
|
252
|
+
emit(`removed ${label}/${name}`);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
log.skip(`${label}/${name} not present`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (scope === "user") {
|
|
259
|
+
// No `~/skills-lock.json` to mutate.
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const lockPath = path.join(cwd, "skills-lock.json");
|
|
263
|
+
if (!fs.existsSync(lockPath)) {
|
|
264
|
+
emit(`skills-lock.json not present`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Read + validate so we don't silently corrupt a hand-edited lockfile.
|
|
268
|
+
// If the user damaged it before calling us, throw — the install side's
|
|
269
|
+
// contract is "lockfile is authoritative", and writing back a partial
|
|
270
|
+
// tree would amplify their damage.
|
|
271
|
+
const raw = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
272
|
+
validateSkillsLock(raw);
|
|
273
|
+
if (!(name in raw.skills)) {
|
|
274
|
+
emit(`${name} not in skills-lock.json`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// Build a shallow copy so we don't mutate `raw` in case the caller
|
|
278
|
+
// holds a reference. Object spread preserves insertion order of
|
|
279
|
+
// remaining keys (deterministic test output).
|
|
280
|
+
const nextSkills = { ...raw.skills };
|
|
281
|
+
delete nextSkills[name];
|
|
282
|
+
const next = { ...raw, skills: nextSkills };
|
|
283
|
+
atomicWriteFile(lockPath, JSON.stringify(next, null, 2) + "\n");
|
|
284
|
+
log.ok(`${name} removed from skills-lock.json`);
|
|
285
|
+
emit(`removed ${name} from skills-lock.json`);
|
|
286
|
+
}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PluginState, StateReport } from "./api-types.js";
|
|
2
|
+
export interface Catalog {
|
|
3
|
+
workflowVersion: string;
|
|
4
|
+
skills: Record<string, {
|
|
5
|
+
description: string;
|
|
6
|
+
expectedHash: string;
|
|
7
|
+
isWorkflow: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
recommendedSkills: Record<string, {
|
|
10
|
+
description: string;
|
|
11
|
+
expectedHash: string;
|
|
12
|
+
}>;
|
|
13
|
+
plugins: Record<string, {
|
|
14
|
+
description: string;
|
|
15
|
+
/** Agents this plugin can install into. Length 1 or 2. */
|
|
16
|
+
agents: ("claude" | "codex")[];
|
|
17
|
+
expectedVersion?: string;
|
|
18
|
+
}>;
|
|
19
|
+
hooks: Record<string, {
|
|
20
|
+
description: string;
|
|
21
|
+
expectedHash: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
export interface ScanOptions {
|
|
25
|
+
execPluginList?: () => Promise<{
|
|
26
|
+
installed: any[];
|
|
27
|
+
available: any[];
|
|
28
|
+
}>;
|
|
29
|
+
readCodexConfig?: () => Promise<string | null>;
|
|
30
|
+
readCodexPluginsDir?: () => Promise<Map<string, string>>;
|
|
31
|
+
}
|
|
32
|
+
export declare function scanState(projectRoot: string, catalog: Catalog, opts?: ScanOptions): Promise<StateReport>;
|
|
33
|
+
/**
|
|
34
|
+
* Dedupe plugins by `id`, merging dual-Agent records into a single
|
|
35
|
+
* multi-agent row. Aggregation rules:
|
|
36
|
+
*
|
|
37
|
+
* agents: union of all agent arrays for this id (claude before codex).
|
|
38
|
+
* status: installed ⇔ every agent's record is installed
|
|
39
|
+
* not-installed ⇔ every agent's record is not-installed
|
|
40
|
+
* otherwise → update-available (partial install or any agent
|
|
41
|
+
* with a pending update). One Apply covers all
|
|
42
|
+
* gaps because the handler iterates `agents`.
|
|
43
|
+
*
|
|
44
|
+
* Non-status fields (description, currentVersion, expectedVersion,
|
|
45
|
+
* versionSource) come from the first record we see. Today both sides report
|
|
46
|
+
* the same description (catalog-driven) and the same versions for any
|
|
47
|
+
* registry-pinned plugin, so this is safe; if a future divergence appears
|
|
48
|
+
* we'll need a deliberate merge policy.
|
|
49
|
+
*/
|
|
50
|
+
export declare function mergePluginsById(records: PluginState[]): PluginState[];
|
|
51
|
+
/** Default: run `claude plugins list --json` and `claude plugins list
|
|
52
|
+
* --available --json`. Returns null is NOT an option here — server.ts
|
|
53
|
+
* decides whether to pass this function based on `which claude`. */
|
|
54
|
+
export declare function defaultExecPluginList(): Promise<{
|
|
55
|
+
installed: any[];
|
|
56
|
+
available: any[];
|
|
57
|
+
}>;
|
|
58
|
+
export declare function defaultReadCodexConfig(): Promise<string | null>;
|
|
59
|
+
/** Walk `~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/`, returning
|
|
60
|
+
* the highest-mtime version per `<plugin>@<marketplace>` id. The version
|
|
61
|
+
* semantics are catalog-pinned, so we surface the directory name verbatim
|
|
62
|
+
* rather than try to semver-sort. */
|
|
63
|
+
export declare function defaultReadCodexPluginsDir(): Promise<Map<string, string>>;
|