auriga-cli 1.24.0 → 1.26.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 +17 -22
- package/README.zh-CN.md +18 -22
- package/dist/api-types.d.ts +24 -16
- package/dist/apply-handlers.js +29 -26
- package/dist/catalog.d.ts +1 -2
- package/dist/catalog.json +2 -3
- package/dist/cli.d.ts +20 -0
- package/dist/cli.js +169 -48
- package/dist/guide.js +15 -12
- package/dist/help.js +10 -28
- package/dist/plugins.d.ts +9 -0
- package/dist/plugins.js +20 -2
- package/dist/preset.d.ts +38 -0
- package/dist/preset.js +84 -0
- package/dist/scan-catalog.js +2 -6
- package/dist/server.d.ts +7 -4
- package/dist/server.js +18 -7
- package/dist/state.d.ts +1 -6
- package/dist/state.js +0 -104
- package/dist/types.d.ts +1 -1
- package/dist/types.js +0 -1
- package/dist/utils.d.ts +7 -1
- package/package.json +4 -4
- package/dist/hooks.d.ts +0 -236
- package/dist/hooks.js +0 -965
package/dist/preset.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/preset.ts
|
|
2
|
+
//
|
|
3
|
+
// installPreset —— 「推荐预设安装」的单一编排入口。
|
|
4
|
+
//
|
|
5
|
+
// 预设由三部分组成,按下面的顺序安装:
|
|
6
|
+
// 1. workflow 文档 (CLAUDE.md + AGENTS.md)
|
|
7
|
+
// 2. 工作流 skill (WORKFLOW_SKILLS 全集 —— installSkills 自身已限定)
|
|
8
|
+
// 3. auriga-workflow 插件
|
|
9
|
+
//
|
|
10
|
+
// CLI 的 `--preset`、TUI 的「推荐预设」、Web UI 的一键按钮全部走这一个
|
|
11
|
+
// 函数,因此「预设由什么构成」只有这一处真相。
|
|
12
|
+
//
|
|
13
|
+
// installPreset 返回逐步成败摘要 (PresetStepResult[]):
|
|
14
|
+
// - CLI 用它计算分级退出码(全成功 0 / 部分或全部失败 2),与 runAll
|
|
15
|
+
// 的 graded-exit 语义对齐;
|
|
16
|
+
// - TUI / Web UI 忽略该摘要 —— 各自走 log-and-continue / 流式进度。
|
|
17
|
+
//
|
|
18
|
+
// 三个 installer 经动态 import 引入而非静态 import:这样 preset.ts 是一个
|
|
19
|
+
// 零重依赖的薄编排层 —— 只想读 PRESET_PLUGINS 常量的调用方(参数校验、
|
|
20
|
+
// 帮助文案)不会被迫拉入 plugins.ts 这张大依赖图;installer 模块也只在
|
|
21
|
+
// installPreset 真正被调用时才进入模块图。
|
|
22
|
+
/**
|
|
23
|
+
* 预设安装的插件成员 —— 固定只装 auriga-workflow。
|
|
24
|
+
* installPlugins 的 `selected` 过滤把安装面收敛到这一个插件。
|
|
25
|
+
* `as const` 冻结这个「单一真相」常量,调用方无法 .push() 篡改它。
|
|
26
|
+
*/
|
|
27
|
+
export const PRESET_PLUGINS = ["auriga-workflow"];
|
|
28
|
+
/** 预设的安装顺序:文档 → skill → 插件。 */
|
|
29
|
+
const PRESET_STEPS = [
|
|
30
|
+
"workflow",
|
|
31
|
+
"skills",
|
|
32
|
+
"plugins",
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* 按 workflow → skills → plugins 顺序执行预设安装。
|
|
36
|
+
*
|
|
37
|
+
* 每一步独立 try/catch:一步失败不阻断后续步骤(与 runAll 的
|
|
38
|
+
* log-and-continue 一致),逐步成败汇总后返回给调用方。
|
|
39
|
+
*/
|
|
40
|
+
export async function installPreset(packageRoot, opts) {
|
|
41
|
+
const results = [];
|
|
42
|
+
for (const category of PRESET_STEPS) {
|
|
43
|
+
try {
|
|
44
|
+
await runPresetStep(category, packageRoot, opts);
|
|
45
|
+
results.push({ category, ok: true });
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
results.push({ category, ok: false, err: e.message });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
async function runPresetStep(category, packageRoot, opts) {
|
|
54
|
+
// scope / agent / lang 一并透传给每个 installer —— installer 各取所需
|
|
55
|
+
// (workflow 只用 lang/cwd,skills/plugins 只用 scope/agent),用不到的
|
|
56
|
+
// 字段被忽略。
|
|
57
|
+
const base = {
|
|
58
|
+
interactive: opts.interactive,
|
|
59
|
+
scope: opts.scope,
|
|
60
|
+
agent: opts.agent,
|
|
61
|
+
lang: opts.lang,
|
|
62
|
+
cwd: opts.cwd,
|
|
63
|
+
onLog: opts.onLog,
|
|
64
|
+
};
|
|
65
|
+
switch (category) {
|
|
66
|
+
case "workflow": {
|
|
67
|
+
const { installWorkflow } = await import("./workflow.js");
|
|
68
|
+
return installWorkflow(packageRoot, base);
|
|
69
|
+
}
|
|
70
|
+
case "skills": {
|
|
71
|
+
// installSkills 内部已把安装范围限定到 WORKFLOW_SKILLS 全集 ——
|
|
72
|
+
// 不传 selected 即安装这组工作流 skill,无需在此重复列举。
|
|
73
|
+
const { installSkills } = await import("./skills.js");
|
|
74
|
+
return installSkills(packageRoot, base);
|
|
75
|
+
}
|
|
76
|
+
case "plugins": {
|
|
77
|
+
const { installPlugins } = await import("./plugins.js");
|
|
78
|
+
return installPlugins(packageRoot, {
|
|
79
|
+
...base,
|
|
80
|
+
selected: [...PRESET_PLUGINS],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/scan-catalog.js
CHANGED
|
@@ -7,7 +7,7 @@ import { loadCatalog } from "./catalog.js";
|
|
|
7
7
|
export async function buildScanCatalog(packageRoot) {
|
|
8
8
|
const dist = loadCatalog(packageRoot);
|
|
9
9
|
// v1.19.0 dropped update-available status. The scanner is now presence-
|
|
10
|
-
// only: skills /
|
|
10
|
+
// only: skills / plugins / workflow all report installed iff their
|
|
11
11
|
// truth source exists, not-installed otherwise. No version / hash / event
|
|
12
12
|
// comparison happens, so the build-time catalog is reduced to the bare
|
|
13
13
|
// {description, agents?, external?} shape per entry.
|
|
@@ -35,9 +35,5 @@ export async function buildScanCatalog(packageRoot) {
|
|
|
35
35
|
...(entry.external === true ? { external: true } : {}),
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
for (const entry of dist.hooks) {
|
|
40
|
-
hooks[entry.name] = { description: entry.description };
|
|
41
|
-
}
|
|
42
|
-
return { skills, recommendedSkills, plugins, hooks };
|
|
38
|
+
return { skills, recommendedSkills, plugins };
|
|
43
39
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -5,9 +5,12 @@ export interface ApplyHandlerOptions {
|
|
|
5
5
|
* translate into the per-installer flag (`--scope project|user`). The
|
|
6
6
|
* workflow handler ignores it (workflow has no scope concept). */
|
|
7
7
|
scope?: "project" | "user";
|
|
8
|
-
/** Workflow CLAUDE.md language variant.
|
|
9
|
-
*
|
|
8
|
+
/** Workflow CLAUDE.md language variant. Meaningful for the workflow and
|
|
9
|
+
* preset handlers; other handlers ignore it. Omitted = "en". */
|
|
10
10
|
lang?: "en" | "zh-CN";
|
|
11
|
+
/** Preset install runtime. Only meaningful for the preset handler;
|
|
12
|
+
* other handlers ignore it. Omitted = "both". */
|
|
13
|
+
agent?: "claude" | "codex" | "both";
|
|
11
14
|
}
|
|
12
15
|
export type ApplyHandler = (action: ApplyAction, name: string, opts: ApplyHandlerOptions) => Promise<void>;
|
|
13
16
|
export interface ApplyHandlers {
|
|
@@ -15,14 +18,14 @@ export interface ApplyHandlers {
|
|
|
15
18
|
skill: ApplyHandler;
|
|
16
19
|
"recommended-skill": ApplyHandler;
|
|
17
20
|
plugin: ApplyHandler;
|
|
18
|
-
|
|
21
|
+
preset: ApplyHandler;
|
|
19
22
|
}
|
|
20
23
|
export interface ApplyCatalog {
|
|
21
24
|
workflow: Set<string>;
|
|
22
25
|
skill: Set<string>;
|
|
23
26
|
"recommended-skill": Set<string>;
|
|
24
27
|
plugin: Set<string>;
|
|
25
|
-
|
|
28
|
+
preset: Set<string>;
|
|
26
29
|
}
|
|
27
30
|
export interface StartServerOptions {
|
|
28
31
|
port?: number;
|
package/dist/server.js
CHANGED
|
@@ -169,11 +169,12 @@ const VALID_CATEGORIES = new Set([
|
|
|
169
169
|
"skill",
|
|
170
170
|
"recommended-skill",
|
|
171
171
|
"plugin",
|
|
172
|
-
"
|
|
172
|
+
"preset",
|
|
173
173
|
]);
|
|
174
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
|
+
const VALID_AGENTS = new Set(["claude", "codex", "both"]);
|
|
177
178
|
function parseApplyRequest(raw) {
|
|
178
179
|
let parsed;
|
|
179
180
|
try {
|
|
@@ -190,7 +191,7 @@ function parseApplyRequest(raw) {
|
|
|
190
191
|
for (const it of items) {
|
|
191
192
|
if (!it || typeof it !== "object")
|
|
192
193
|
return null;
|
|
193
|
-
const { category, name, action, scope, lang } = it;
|
|
194
|
+
const { category, name, action, scope, lang, agent } = it;
|
|
194
195
|
if (typeof category !== "string" || !VALID_CATEGORIES.has(category)) {
|
|
195
196
|
return null;
|
|
196
197
|
}
|
|
@@ -207,12 +208,21 @@ function parseApplyRequest(raw) {
|
|
|
207
208
|
if (category === "workflow")
|
|
208
209
|
return null;
|
|
209
210
|
}
|
|
210
|
-
// Lang is optional and
|
|
211
|
-
//
|
|
211
|
+
// Lang is optional and meaningful for category="workflow" and
|
|
212
|
+
// category="preset" (the preset installs the workflow doc). Any other
|
|
213
|
+
// pairing is a client bug and we reject loudly.
|
|
212
214
|
if (lang !== undefined) {
|
|
213
215
|
if (typeof lang !== "string" || !VALID_LANGS.has(lang))
|
|
214
216
|
return null;
|
|
215
|
-
if (category !== "workflow")
|
|
217
|
+
if (category !== "workflow" && category !== "preset")
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
// Agent is optional and only meaningful for category="preset" (the
|
|
221
|
+
// per-plugin agent is derived from the catalog, not client-supplied).
|
|
222
|
+
if (agent !== undefined) {
|
|
223
|
+
if (typeof agent !== "string" || !VALID_AGENTS.has(agent))
|
|
224
|
+
return null;
|
|
225
|
+
if (category !== "preset")
|
|
216
226
|
return null;
|
|
217
227
|
}
|
|
218
228
|
}
|
|
@@ -375,6 +385,7 @@ export async function startServer(opts) {
|
|
|
375
385
|
onLog: (line, level) => emit(job, { type: "item:log", index: i, line, level }),
|
|
376
386
|
scope: item.scope,
|
|
377
387
|
lang: item.lang,
|
|
388
|
+
agent: item.agent,
|
|
378
389
|
});
|
|
379
390
|
emit(job, { type: "item:done", index: i, success: true });
|
|
380
391
|
}
|
|
@@ -752,7 +763,7 @@ function parseScopesParam(searchParams) {
|
|
|
752
763
|
const raw = searchParams.get("scopes");
|
|
753
764
|
if (!raw)
|
|
754
765
|
return null;
|
|
755
|
-
const allowedCategories = new Set(["workflow", "skills", "plugins"
|
|
766
|
+
const allowedCategories = new Set(["workflow", "skills", "plugins"]);
|
|
756
767
|
const allowedScopes = new Set(["user", "project"]);
|
|
757
768
|
const out = {};
|
|
758
769
|
for (const pair of raw.split(",")) {
|
|
@@ -801,5 +812,5 @@ const defaultHandlersNotConfigured = {
|
|
|
801
812
|
skill: handlerNotConfigured,
|
|
802
813
|
"recommended-skill": handlerNotConfigured,
|
|
803
814
|
plugin: handlerNotConfigured,
|
|
804
|
-
|
|
815
|
+
preset: handlerNotConfigured,
|
|
805
816
|
};
|
package/dist/state.d.ts
CHANGED
|
@@ -18,9 +18,6 @@ export interface Catalog {
|
|
|
18
18
|
* reporting; that surface was removed). */
|
|
19
19
|
external?: boolean;
|
|
20
20
|
}>;
|
|
21
|
-
hooks: Record<string, {
|
|
22
|
-
description: string;
|
|
23
|
-
}>;
|
|
24
21
|
}
|
|
25
22
|
export interface ScanOptions {
|
|
26
23
|
/** Run `claude plugins list` for the given scope. The scope argument is
|
|
@@ -37,13 +34,11 @@ export interface ScanOptions {
|
|
|
37
34
|
readCodexPluginsDir?: () => Promise<Map<string, string>>;
|
|
38
35
|
/** Per-category scope picker. Each field is independently routed to the
|
|
39
36
|
* right truth source. Defaults match the Web UI's per-column picker:
|
|
40
|
-
* workflow = 'project', skills = 'project',
|
|
41
|
-
* plugins = 'user', hooks = 'user'. */
|
|
37
|
+
* workflow = 'project', skills = 'project', plugins = 'user'. */
|
|
42
38
|
scopes?: {
|
|
43
39
|
workflow?: ScanScope;
|
|
44
40
|
skills?: ScanScope;
|
|
45
41
|
plugins?: ScanScope;
|
|
46
|
-
hooks?: ScanScope;
|
|
47
42
|
};
|
|
48
43
|
/** Test-time HOME override. When unset the scanner reads os.homedir()
|
|
49
44
|
* (which itself consults process.env.HOME / USERPROFILE), so tests that
|
package/dist/state.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
// <proj>/.claude/skills/<name>/SKILL.md (project scope)
|
|
9
9
|
// Plugins(Claude): execPluginList(scope) + settings.json enabledPlugins
|
|
10
10
|
// Plugins(Codex): ~/.codex/config.toml + ~/.codex/plugins/cache (user only)
|
|
11
|
-
// Hooks: <scope>/.claude/settings.json `hooks` segment, matched by _marker
|
|
12
11
|
//
|
|
13
12
|
// Scanner is presence-only: states are `installed` / `not-installed` /
|
|
14
13
|
// `partial-install` (dual-Agent half-install). v1.19.0 dropped
|
|
@@ -46,7 +45,6 @@ const DEFAULT_SCOPES = {
|
|
|
46
45
|
workflow: "project",
|
|
47
46
|
skills: "project",
|
|
48
47
|
plugins: "user",
|
|
49
|
-
hooks: "user",
|
|
50
48
|
};
|
|
51
49
|
export async function scanState(projectRoot, catalog, opts = {}) {
|
|
52
50
|
const warnings = [];
|
|
@@ -56,7 +54,6 @@ export async function scanState(projectRoot, catalog, opts = {}) {
|
|
|
56
54
|
const skills = scanSkills(scopes.skills, projectRoot, home, catalog.skills,
|
|
57
55
|
/* recommended */ false, warnings);
|
|
58
56
|
const recommendedSkills = scanRecommendedSkills(scopes.skills, projectRoot, home, catalog.recommendedSkills, warnings);
|
|
59
|
-
const hooks = scanHooks(scopes.hooks, projectRoot, home, catalog.hooks, warnings);
|
|
60
57
|
const claudePluginEntries = filterPluginsByAgent(catalog.plugins, "claude");
|
|
61
58
|
const codexPluginEntries = filterPluginsByAgent(catalog.plugins, "codex");
|
|
62
59
|
const claudePlugins = await scanClaudePlugins(scopes.plugins, claudePluginEntries, opts.execPluginList, warnings);
|
|
@@ -79,7 +76,6 @@ export async function scanState(projectRoot, catalog, opts = {}) {
|
|
|
79
76
|
skills,
|
|
80
77
|
recommendedSkills,
|
|
81
78
|
plugins: mergePluginsById([...claudePlugins, ...codexPlugins]),
|
|
82
|
-
hooks,
|
|
83
79
|
warnings,
|
|
84
80
|
};
|
|
85
81
|
}
|
|
@@ -462,106 +458,6 @@ function parseCodexEnabledPluginIds(tomlContent) {
|
|
|
462
458
|
return ids;
|
|
463
459
|
}
|
|
464
460
|
// ---------------------------------------------------------------------------
|
|
465
|
-
// Hooks — read from <scope>/.claude/settings.json `hooks` segment, matched by
|
|
466
|
-
// `_marker` sentinel against catalog hook names. Settings.json shape (Claude
|
|
467
|
-
// Code convention):
|
|
468
|
-
//
|
|
469
|
-
// {
|
|
470
|
-
// "hooks": {
|
|
471
|
-
// "<EventName>": [
|
|
472
|
-
// {
|
|
473
|
-
// "matcher": "<pattern>",
|
|
474
|
-
// "if": "<optional Claude-Code filter>",
|
|
475
|
-
// "hooks": [
|
|
476
|
-
// { "type": "command", "command": "...", "_marker": "<name>" }
|
|
477
|
-
// ]
|
|
478
|
-
// }
|
|
479
|
-
// ]
|
|
480
|
-
// }
|
|
481
|
-
// }
|
|
482
|
-
//
|
|
483
|
-
// ---------------------------------------------------------------------------
|
|
484
|
-
function settingsPathForScope(scope, projectRoot, home) {
|
|
485
|
-
if (scope === "user")
|
|
486
|
-
return path.join(home, ".claude", "settings.json");
|
|
487
|
-
return path.join(projectRoot, ".claude", "settings.json");
|
|
488
|
-
}
|
|
489
|
-
/** Returns the set of `_marker` sentinel values present in the settings
|
|
490
|
-
* `hooks` segment. Malformed sub-shapes are skipped silently. v1.19.0
|
|
491
|
-
* reduced this from a full {event, matcher, if, command} record (used for
|
|
492
|
-
* drift detection) to a presence-only Set — re-install is the update
|
|
493
|
-
* path now, so the scanner doesn't need to compare entry shapes. */
|
|
494
|
-
function indexSettingsMarkers(settings) {
|
|
495
|
-
const out = new Set();
|
|
496
|
-
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
497
|
-
return out;
|
|
498
|
-
const hooksSeg = settings.hooks;
|
|
499
|
-
if (!hooksSeg || typeof hooksSeg !== "object" || Array.isArray(hooksSeg))
|
|
500
|
-
return out;
|
|
501
|
-
for (const blocks of Object.values(hooksSeg)) {
|
|
502
|
-
if (!Array.isArray(blocks))
|
|
503
|
-
continue;
|
|
504
|
-
for (const block of blocks) {
|
|
505
|
-
if (!block || typeof block !== "object" || Array.isArray(block))
|
|
506
|
-
continue;
|
|
507
|
-
const actions = block.hooks;
|
|
508
|
-
if (!Array.isArray(actions))
|
|
509
|
-
continue;
|
|
510
|
-
for (const action of actions) {
|
|
511
|
-
if (!action || typeof action !== "object" || Array.isArray(action))
|
|
512
|
-
continue;
|
|
513
|
-
const marker = action._marker;
|
|
514
|
-
if (typeof marker === "string")
|
|
515
|
-
out.add(marker);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return out;
|
|
520
|
-
}
|
|
521
|
-
function scanHooks(scope, projectRoot, home, catalogHooks, warnings) {
|
|
522
|
-
const settingsPath = settingsPathForScope(scope, projectRoot, home);
|
|
523
|
-
let settingsRaw = null;
|
|
524
|
-
let settingsErr = null;
|
|
525
|
-
try {
|
|
526
|
-
settingsRaw = fs.readFileSync(settingsPath, "utf8");
|
|
527
|
-
}
|
|
528
|
-
catch (err) {
|
|
529
|
-
if (err && err.code === "ENOENT") {
|
|
530
|
-
settingsErr = "absent";
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
settingsErr = "unreadable";
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
let parsed = null;
|
|
537
|
-
if (settingsRaw !== null) {
|
|
538
|
-
try {
|
|
539
|
-
parsed = JSON.parse(settingsRaw);
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
settingsErr = "unreadable";
|
|
543
|
-
parsed = null;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
if (settingsErr === "unreadable") {
|
|
547
|
-
warnings.push({
|
|
548
|
-
code: "settings-unreadable",
|
|
549
|
-
message: `Settings file unreadable or corrupt JSON: ${settingsPath}`,
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
const markers = indexSettingsMarkers(parsed);
|
|
553
|
-
const out = [];
|
|
554
|
-
for (const [name, def] of Object.entries(catalogHooks)) {
|
|
555
|
-
out.push({
|
|
556
|
-
name,
|
|
557
|
-
description: def.description,
|
|
558
|
-
status: markers.has(name) ? "installed" : "not-installed",
|
|
559
|
-
observedScope: scope,
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
return out;
|
|
563
|
-
}
|
|
564
|
-
// ---------------------------------------------------------------------------
|
|
565
461
|
// Default external-I/O implementations (used when ScanOptions are not
|
|
566
462
|
// injected — server.ts wires these up in production).
|
|
567
463
|
// ---------------------------------------------------------------------------
|
package/dist/types.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* forcing leaf renderers (help.ts, guide.ts) to depend on the CLI
|
|
5
5
|
* entrypoint just to pull one union.
|
|
6
6
|
*/
|
|
7
|
-
export type CategoryName = "workflow" | "skills" | "recommended" | "plugins"
|
|
7
|
+
export type CategoryName = "workflow" | "skills" | "recommended" | "plugins";
|
|
8
8
|
export declare const CATEGORY_NAMES: readonly CategoryName[];
|
package/dist/types.js
CHANGED
package/dist/utils.d.ts
CHANGED
|
@@ -33,10 +33,16 @@ export interface InstallOpts {
|
|
|
33
33
|
lang?: string;
|
|
34
34
|
/** workflow only — install target directory (absolute or cwd-relative). */
|
|
35
35
|
cwd?: string;
|
|
36
|
-
/** skills / recommended / plugins
|
|
36
|
+
/** skills / recommended / plugins — `"user"` means install globally. */
|
|
37
37
|
scope?: "project" | "user";
|
|
38
38
|
/** plugins only — runtime to install plugins for. Defaults to Claude Code. */
|
|
39
39
|
agent?: PluginAgent;
|
|
40
|
+
/**
|
|
41
|
+
* plugins only — plugin names to drop from the interactive selection
|
|
42
|
+
* list. The TUI's「其他插件」item sets this to `["auriga-workflow"]`
|
|
43
|
+
* so the plugin already covered by the preset isn't offered twice.
|
|
44
|
+
*/
|
|
45
|
+
excludePlugins?: string[];
|
|
40
46
|
/**
|
|
41
47
|
* sub-item filter. `undefined` = full set of this category.
|
|
42
48
|
* Names are validated against the catalog by the CLI layer; installers
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auriga-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Interactive CLI to install Claude Code harness modules (Workflow, Skills, Recommended Skills, Plugins
|
|
3
|
+
"version": "1.26.0",
|
|
4
|
+
"description": "Interactive CLI to install Claude Code harness modules (Workflow, Skills, Recommended Skills, Plugins)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"dev": "tsc --watch",
|
|
26
26
|
"start": "node dist/cli.js",
|
|
27
27
|
"pretest": "npm run build",
|
|
28
|
-
"test": "tsc -p tsconfig.test.json && DEV=1 node --test --experimental-test-module-mocks dist-test/tests/
|
|
29
|
-
"test:watch": "tsc -p tsconfig.test.json --watch & node --test --watch --experimental-test-module-mocks dist-test/tests/
|
|
28
|
+
"test": "tsc -p tsconfig.test.json && DEV=1 node --test --experimental-test-module-mocks dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/preset.test.js dist-test/tests/legacy-menu.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
|
|
29
|
+
"test:watch": "tsc -p tsconfig.test.json --watch & node --test --watch --experimental-test-module-mocks dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/preset.test.js dist-test/tests/legacy-menu.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
|
|
30
30
|
"pretest:e2e": "npm run build",
|
|
31
31
|
"test:e2e": "tsc -p tsconfig.test.json && node --test dist-test/tests/e2e-install.test.js",
|
|
32
32
|
"pretest:web-ui-e2e": "npm run build && npm --prefix ui ci && npm --prefix ui run build",
|
package/dist/hooks.d.ts
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { type InstallOpts } from "./utils.js";
|
|
2
|
-
export interface HookDep {
|
|
3
|
-
name: string;
|
|
4
|
-
via: "brew";
|
|
5
|
-
optional?: boolean;
|
|
6
|
-
}
|
|
7
|
-
export interface HookSettingsEvent {
|
|
8
|
-
event: string;
|
|
9
|
-
/** Tool-name / regex filter; mapped onto the container-level
|
|
10
|
-
* `matcher` field in settings.json. Absent → every tool fires. */
|
|
11
|
-
matcher?: string;
|
|
12
|
-
/** Permission-rule-syntax filter — mapped onto the nested action-level
|
|
13
|
-
* `if` field in settings.json. Format: `<ToolName>(<substring>)`, e.g.
|
|
14
|
-
* `Bash(gh pr create)` — see IF_RE for the exact grammar. Lets the
|
|
15
|
-
* Claude Code runtime skip hook dispatch when the tool input doesn't
|
|
16
|
-
* match, avoiding a Node subprocess spawn per unrelated call.
|
|
17
|
-
* Absent → no registry-level content filter (the hook script runs
|
|
18
|
-
* for every invocation that passed `matcher`). */
|
|
19
|
-
if?: string;
|
|
20
|
-
}
|
|
21
|
-
export interface HookDef {
|
|
22
|
-
name: string;
|
|
23
|
-
description: string;
|
|
24
|
-
runtimePlatforms: string[];
|
|
25
|
-
settingsEvents: HookSettingsEvent[];
|
|
26
|
-
command: string;
|
|
27
|
-
files: string[];
|
|
28
|
-
preserveFiles?: string[];
|
|
29
|
-
deps?: HookDep[];
|
|
30
|
-
marker: string;
|
|
31
|
-
/**
|
|
32
|
-
* Per-hook customization hints rendered in the post-install summary.
|
|
33
|
-
* The literal `{hookDir}` is substituted with the hook's resolved
|
|
34
|
-
* install directory at print time. Empty / omitted → installer falls
|
|
35
|
-
* back to a generic "see <dir>/README.md" pointer.
|
|
36
|
-
*/
|
|
37
|
-
customizeHints?: string[];
|
|
38
|
-
/**
|
|
39
|
-
* Whether the hook is part of the default-on set. `false` makes the
|
|
40
|
-
* hook opt-in: non-interactive `install hooks` with no `--hook` filter
|
|
41
|
-
* skips it, and the interactive checkbox leaves it unchecked. Absent
|
|
42
|
-
* / `true` → installed by default. Used for hooks with intrusive
|
|
43
|
-
* side effects (OS notifications, brew deps, platform constraints)
|
|
44
|
-
* that users probably want to pick up consciously.
|
|
45
|
-
*/
|
|
46
|
-
defaultOn?: boolean;
|
|
47
|
-
}
|
|
48
|
-
export interface HooksConfig {
|
|
49
|
-
hooks: HookDef[];
|
|
50
|
-
}
|
|
51
|
-
export interface SettingsHookAction {
|
|
52
|
-
type: "command";
|
|
53
|
-
command: string;
|
|
54
|
-
_marker?: string;
|
|
55
|
-
/** Per-action permission-rule filter (Claude Code ≥ 2026-04 schema).
|
|
56
|
-
* Format: `<ToolName>(<substring>)`. Older runtimes ignore unknown
|
|
57
|
-
* fields, so emitting this is forward-safe. */
|
|
58
|
-
if?: string;
|
|
59
|
-
}
|
|
60
|
-
export interface SettingsHookGroup {
|
|
61
|
-
matcher?: string;
|
|
62
|
-
hooks: SettingsHookAction[];
|
|
63
|
-
}
|
|
64
|
-
export interface SettingsFile {
|
|
65
|
-
hooks?: Record<string, SettingsHookGroup[]>;
|
|
66
|
-
[key: string]: unknown;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Pure, idempotent settings merge. Deep-clones input, dedupes by two
|
|
70
|
-
* checks in priority order:
|
|
71
|
-
*
|
|
72
|
-
* 1. sentinel `_marker` field — primary key. Survives path drift, lets
|
|
73
|
-
* a future uninstall command find our entries unambiguously.
|
|
74
|
-
* 2. command-string equality — secondary, catches the case where the
|
|
75
|
-
* user (or another tool) already added an equivalent entry by hand
|
|
76
|
-
* and never wrote our marker. Without this fallback we would happily
|
|
77
|
-
* append a duplicate next to it and the hook would fire twice.
|
|
78
|
-
*
|
|
79
|
-
* `options.matcher` writes to the container-level `matcher` (tool-name
|
|
80
|
-
* filter); `options.ifRule` writes to the action-level `if` (permission-
|
|
81
|
-
* rule substring filter, Claude Code ≥ 2026-04). Either or both may be
|
|
82
|
-
* absent.
|
|
83
|
-
*
|
|
84
|
-
* Upgrade path: if an entry with our marker already exists but its
|
|
85
|
-
* matcher / if disagrees with the desired values, we update those two
|
|
86
|
-
* fields in place (preserving everything else — command, sibling
|
|
87
|
-
* actions, the user's other groups — untouched). This is the path for
|
|
88
|
-
* a user who installed an older registry version and re-runs the
|
|
89
|
-
* installer after hooks.json changed. Pure no-op when the existing
|
|
90
|
-
* fields already match.
|
|
91
|
-
*
|
|
92
|
-
* Inputs are defense-in-depth revalidated here against IF_RE + the
|
|
93
|
-
* event-name regex even though registry callers already passed
|
|
94
|
-
* loadHooksConfig, so a direct library caller can't write malformed
|
|
95
|
-
* values into settings.json by bypassing the registry loader.
|
|
96
|
-
*
|
|
97
|
-
* Throws if `settings.hooks[event]` exists but is not an array — that
|
|
98
|
-
* means the user has hand-edited their settings into a shape we do not
|
|
99
|
-
* recognize, and silently replacing it with an empty array would lose
|
|
100
|
-
* data. Callers should catch and surface the error to the user.
|
|
101
|
-
*/
|
|
102
|
-
export declare function addHookToSettings(settings: SettingsFile, event: string, command: string, marker: string, options?: {
|
|
103
|
-
matcher?: string;
|
|
104
|
-
ifRule?: string;
|
|
105
|
-
}): {
|
|
106
|
-
settings: SettingsFile;
|
|
107
|
-
mutated: boolean;
|
|
108
|
-
};
|
|
109
|
-
/**
|
|
110
|
-
* Pure inverse of addHookToSettings: removes every action carrying
|
|
111
|
-
* `_marker` from every event in the settings tree. Returns the mutated
|
|
112
|
-
* copy and the count of actions removed. If a group becomes empty after
|
|
113
|
-
* removal, the whole group is dropped; if an event becomes empty, the
|
|
114
|
-
* event key is dropped.
|
|
115
|
-
*/
|
|
116
|
-
export declare function removeHookFromSettings(settings: SettingsFile, marker: string): {
|
|
117
|
-
settings: SettingsFile;
|
|
118
|
-
removed: number;
|
|
119
|
-
};
|
|
120
|
-
type Scope = "project-local" | "project" | "user";
|
|
121
|
-
/**
|
|
122
|
-
* Non-interactive scope map for hooks.
|
|
123
|
-
*
|
|
124
|
-
* Non-interactive surface only knows about two values — `project` (the
|
|
125
|
-
* default) and `user`. `project-local` exists only in the TTY menu; it's
|
|
126
|
-
* a per-developer uncommitted scope and carries enough "did you really
|
|
127
|
-
* mean this?" surface area that we gate it behind an interactive
|
|
128
|
-
* confirmation rather than exposing it as a CLI flag value.
|
|
129
|
-
*
|
|
130
|
-
* Exported so `tests/hooks.test.ts` can lock the contract down as a
|
|
131
|
-
* unit test.
|
|
132
|
-
*/
|
|
133
|
-
export declare function mapNonInteractiveScope(scope: string | undefined): Scope;
|
|
134
|
-
export declare function depBinary(dep: HookDep): string;
|
|
135
|
-
export declare function loadHooksConfig(packageRoot: string): HooksConfig;
|
|
136
|
-
export interface InstallHookResult {
|
|
137
|
-
hook: string;
|
|
138
|
-
written: number;
|
|
139
|
-
preserved: number;
|
|
140
|
-
scope: Scope;
|
|
141
|
-
hookDir: string;
|
|
142
|
-
settingsPath: string;
|
|
143
|
-
settingsMutated: boolean;
|
|
144
|
-
settingsError?: string;
|
|
145
|
-
aborted?: string;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Non-interactive single-hook install. Driven by installHooks (which
|
|
149
|
-
* collects user choices via prompts) and by tools/verify-hooks.mjs (which
|
|
150
|
-
* exercises the install path end-to-end without prompts).
|
|
151
|
-
*
|
|
152
|
-
* Failure ordering matters: deps run first (no state changes), then
|
|
153
|
-
* settings is read AND parsed (still no state changes), and only after
|
|
154
|
-
* parsing succeeds do we touch the filesystem to copy hook files. A
|
|
155
|
-
* malformed settings file therefore aborts cleanly and leaves nothing
|
|
156
|
-
* behind.
|
|
157
|
-
*/
|
|
158
|
-
export declare function installHook(hook: HookDef, scope: Scope, projectBase: string, packageRoot: string): Promise<InstallHookResult>;
|
|
159
|
-
/**
|
|
160
|
-
* Scan all 3 scope settings files for a hook's marker, returning every
|
|
161
|
-
* scope where the marker is currently present and is NOT the scope the
|
|
162
|
-
* caller is about to install into. Used by installHooks to detect
|
|
163
|
-
* cross-scope leftovers from a previous install — which would cause the
|
|
164
|
-
* hook to fire multiple times if not cleaned up.
|
|
165
|
-
*
|
|
166
|
-
* Pure-ish: reads files but does not mutate. Silently skips files that
|
|
167
|
-
* fail to parse — surfacing those errors is the install path's job.
|
|
168
|
-
*/
|
|
169
|
-
export interface StaleScope {
|
|
170
|
-
scope: Scope;
|
|
171
|
-
settingsPath: string;
|
|
172
|
-
count: number;
|
|
173
|
-
}
|
|
174
|
-
export declare function findStaleScopes(hook: HookDef, currentScope: Scope, projectBase: string): StaleScope[];
|
|
175
|
-
/**
|
|
176
|
-
* Remove every action carrying `hook.marker` from the given scope's
|
|
177
|
-
* settings file. Atomic write, snapshot-once .bak. Returns the count of
|
|
178
|
-
* actions removed (0 if nothing matched or file did not exist).
|
|
179
|
-
*/
|
|
180
|
-
export declare function cleanHookFromScope(hook: HookDef, scope: Scope, projectBase: string): {
|
|
181
|
-
removed: number;
|
|
182
|
-
settingsPath: string;
|
|
183
|
-
};
|
|
184
|
-
/**
|
|
185
|
-
* Non-interactive selection resolver for hooks.
|
|
186
|
-
*
|
|
187
|
-
* Diverges from resolvePluginSelection in the no-filter case. Hooks can
|
|
188
|
-
* carry intrusive side effects (OS notifications, brew deps), so the
|
|
189
|
-
* safe default is NOT "install everything". Three cases:
|
|
190
|
-
*
|
|
191
|
-
* - undefined (no --hook passed) → default-on set (filter on defaultOn !== false)
|
|
192
|
-
* - ["*"] (explicit opt-in to everything) → full compatible set
|
|
193
|
-
* - explicit names → exactly those (even if defaultOn is false)
|
|
194
|
-
*/
|
|
195
|
-
export declare function resolveHookSelection(compatible: HookDef[], selected: string[] | undefined): HookDef[];
|
|
196
|
-
/**
|
|
197
|
-
* Given the full registry and the platform-filtered compatible subset,
|
|
198
|
-
* return the names in `selected` that refer to real hooks but aren't
|
|
199
|
-
* available on the current platform. Empty result means the selection
|
|
200
|
-
* is either fully compatible or references unknown hooks (that case is
|
|
201
|
-
* left to the catalog validator — we don't pretend unknown names are
|
|
202
|
-
* platform issues).
|
|
203
|
-
*/
|
|
204
|
-
export declare function findIncompatibleExplicit(all: HookDef[], compatible: HookDef[], selected: string[]): string[];
|
|
205
|
-
export declare function installHooks(packageRoot: string, opts: InstallOpts): Promise<void>;
|
|
206
|
-
/**
|
|
207
|
-
* Uninstall a single hook. Defaults to project scope; explicit
|
|
208
|
-
* `scope:"user"` cleans `~/.claude/...` instead.
|
|
209
|
-
*
|
|
210
|
-
* Project scope (default):
|
|
211
|
-
* - rm `<cwd>/.claude/hooks/<name>/` directory.
|
|
212
|
-
* - Strip the hook's marker from `<cwd>/.claude/settings.json` AND
|
|
213
|
-
* `<cwd>/.claude/settings.local.json` (project + project-local share
|
|
214
|
-
* the on-disk hook dir, so cleaning both settings files keeps users
|
|
215
|
-
* who switched scopes from accumulating dangling registrations).
|
|
216
|
-
*
|
|
217
|
-
* User scope:
|
|
218
|
-
* - rm `~/.claude/hooks/<name>/` directory.
|
|
219
|
-
* - Strip the hook's marker from `~/.claude/settings.json`.
|
|
220
|
-
* - Project files are NOT touched.
|
|
221
|
-
*
|
|
222
|
-
* Marker discovery: tries the live registry at `<cwd>` (or the npx
|
|
223
|
-
* package root if that fails) so we use the same marker the install path
|
|
224
|
-
* stamped in. If the registry can't resolve the hook (renamed / removed
|
|
225
|
-
* upstream), we fall back to a `auriga:<name>` convention — every shipped
|
|
226
|
-
* hook to date follows it, so the fallback is reliable for the common
|
|
227
|
-
* case.
|
|
228
|
-
*
|
|
229
|
-
* Idempotent: missing hook dir / missing settings / absent marker → no-op.
|
|
230
|
-
*/
|
|
231
|
-
export declare function uninstallHook(name: string, opts: {
|
|
232
|
-
cwd: string;
|
|
233
|
-
scope?: "project" | "user";
|
|
234
|
-
onLog?: (line: string) => void;
|
|
235
|
-
}): Promise<void>;
|
|
236
|
-
export {};
|