auriga-cli 1.18.0 → 1.18.3
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/dist/api-types.d.ts +25 -1
- package/dist/catalog.d.ts +8 -0
- package/dist/catalog.json +9 -5
- package/dist/cli.js +6 -1
- package/dist/scan-catalog.js +60 -31
- package/dist/server.js +50 -4
- package/dist/state.d.ts +43 -6
- package/dist/state.js +447 -164
- package/package.json +1 -1
package/dist/api-types.d.ts
CHANGED
|
@@ -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.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export interface CatalogEntry {
|
|
2
2
|
name: string;
|
|
3
3
|
description: string;
|
|
4
|
+
/** Build-time-baked plugin version. Set ONLY for plugin entries whose
|
|
5
|
+
* source lives in this repo's `plugins/<name>/` directory — the scanner
|
|
6
|
+
* uses it to surface "update-available" when the user's installed copy
|
|
7
|
+
* is older. Absent for skill / hook entries and for external-marketplace
|
|
8
|
+
* plugins (whose manifest lives upstream). Must be baked at build time
|
|
9
|
+
* because `plugins/<name>/.claude-plugin/plugin.json` is NOT shipped in
|
|
10
|
+
* the npm tarball (see `package.json` `files` field). */
|
|
11
|
+
expectedVersion?: string;
|
|
4
12
|
}
|
|
5
13
|
export interface Catalog {
|
|
6
14
|
generatedAt: string;
|
package/dist/catalog.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-05-
|
|
2
|
+
"generatedAt": "2026-05-12T13:58:42.583Z",
|
|
3
3
|
"workflowSkills": [
|
|
4
4
|
{
|
|
5
5
|
"name": "brainstorming",
|
|
@@ -87,19 +87,23 @@
|
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
"name": "auriga-go",
|
|
90
|
-
"description": "(Claude/Codex) Workflow autopilot for the auriga workflow. Reminder-based navigation across CLAUDE.md's phases. Bundles a /goalify skill that plans an autonomous goal from a spec or work-in-progress and dispatches it via Claude Code's built-in /goal command."
|
|
90
|
+
"description": "(Claude/Codex) Workflow autopilot for the auriga workflow. Reminder-based navigation across CLAUDE.md's phases. Bundles a /goalify skill that plans an autonomous goal from a spec or work-in-progress and dispatches it via Claude Code's built-in /goal command.",
|
|
91
|
+
"expectedVersion": "1.1.0"
|
|
91
92
|
},
|
|
92
93
|
{
|
|
93
94
|
"name": "auriga-git-guards",
|
|
94
|
-
"description": "(Claude/Codex) Git lifecycle guardrails: commit-reminder + PR-create snapshot inject + PR-ready structural block. Bundles the git-workflow skill (Claude Code + Codex)."
|
|
95
|
+
"description": "(Claude/Codex) Git lifecycle guardrails: commit-reminder + PR-create snapshot inject + PR-ready structural block. Bundles the git-workflow skill (Claude Code + Codex).",
|
|
96
|
+
"expectedVersion": "1.1.0"
|
|
95
97
|
},
|
|
96
98
|
{
|
|
97
99
|
"name": "deep-review",
|
|
98
|
-
"description": "(Claude/Codex) Multi-dimensional PR review orchestrator. Dispatches parallel reviewers (spec-conformance, correctness, test-quality, docs-sync, robustness, security, ux, performance, structure, code-quality, skill-plugin-quality) and synthesizes findings into an actionable punch list. Supports project-level custom reviewers via docs/rules/review/ and ships a reviewer-creator skill for scaffolding them."
|
|
100
|
+
"description": "(Claude/Codex) Multi-dimensional PR review orchestrator. Dispatches parallel reviewers (spec-conformance, correctness, test-quality, docs-sync, robustness, security, ux, performance, structure, code-quality, skill-plugin-quality) and synthesizes findings into an actionable punch list. Supports project-level custom reviewers via docs/rules/review/ and ships a reviewer-creator skill for scaffolding them.",
|
|
101
|
+
"expectedVersion": "0.3.1"
|
|
99
102
|
},
|
|
100
103
|
{
|
|
101
104
|
"name": "session-instructions-loader",
|
|
102
|
-
"description": "(Codex) Injects extra instruction files on session start"
|
|
105
|
+
"description": "(Codex) Injects extra instruction files on session start",
|
|
106
|
+
"expectedVersion": "1.0.0"
|
|
103
107
|
}
|
|
104
108
|
],
|
|
105
109
|
"hooks": [
|
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
|
-
|
|
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");
|
package/dist/scan-catalog.js
CHANGED
|
@@ -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
|
|
11
|
+
// .claude/hooks/hooks.json — registers 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,15 @@ 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
|
+
}
|
|
23
34
|
const WORKFLOW_VERSION_RE = /^#\s*auriga Workflow\s*\(v([\d.]+)\)/m;
|
|
24
35
|
async function tryReadFile(p) {
|
|
25
36
|
try {
|
|
@@ -29,10 +40,6 @@ async function tryReadFile(p) {
|
|
|
29
40
|
return null;
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
|
-
async function sha256File(p) {
|
|
33
|
-
const bytes = await readFile(p);
|
|
34
|
-
return createHash("sha256").update(bytes).digest("hex");
|
|
35
|
-
}
|
|
36
43
|
export async function buildScanCatalog(packageRoot) {
|
|
37
44
|
const dist = loadCatalog(packageRoot);
|
|
38
45
|
// Workflow version: parse from auriga-cli's own CLAUDE.md template.
|
|
@@ -41,22 +48,18 @@ export async function buildScanCatalog(packageRoot) {
|
|
|
41
48
|
const claudeMd = await tryReadFile(path.join(packageRoot, "CLAUDE.md"));
|
|
42
49
|
const m = claudeMd ? WORKFLOW_VERSION_RE.exec(claudeMd) : null;
|
|
43
50
|
const workflowVersion = m ? m[1] : "";
|
|
44
|
-
// Skills + recommended:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
catch {
|
|
52
|
-
// corrupted lock → no expectations; user state still classifies safely
|
|
53
|
-
}
|
|
54
|
-
}
|
|
51
|
+
// Skills + recommended: sha256 of each shipped SKILL.md. This is the same
|
|
52
|
+
// hash the scanner computes for `<scope>/.claude/skills/<name>/SKILL.md`
|
|
53
|
+
// at scan time, so a match means "user's installed copy is identical to
|
|
54
|
+
// the version auriga-cli ships". skills-lock.json's `computedHash` field
|
|
55
|
+
// hashes the entire skill directory (every file, sorted), which doesn't
|
|
56
|
+
// line up with the scanner's per-file model — we deliberately ignore it.
|
|
57
|
+
const skillsRoot = path.join(packageRoot, ".agents", "skills");
|
|
55
58
|
const skills = {};
|
|
56
59
|
for (const entry of dist.workflowSkills) {
|
|
57
60
|
skills[entry.name] = {
|
|
58
61
|
description: entry.description,
|
|
59
|
-
expectedHash:
|
|
62
|
+
expectedHash: await sha256SkillMd(skillsRoot, entry.name),
|
|
60
63
|
isWorkflow: true,
|
|
61
64
|
};
|
|
62
65
|
}
|
|
@@ -64,7 +67,7 @@ export async function buildScanCatalog(packageRoot) {
|
|
|
64
67
|
for (const entry of dist.recommendedSkills) {
|
|
65
68
|
recommendedSkills[entry.name] = {
|
|
66
69
|
description: entry.description,
|
|
67
|
-
expectedHash:
|
|
70
|
+
expectedHash: await sha256SkillMd(skillsRoot, entry.name),
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
73
|
// Plugins: split by agent based on which config file lists them. A
|
|
@@ -111,22 +114,48 @@ export async function buildScanCatalog(packageRoot) {
|
|
|
111
114
|
agents.push("codex");
|
|
112
115
|
if (agents.length === 0)
|
|
113
116
|
agents.push("claude"); // unknown defaults to claude
|
|
114
|
-
|
|
117
|
+
// expectedVersion comes from the build-time-baked field in dist/catalog.json
|
|
118
|
+
// (populated by `src/build/generate-catalog.ts` from
|
|
119
|
+
// `plugins/<name>/.claude-plugin/plugin.json`). It MUST be baked at build
|
|
120
|
+
// time because `plugins/<name>/` is NOT shipped in the npm tarball
|
|
121
|
+
// (`package.json` `files` field allowlists only `dist/`), so reading it
|
|
122
|
+
// at runtime from packageRoot would silently fail for npm-installed users.
|
|
123
|
+
plugins[entry.name] = {
|
|
124
|
+
description: entry.description,
|
|
125
|
+
agents,
|
|
126
|
+
...(typeof entry.expectedVersion === "string" && entry.expectedVersion.length > 0
|
|
127
|
+
? { expectedVersion: entry.expectedVersion }
|
|
128
|
+
: {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Hooks: the scanner reads <scope>/.claude/settings.json and matches by
|
|
132
|
+
// `_marker` (see state.ts scanHooks). Drift detection compares the
|
|
133
|
+
// registered event / matcher / if values against the hook's canonical
|
|
134
|
+
// settingsEvents[0] from .claude/hooks/hooks.json. We deliberately do NOT
|
|
135
|
+
// hash index.mjs — the user's installed index.mjs lives at <scope>/.claude/
|
|
136
|
+
// hooks/<name>/index.mjs and isn't part of the settings.json drift signal.
|
|
137
|
+
const hooksJsonPath = path.join(packageRoot, ".claude", "hooks", "hooks.json");
|
|
138
|
+
const hooksJsonRaw = await tryReadFile(hooksJsonPath);
|
|
139
|
+
const hooksJson = hooksJsonRaw ? JSON.parse(hooksJsonRaw) : {};
|
|
140
|
+
const settingsEventsByName = new Map();
|
|
141
|
+
for (const h of hooksJson.hooks ?? []) {
|
|
142
|
+
if (typeof h.name === "string" && h.settingsEvents?.length) {
|
|
143
|
+
settingsEventsByName.set(h.name, h.settingsEvents[0]);
|
|
144
|
+
}
|
|
115
145
|
}
|
|
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
146
|
const hooks = {};
|
|
120
147
|
for (const entry of dist.hooks) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
expectedHash
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
const ev = settingsEventsByName.get(entry.name);
|
|
149
|
+
hooks[entry.name] = {
|
|
150
|
+
description: entry.description,
|
|
151
|
+
// Empty expectedHash flips state.ts into wildcard mode — drift judged
|
|
152
|
+
// purely from the structured expected* fields below, not from a
|
|
153
|
+
// settings-entry content hash.
|
|
154
|
+
expectedHash: "",
|
|
155
|
+
...(typeof ev?.event === "string" ? { expectedEvent: ev.event } : {}),
|
|
156
|
+
...(typeof ev?.matcher === "string" ? { expectedMatcher: ev.matcher } : {}),
|
|
157
|
+
...(typeof ev?.if === "string" ? { expectedIf: ev.if } : {}),
|
|
158
|
+
};
|
|
130
159
|
}
|
|
131
160
|
return {
|
|
132
161
|
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
|
|
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
|
-
|
|
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`
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
|
|
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
|
}>;
|