auriga-cli 1.17.0 → 1.18.2

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 CHANGED
@@ -113,7 +113,9 @@ Installs selected skills via `npx skills add`, targeting both Claude Code and Co
113
113
  | claude-code-agent | [Ben2pc/g-claude-code-plugins](https://github.com/Ben2pc/g-claude-code-plugins) | Delegate coding, review, diagnosis, and planning to standalone Claude Code sessions |
114
114
  | code-simplification | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | Refactor for clarity without changing behavior — trim accumulated complexity |
115
115
  | codex-agent | [Ben2pc/g-claude-code-plugins](https://github.com/Ben2pc/g-claude-code-plugins) | Delegate to Codex sessions for cross-model coverage |
116
+ | deprecation-and-migration | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | Sunset, replace, or migrate legacy code — deprecation discipline |
116
117
  | design-taste-frontend | [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill) | Senior UI/UX engineer with metric-based design rules and strict component architecture |
118
+ | documentation-and-adrs | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | Record architectural decisions and the *why* — context for future engineers / agents |
117
119
  | frontend-design | [anthropics/skills](https://github.com/anthropics/skills) | Distinctive, production-grade frontend UI generation that avoids generic AI aesthetics |
118
120
  | make-interfaces-feel-better | [jakubkrehel/make-interfaces-feel-better](https://github.com/jakubkrehel/make-interfaces-feel-better) | Polish principles — animations, surfaces, typography, performance |
119
121
 
package/README.zh-CN.md CHANGED
@@ -113,7 +113,9 @@ npx auriga-cli
113
113
  | claude-code-agent | [Ben2pc/g-claude-code-plugins](https://github.com/Ben2pc/g-claude-code-plugins) | 通过 Claude Code Agent SDK 把任务委派给独立 Claude Code 会话 |
114
114
  | code-simplification | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | 不改变行为前提下重构代码以提升可读性 —— 清掉累积的不必要复杂度 |
115
115
  | codex-agent | [Ben2pc/g-claude-code-plugins](https://github.com/Ben2pc/g-claude-code-plugins) | 委派给 Codex 会话,做跨模型覆盖 |
116
+ | deprecation-and-migration | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | 废弃与迁移流程 —— 安全地下线、替换或迁移遗留代码 |
116
117
  | design-taste-frontend | [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill) | 高阶 UI/UX 工程师 —— 度量化设计规则与严格的组件架构约束 |
118
+ | documentation-and-adrs | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | 记录架构决策与"为什么" —— 为后来的工程师和 Agent 沉淀上下文 |
117
119
  | frontend-design | [anthropics/skills](https://github.com/anthropics/skills) | 生成有辨识度、production 级的前端界面,避开常见 AI 同质化美学 |
118
120
  | make-interfaces-feel-better | [jakubkrehel/make-interfaces-feel-better](https://github.com/jakubkrehel/make-interfaces-feel-better) | 界面打磨原则 —— 动画、表面、排版、性能 |
119
121
 
@@ -1,4 +1,13 @@
1
1
  export type ItemStatus = "installed" | "update-available" | "not-installed";
2
+ /**
3
+ * Per-category scan scope. Each category (workflow / skills / plugins / hooks)
4
+ * can be independently scanned in either user scope (~/.claude/, ~/.codex/)
5
+ * or project scope (<proj>/.claude/). The Web UI's per-column scope picker
6
+ * carries these through the `/api/state` query so the scanner reads the
7
+ * right truth source per category. Codex plugins are user-scope only by
8
+ * design and ignore this field.
9
+ */
10
+ export type ScanScope = "user" | "project";
2
11
  export interface StateReport {
3
12
  /** Absolute path to the project the server was launched against. Surfaced
4
13
  * in the UI's top bar so users know where Apply will write project-scope
@@ -16,6 +25,13 @@ export interface WorkflowState {
16
25
  status: ItemStatus;
17
26
  currentVersion?: string;
18
27
  expectedVersion: string;
28
+ /** Which scope the scanner read to produce this row. Reflects the scope
29
+ * scanned, not where the file was found — e.g. when scope=user and
30
+ * ~/.claude/CLAUDE.md is absent, observedScope is still "user". The
31
+ * scanner ALWAYS sets this field at runtime; it is typed optional only
32
+ * so the mergePluginsById regression helper (which carries over from the
33
+ * pre-rewrite suite without an explicit scope) continues to compile. */
34
+ observedScope?: ScanScope;
19
35
  }
20
36
  export interface SkillState {
21
37
  name: string;
@@ -24,6 +40,8 @@ export interface SkillState {
24
40
  isWorkflow: boolean;
25
41
  currentHash?: string;
26
42
  expectedHash: string;
43
+ /** Scope the scanner read to produce this row. See WorkflowState comment. */
44
+ observedScope?: ScanScope;
27
45
  }
28
46
  export type ApplyAgent = "claude" | "codex";
29
47
  export interface PluginState {
@@ -41,6 +59,10 @@ export interface PluginState {
41
59
  currentVersion?: string;
42
60
  expectedVersion?: string;
43
61
  versionSource: "upstream-live" | "catalog";
62
+ /** Scope the scanner read to produce this row. Codex plugins are always
63
+ * "user" (Codex has no project-scope plugin concept). See WorkflowState
64
+ * comment on why this is typed optional. */
65
+ observedScope?: ScanScope;
44
66
  }
45
67
  export interface HookState {
46
68
  name: string;
@@ -48,9 +70,11 @@ export interface HookState {
48
70
  status: ItemStatus;
49
71
  currentHash?: string;
50
72
  expectedHash: string;
73
+ /** Scope the scanner read to produce this row. See WorkflowState comment. */
74
+ observedScope?: ScanScope;
51
75
  }
52
76
  export interface StateWarning {
53
- code: "claude-cli-missing" | "codex-cli-missing" | "marketplace-offline";
77
+ code: "claude-cli-missing" | "codex-cli-missing" | "marketplace-offline" | "claude-code-not-installed" | "settings-unreadable" | "skill-malformed" | "workflow-unknown-version";
54
78
  message: string;
55
79
  }
56
80
  export type ApplyCategory = "workflow" | "skill" | "recommended-skill" | "plugin" | "hook";
package/dist/catalog.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-05-12T11:30:47.843Z",
2
+ "generatedAt": "2026-05-12T13:41:34.719Z",
3
3
  "workflowSkills": [
4
4
  {
5
5
  "name": "brainstorming",
@@ -51,10 +51,18 @@
51
51
  "name": "codex-agent",
52
52
  "description": "Delegate coding, review, diagnosis, planning, and browser tasks to an independent Codex session via `codex exec` / resume / review."
53
53
  },
54
+ {
55
+ "name": "deprecation-and-migration",
56
+ "description": "Manages deprecation and migration. Use when removing old systems, APIs, or features. Use when migrating users from one implementation to another. Use when deciding whether to maintain or sunset existing code."
57
+ },
54
58
  {
55
59
  "name": "design-taste-frontend",
56
60
  "description": "Senior UI/UX Engineer. Architect digital interfaces overriding default LLM biases. Enforces metric-based rules, strict component architecture, CSS hardware acceleration, and balanced design engineering."
57
61
  },
62
+ {
63
+ "name": "documentation-and-adrs",
64
+ "description": "Records decisions and documentation. Use when making architectural decisions, changing public APIs, shipping features, or when you need to record context that future engineers and agents will need to understand the codebase."
65
+ },
58
66
  {
59
67
  "name": "frontend-design",
60
68
  "description": "Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics."
package/dist/cli.js CHANGED
@@ -562,7 +562,12 @@ async function dispatchInstaller(category, packageRoot, opts) {
562
562
  // ---------------------------------------------------------------------------
563
563
  const UI_DEFAULT_PORT = 4747;
564
564
  const UI_PORT_RANGE = 10; // 4747..4756
565
- const UI_HEARTBEAT_TIMEOUT_MS = 15_000;
565
+ // 2 minutes covers Chrome's "intensive throttling" of background tabs
566
+ // (kicks in after ~5 min of being hidden, drops setInterval to ~1 ping/min).
567
+ // At 15s the browser tab being switched away for a moment would tear down
568
+ // the server — bad UX. Closing the browser now takes up to 2 min to release
569
+ // the port; users who care can ctrl+C the CLI for immediate exit.
570
+ const UI_HEARTBEAT_TIMEOUT_MS = 120_000;
566
571
  async function runUi(p, version) {
567
572
  // Lazy-load the server-side deps so the install / guide paths stay light.
568
573
  const { randomBytes } = await import("node:crypto");
@@ -8,7 +8,9 @@
8
8
  // skills-lock.json — expected SHA256 for every vendored skill
9
9
  // .claude/plugins.json — Claude plugin entries (agent = "claude")
10
10
  // .agents/plugins/install.json — Codex plugin entries (agent = "codex")
11
- // .claude/hooks/<name>/index.mjsruntime SHA256 = expected hash
11
+ // .claude/hooks/hooks.jsonregisters settingsEvents per hook (event /
12
+ // matcher / if) used by state.ts for drift
13
+ // detection in <scope>/.claude/settings.json
12
14
  // CLAUDE.md — `# auriga Workflow (vX.Y.Z)` provides
13
15
  // workflowVersion
14
16
  //
@@ -20,6 +22,39 @@ import { createHash } from "node:crypto";
20
22
  import { readFile } from "node:fs/promises";
21
23
  import path from "node:path";
22
24
  import { loadCatalog } from "./catalog.js";
25
+ async function sha256SkillMd(skillsRoot, name) {
26
+ try {
27
+ const buf = await readFile(path.join(skillsRoot, name, "SKILL.md"));
28
+ return createHash("sha256").update(buf).digest("hex");
29
+ }
30
+ catch {
31
+ return "";
32
+ }
33
+ }
34
+ /** Read `plugins/<name>/.claude-plugin/plugin.json` (or `.codex-plugin/plugin.json`
35
+ * as fallback) and return the `version` field. Returns "" when no manifest
36
+ * exists or the JSON is malformed — the scanner then leaves expectedVersion
37
+ * unset, which means external-marketplace plugins (whose source lives
38
+ * upstream, not in this repo) get the "trust installed" classification. */
39
+ async function readPluginManifestVersion(packageRoot, name) {
40
+ const candidates = [
41
+ path.join(packageRoot, "plugins", name, ".claude-plugin", "plugin.json"),
42
+ path.join(packageRoot, "plugins", name, ".codex-plugin", "plugin.json"),
43
+ ];
44
+ for (const p of candidates) {
45
+ try {
46
+ const raw = await readFile(p, "utf8");
47
+ const parsed = JSON.parse(raw);
48
+ if (typeof parsed.version === "string" && parsed.version.length > 0) {
49
+ return parsed.version;
50
+ }
51
+ }
52
+ catch {
53
+ /* try next candidate */
54
+ }
55
+ }
56
+ return "";
57
+ }
23
58
  const WORKFLOW_VERSION_RE = /^#\s*auriga Workflow\s*\(v([\d.]+)\)/m;
24
59
  async function tryReadFile(p) {
25
60
  try {
@@ -29,10 +64,6 @@ async function tryReadFile(p) {
29
64
  return null;
30
65
  }
31
66
  }
32
- async function sha256File(p) {
33
- const bytes = await readFile(p);
34
- return createHash("sha256").update(bytes).digest("hex");
35
- }
36
67
  export async function buildScanCatalog(packageRoot) {
37
68
  const dist = loadCatalog(packageRoot);
38
69
  // Workflow version: parse from auriga-cli's own CLAUDE.md template.
@@ -41,22 +72,18 @@ export async function buildScanCatalog(packageRoot) {
41
72
  const claudeMd = await tryReadFile(path.join(packageRoot, "CLAUDE.md"));
42
73
  const m = claudeMd ? WORKFLOW_VERSION_RE.exec(claudeMd) : null;
43
74
  const workflowVersion = m ? m[1] : "";
44
- // Skills + recommended: hashes from skills-lock.json.
45
- let lock = {};
46
- const lockText = await tryReadFile(path.join(packageRoot, "skills-lock.json"));
47
- if (lockText) {
48
- try {
49
- lock = JSON.parse(lockText);
50
- }
51
- catch {
52
- // corrupted lock → no expectations; user state still classifies safely
53
- }
54
- }
75
+ // Skills + recommended: sha256 of each shipped SKILL.md. This is the same
76
+ // hash the scanner computes for `<scope>/.claude/skills/<name>/SKILL.md`
77
+ // at scan time, so a match means "user's installed copy is identical to
78
+ // the version auriga-cli ships". skills-lock.json's `computedHash` field
79
+ // hashes the entire skill directory (every file, sorted), which doesn't
80
+ // line up with the scanner's per-file model — we deliberately ignore it.
81
+ const skillsRoot = path.join(packageRoot, ".agents", "skills");
55
82
  const skills = {};
56
83
  for (const entry of dist.workflowSkills) {
57
84
  skills[entry.name] = {
58
85
  description: entry.description,
59
- expectedHash: lock.skills?.[entry.name]?.computedHash ?? "",
86
+ expectedHash: await sha256SkillMd(skillsRoot, entry.name),
60
87
  isWorkflow: true,
61
88
  };
62
89
  }
@@ -64,7 +91,7 @@ export async function buildScanCatalog(packageRoot) {
64
91
  for (const entry of dist.recommendedSkills) {
65
92
  recommendedSkills[entry.name] = {
66
93
  description: entry.description,
67
- expectedHash: lock.skills?.[entry.name]?.computedHash ?? "",
94
+ expectedHash: await sha256SkillMd(skillsRoot, entry.name),
68
95
  };
69
96
  }
70
97
  // Plugins: split by agent based on which config file lists them. A
@@ -111,22 +138,49 @@ export async function buildScanCatalog(packageRoot) {
111
138
  agents.push("codex");
112
139
  if (agents.length === 0)
113
140
  agents.push("claude"); // unknown defaults to claude
114
- plugins[entry.name] = { description: entry.description, agents };
141
+ // Bake expectedVersion from the owned plugin's manifest. For Claude-side
142
+ // plugins prefer plugins/<name>/.claude-plugin/plugin.json; fall back to
143
+ // .codex-plugin/plugin.json for codex-only plugins (e.g.
144
+ // session-instructions-loader). External-marketplace plugins (skill-creator,
145
+ // claude-md-management, codex) have no local manifest — they install from
146
+ // their upstream marketplace, so we deliberately leave expectedVersion
147
+ // undefined. The scanner then falls through to "trust whatever is installed",
148
+ // which matches what the user agreed to when they registered the upstream.
149
+ const expectedVersion = await readPluginManifestVersion(packageRoot, entry.name);
150
+ plugins[entry.name] = {
151
+ description: entry.description,
152
+ agents,
153
+ ...(expectedVersion ? { expectedVersion } : {}),
154
+ };
155
+ }
156
+ // Hooks: the scanner reads <scope>/.claude/settings.json and matches by
157
+ // `_marker` (see state.ts scanHooks). Drift detection compares the
158
+ // registered event / matcher / if values against the hook's canonical
159
+ // settingsEvents[0] from .claude/hooks/hooks.json. We deliberately do NOT
160
+ // hash index.mjs — the user's installed index.mjs lives at <scope>/.claude/
161
+ // hooks/<name>/index.mjs and isn't part of the settings.json drift signal.
162
+ const hooksJsonPath = path.join(packageRoot, ".claude", "hooks", "hooks.json");
163
+ const hooksJsonRaw = await tryReadFile(hooksJsonPath);
164
+ const hooksJson = hooksJsonRaw ? JSON.parse(hooksJsonRaw) : {};
165
+ const settingsEventsByName = new Map();
166
+ for (const h of hooksJson.hooks ?? []) {
167
+ if (typeof h.name === "string" && h.settingsEvents?.length) {
168
+ settingsEventsByName.set(h.name, h.settingsEvents[0]);
169
+ }
115
170
  }
116
- // Hooks: runtime SHA256 of each hook's index.mjs serves as the expected
117
- // hash. If the file can't be read, leave the expectation empty so the
118
- // hook classifies as not-installed.
119
171
  const hooks = {};
120
172
  for (const entry of dist.hooks) {
121
- const hookEntry = path.join(packageRoot, ".claude", "hooks", entry.name, "index.mjs");
122
- let expectedHash = "";
123
- try {
124
- expectedHash = await sha256File(hookEntry);
125
- }
126
- catch {
127
- /* missing or unreadable hook payload; leave hash empty */
128
- }
129
- hooks[entry.name] = { description: entry.description, expectedHash };
173
+ const ev = settingsEventsByName.get(entry.name);
174
+ hooks[entry.name] = {
175
+ description: entry.description,
176
+ // Empty expectedHash flips state.ts into wildcard mode — drift judged
177
+ // purely from the structured expected* fields below, not from a
178
+ // settings-entry content hash.
179
+ expectedHash: "",
180
+ ...(typeof ev?.event === "string" ? { expectedEvent: ev.event } : {}),
181
+ ...(typeof ev?.matcher === "string" ? { expectedMatcher: ev.matcher } : {}),
182
+ ...(typeof ev?.if === "string" ? { expectedIf: ev.if } : {}),
183
+ };
130
184
  }
131
185
  return {
132
186
  workflowVersion,
package/dist/server.js CHANGED
@@ -7,13 +7,14 @@
7
7
  //
8
8
  // Public contract is anchored in docs/architecture/web-ui.md §4 (server
9
9
  // surface), §6 (data flow + types), §7 (errors).
10
+ import { spawnSync } from "node:child_process";
10
11
  import { createServer } from "node:http";
11
12
  import { Buffer } from "node:buffer";
12
13
  import { randomBytes, timingSafeEqual } from "node:crypto";
13
14
  import { readFile } from "node:fs/promises";
14
15
  import path from "node:path";
15
16
  import { buildScanCatalog } from "./scan-catalog.js";
16
- import { scanState } from "./state.js";
17
+ import { defaultExecPluginList, scanState } from "./state.js";
17
18
  // Body parsing cap. /api/apply payloads are tiny (an array of item refs);
18
19
  // 1 MiB is generously above the largest realistic batch and small enough that
19
20
  // abusive clients can't pin memory.
@@ -576,7 +577,7 @@ export async function startServer(opts) {
576
577
  return;
577
578
  }
578
579
  if (pathname === "/api/state" && method === "GET") {
579
- await routeState(opts.cwd, opts.packageRoot ?? opts.cwd, res);
580
+ await routeState(opts.cwd, opts.packageRoot ?? opts.cwd, searchParams, res);
580
581
  return;
581
582
  }
582
583
  if (pathname === "/api/apply" && method === "POST") {
@@ -705,10 +706,21 @@ export async function startServer(opts) {
705
706
  // ---------------------------------------------------------------------------
706
707
  // Route: GET /api/state
707
708
  // ---------------------------------------------------------------------------
708
- async function routeState(cwd, packageRoot, res) {
709
+ async function routeState(cwd, packageRoot, searchParams, res) {
709
710
  try {
710
711
  const catalog = await buildScanCatalog(packageRoot);
711
- const report = await scanState(cwd, catalog);
712
+ const scopes = parseScopesParam(searchParams);
713
+ // Only wire `defaultExecPluginList` if the `claude` CLI is on PATH —
714
+ // otherwise the scanner's degraded-path warning ("claude-cli-missing")
715
+ // is what we want the UI to surface. Cache the result per process so we
716
+ // don't re-`which` on every /api/state poll.
717
+ const execPluginList = isClaudeOnPath()
718
+ ? (scope) => defaultExecPluginList(scope, cwd)
719
+ : undefined;
720
+ const report = await scanState(cwd, catalog, {
721
+ ...(scopes ? { scopes } : {}),
722
+ ...(execPluginList ? { execPluginList } : {}),
723
+ });
712
724
  sendJson(res, 200, report);
713
725
  }
714
726
  catch {
@@ -717,6 +729,40 @@ async function routeState(cwd, packageRoot, res) {
717
729
  sendJson(res, 500, { error: "scan-failed" });
718
730
  }
719
731
  }
732
+ let claudeOnPathCache = null;
733
+ function isClaudeOnPath() {
734
+ if (claudeOnPathCache !== null)
735
+ return claudeOnPathCache;
736
+ const res = spawnSync("which", ["claude"], { stdio: "ignore" });
737
+ claudeOnPathCache = res.status === 0;
738
+ return claudeOnPathCache;
739
+ }
740
+ /**
741
+ * Parses the /api/state `scopes` query into a per-category map for
742
+ * `scanState`. The query string shape is comma-separated `category:scope`
743
+ * pairs, e.g. `?scopes=workflow:user,skills:project,plugins:user`. Unknown
744
+ * categories and unknown scope values are dropped (rather than throwing) so
745
+ * the scanner falls back to install defaults on garbled input — keeps the
746
+ * Web UI degraded-but-functional rather than 500-ing on a stale link.
747
+ *
748
+ * Returns `null` when the param is absent so the caller can omit `opts.scopes`
749
+ * entirely and let `scanState` apply its built-in defaults.
750
+ */
751
+ function parseScopesParam(searchParams) {
752
+ const raw = searchParams.get("scopes");
753
+ if (!raw)
754
+ return null;
755
+ const allowedCategories = new Set(["workflow", "skills", "plugins", "hooks"]);
756
+ const allowedScopes = new Set(["user", "project"]);
757
+ const out = {};
758
+ for (const pair of raw.split(",")) {
759
+ const [cat, scope] = pair.split(":");
760
+ if (allowedCategories.has(cat) && allowedScopes.has(scope)) {
761
+ out[cat] = scope;
762
+ }
763
+ }
764
+ return Object.keys(out).length > 0 ? out : null;
765
+ }
720
766
  // ---------------------------------------------------------------------------
721
767
  // Route: GET /api/catalog
722
768
  // ---------------------------------------------------------------------------
package/dist/state.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PluginState, StateReport } from "./api-types.js";
1
+ import type { PluginState, ScanScope, StateReport } from "./api-types.js";
2
2
  export interface Catalog {
3
3
  workflowVersion: string;
4
4
  skills: Record<string, {
@@ -18,16 +18,51 @@ export interface Catalog {
18
18
  }>;
19
19
  hooks: Record<string, {
20
20
  description: string;
21
+ /** Coarse drift signal. The current production scan-catalog still
22
+ * populates this with sha256(index.mjs) for back-compat with the v0.x
23
+ * scanner that hashed the user's installed script. The new
24
+ * settings.json-based scanner ignores it for drift comparison unless
25
+ * the catalog also exposes the structured expected* fields below. */
21
26
  expectedHash: string;
27
+ /** Settings.json event name (e.g. "PostToolUse", "Notification"). When
28
+ * set, the scanner flags drift if the on-disk settings entry registers
29
+ * under a different event. Optional; left undefined the scanner trusts
30
+ * whatever event the marker was found under. */
31
+ expectedEvent?: string;
32
+ /** Settings.json `matcher` field (e.g. "Write|Edit" for PostToolUse).
33
+ * Empty string means "no matcher" (e.g. Notification hooks). When set,
34
+ * the scanner flags drift if the on-disk value differs. */
35
+ expectedMatcher?: string;
36
+ /** Settings.json `if` field (Claude-Code-specific filter expression).
37
+ * Same drift semantics as expectedMatcher. */
38
+ expectedIf?: string;
22
39
  }>;
23
40
  }
24
41
  export interface ScanOptions {
25
- execPluginList?: () => Promise<{
42
+ /** Run `claude plugins list` for the given scope. The scope argument is
43
+ * required so server.ts can pass --user / --project through to the CLI
44
+ * per opts.scopes.plugins. Implementations may accept a zero-arg legacy
45
+ * form for back-compat but MUST honor a scope argument when given. */
46
+ execPluginList?: (scope: ScanScope) => Promise<{
26
47
  installed: any[];
27
48
  available: any[];
28
49
  }>;
29
50
  readCodexConfig?: () => Promise<string | null>;
30
51
  readCodexPluginsDir?: () => Promise<Map<string, string>>;
52
+ /** Per-category scope picker. Each field is independently routed to the
53
+ * right truth source. Defaults match the Web UI's per-column picker:
54
+ * workflow = 'project', skills = 'project',
55
+ * plugins = 'user', hooks = 'user'. */
56
+ scopes?: {
57
+ workflow?: ScanScope;
58
+ skills?: ScanScope;
59
+ plugins?: ScanScope;
60
+ hooks?: ScanScope;
61
+ };
62
+ /** Test-time HOME override. When unset the scanner reads os.homedir()
63
+ * (which itself consults process.env.HOME / USERPROFILE), so tests that
64
+ * redirect HOME via env vars also work. */
65
+ homeDir?: string;
31
66
  }
32
67
  export declare function scanState(projectRoot: string, catalog: Catalog, opts?: ScanOptions): Promise<StateReport>;
33
68
  /**
@@ -48,10 +83,12 @@ export declare function scanState(projectRoot: string, catalog: Catalog, opts?:
48
83
  * we'll need a deliberate merge policy.
49
84
  */
50
85
  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<{
86
+ /** Default: run `claude plugins list --json` (no scope flag — the CLI
87
+ * doesn't expose one) plus the `--available` variant, then filter the
88
+ * installed records to the requested scope (and current projectRoot for
89
+ * project-scope) client-side. Server.ts decides whether to pass this
90
+ * function based on `which claude`. */
91
+ export declare function defaultExecPluginList(scope?: ScanScope, projectRoot?: string): Promise<{
55
92
  installed: any[];
56
93
  available: any[];
57
94
  }>;