auriga-cli 1.18.4 → 1.19.1

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
@@ -58,7 +58,7 @@ For a browser-based view of what's installed and one-click apply, run:
58
58
  npx auriga-cli web-ui
59
59
  ```
60
60
 
61
- This boots a local server on `127.0.0.1`, opens your default browser, and serves a dashboard that scans the current project, shows each module's status (installed / update-available / not-installed), and applies install / update / uninstall in a queue with live SSE progress. The server shuts down on its own ~15 s after the browser closes.
61
+ This boots a local server on `127.0.0.1`, opens your default browser, and serves a dashboard that scans the current project, shows each module's status (installed / not-installed / partial-install), and applies install / uninstall in a queue with live SSE progress. Re-running install is the update path — every installer is idempotent and overwrites in place. The server shuts down on its own ~15 s after the browser closes.
62
62
 
63
63
  The UI is opt-in — `npx auriga-cli` still launches the TTY menu below.
64
64
 
package/README.zh-CN.md CHANGED
@@ -52,13 +52,13 @@ npx -y auriga-cli --help # 完整 catalog + flag 说明
52
52
 
53
53
  ### Web UI(可选)
54
54
 
55
- 如果想在浏览器里看到“已装 / 可更新 / 未装”全景并一键 apply,跑:
55
+ 如果想在浏览器里看到“已装 / 未装 / 半装”全景并一键 apply,跑:
56
56
 
57
57
  ```bash
58
58
  npx auriga-cli web-ui
59
59
  ```
60
60
 
61
- 它会在 `127.0.0.1` 起一个本地 server、自动开浏览器,扫描当前项目并展示 5 个分类的状态。勾选要安装/更新/卸载的项目后点 Apply,SSE 实时回传执行进度。关浏览器后约 15 秒 server 自动退出。
61
+ 它会在 `127.0.0.1` 起一个本地 server、自动开浏览器,扫描当前项目并展示 5 个分类的状态。勾选要安装/卸载的项目后点 Apply,SSE 实时回传执行进度。需要"升级"时就重新 install——每个安装器都是幂等覆盖。关浏览器后约 15 秒 server 自动退出。
62
62
 
63
63
  Web UI 是显式入口;`npx auriga-cli` 仍然走下面的 TTY 菜单。
64
64
 
@@ -1,4 +1,9 @@
1
- export type ItemStatus = "installed" | "update-available" | "not-installed";
1
+ export type ItemStatus = "installed" | "not-installed"
2
+ /** Dual-Agent plugin where some target Agents have the plugin installed
3
+ * and some don't (e.g. Claude side installed, Codex side missing). The
4
+ * user-facing action is "install on the missing side"; the missing
5
+ * agents are enumerated in `PluginState.missingAgents`. */
6
+ | "partial-install";
2
7
  /**
3
8
  * Per-category scan scope. Each category (workflow / skills / plugins / hooks)
4
9
  * can be independently scanned in either user scope (~/.claude/, ~/.codex/)
@@ -23,8 +28,6 @@ export interface StateReport {
23
28
  }
24
29
  export interface WorkflowState {
25
30
  status: ItemStatus;
26
- currentVersion?: string;
27
- expectedVersion: string;
28
31
  /** Which scope the scanner read to produce this row. Reflects the scope
29
32
  * scanned, not where the file was found — e.g. when scope=user and
30
33
  * ~/.claude/CLAUDE.md is absent, observedScope is still "user". The
@@ -38,8 +41,6 @@ export interface SkillState {
38
41
  description: string;
39
42
  status: ItemStatus;
40
43
  isWorkflow: boolean;
41
- currentHash?: string;
42
- expectedHash: string;
43
44
  /** Scope the scanner read to produce this row. See WorkflowState comment. */
44
45
  observedScope?: ScanScope;
45
46
  }
@@ -53,38 +54,40 @@ export interface PluginState {
53
54
  * `agents.length === 2` the UI shows a BOTH badge and Apply installs to
54
55
  * each agent in turn. Status is aggregated across all targeted agents:
55
56
  * `installed` ⇔ all agents installed; `not-installed` ⇔ all not-installed;
56
- * any partial state (one side installed, other not) → `update-available`
57
- * so a single Apply backfills the missing side. */
57
+ * mixed → `partial-install` (some installed, some not). */
58
58
  agents: ApplyAgent[];
59
- currentVersion?: string;
60
- expectedVersion?: string;
61
- versionSource: "upstream-live" | "catalog";
59
+ /** Agents that target this plugin but don't have it installed. Populated
60
+ * iff `status === "partial-install"`. Drives the "Missing on Codex"
61
+ * UI caption + tells the user exactly which side needs backfill. */
62
+ missingAgents?: ApplyAgent[];
62
63
  /** Scope the scanner read to produce this row. Codex plugins are always
63
64
  * "user" (Codex has no project-scope plugin concept). See WorkflowState
64
65
  * comment on why this is typed optional. */
65
66
  observedScope?: ScanScope;
66
67
  /** True for plugins whose source lives in an upstream marketplace, not in
67
- * this repo (skill-creator / claude-md-management / codex). The scanner
68
- * short-circuits update-available reporting for these upgrades go
69
- * through `claude plugins update`, not us. The UI renders an EXTERNAL
70
- * badge to make the "not our jurisdiction" signal explicit. */
68
+ * this repo (skill-creator / claude-md-management / codex). Pure UI hint
69
+ * since v1.19.0 the EXTERNAL badge tells users upgrades go through
70
+ * `claude plugins update`, not via auriga-cli. */
71
71
  external?: boolean;
72
72
  }
73
73
  export interface HookState {
74
74
  name: string;
75
75
  description: string;
76
76
  status: ItemStatus;
77
- currentHash?: string;
78
- expectedHash: string;
79
77
  /** Scope the scanner read to produce this row. See WorkflowState comment. */
80
78
  observedScope?: ScanScope;
81
79
  }
82
80
  export interface StateWarning {
83
- code: "claude-cli-missing" | "codex-cli-missing" | "marketplace-offline" | "claude-code-not-installed" | "settings-unreadable" | "skill-malformed" | "workflow-unknown-version";
81
+ code: "claude-cli-missing" | "codex-cli-missing" | "marketplace-offline" | "claude-code-not-installed" | "settings-unreadable" | "skill-malformed"
82
+ /** Project-scope CLAUDE.md (or the user-scope fallback when scanning
83
+ * user scope) is present but has no recognizable `# auriga Workflow (vX.Y.Z)`
84
+ * header. The row reports `not-installed`; install will back up the
85
+ * existing file to `CLAUDE.md.bak` and write ours. */
86
+ | "workflow-foreign-claudemd";
84
87
  message: string;
85
88
  }
86
89
  export type ApplyCategory = "workflow" | "skill" | "recommended-skill" | "plugin" | "hook";
87
- export type ApplyAction = "install" | "update" | "uninstall";
90
+ export type ApplyAction = "install" | "uninstall";
88
91
  /**
89
92
  * Installer scope. Carried per-item so the Web UI can mix scopes within a
90
93
  * single apply batch.
@@ -17,6 +17,10 @@
17
17
  // hook: installHook(hookDef, "project",…)│ needs HookDef
18
18
  // hook: uninstallHook(name, …) │
19
19
  //
20
+ // v1.19.0 dropped the "update" action — every installer is idempotent and
21
+ // overwriting, so re-running install IS the update path. Apply receives
22
+ // "install" or "uninstall" only.
23
+ //
20
24
  // Spec: docs/architecture/web-ui.md §6.4 (apply execution model).
21
25
  import { installHook, loadHooksConfig, uninstallHook } from "./hooks.js";
22
26
  import { installPlugins, uninstallPlugin, } from "./plugins.js";
@@ -24,7 +28,6 @@ import { installRecommendedSkills, installSkills, uninstallSkill, } from "./skil
24
28
  import { installWorkflow, uninstallWorkflow } from "./workflow.js";
25
29
  const ALL_ACTIONS = new Set([
26
30
  "install",
27
- "update",
28
31
  "uninstall",
29
32
  ]);
30
33
  function assertAction(action) {
@@ -43,7 +46,7 @@ export function buildDefaultApplyHandlers(ctx) {
43
46
  // the Workflow column's EN/ZH-CN picker). Falls back to ctx lang for
44
47
  // CLI-mode callers that don't pass it.
45
48
  const installLang = requestedLang ?? lang;
46
- if (action === "install" || action === "update") {
49
+ if (action === "install") {
47
50
  await installWorkflow(packageRoot, {
48
51
  interactive: false,
49
52
  cwd,
@@ -68,7 +71,7 @@ export function buildDefaultApplyHandlers(ctx) {
68
71
  const skill = async (action, name, { onLog, scope }) => {
69
72
  assertAction(action);
70
73
  const installScope = scope ?? "project";
71
- if (action === "install" || action === "update") {
74
+ if (action === "install") {
72
75
  await installSkills(packageRoot, {
73
76
  interactive: false,
74
77
  cwd,
@@ -88,7 +91,7 @@ export function buildDefaultApplyHandlers(ctx) {
88
91
  const recommendedSkill = async (action, name, { onLog, scope }) => {
89
92
  assertAction(action);
90
93
  const installScope = scope ?? "project";
91
- if (action === "install" || action === "update") {
94
+ if (action === "install") {
92
95
  await installRecommendedSkills(packageRoot, {
93
96
  interactive: false,
94
97
  cwd,
@@ -119,7 +122,7 @@ export function buildDefaultApplyHandlers(ctx) {
119
122
  const failures = [];
120
123
  for (const agent of agents) {
121
124
  try {
122
- if (action === "install" || action === "update") {
125
+ if (action === "install") {
123
126
  await installPlugins(packageRoot, {
124
127
  interactive: false,
125
128
  cwd,
@@ -162,10 +165,9 @@ export function buildDefaultApplyHandlers(ctx) {
162
165
  });
163
166
  return;
164
167
  }
165
- // install + update both run installHook with the requested scope. The
166
- // hook installer is idempotent re-running == "update". Look up the
167
- // HookDef in the bundled registry; unknown name loud throw so the
168
- // SSE caller surfaces it as item:done success=false.
168
+ // Look up the HookDef in the bundled registry; unknown name → loud throw
169
+ // so the SSE caller surfaces it as item:done success=false. The hook
170
+ // installer is idempotent; re-running install is the update path.
169
171
  const config = loadHooksConfig(packageRoot);
170
172
  const hookDef = config.hooks.find((h) => h.name === name);
171
173
  if (!hookDef) {
package/dist/catalog.d.ts CHANGED
@@ -1,14 +1,6 @@
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;
12
4
  /** Build-time-baked agent map for plugin entries. Derived from
13
5
  * `.claude/plugins.json` ∪ `.agents/plugins/install.json` — those config
14
6
  * files are NOT shipped in the npm tarball, so the scanner can't read
@@ -17,21 +9,13 @@ export interface CatalogEntry {
17
9
  * Absent on skill / hook entries. */
18
10
  agents?: ("claude" | "codex")[];
19
11
  /** True for plugins whose source lives in an UPSTREAM marketplace
20
- * (skill-creator / claude-md-management / codex), not in this repo. The
21
- * scanner uses this to disable update-available reporting those
22
- * plugins update through `claude plugins update`, not through us. UI
23
- * surfaces an EXTERNAL badge so users know where to look. */
12
+ * (skill-creator / claude-md-management / codex), not in this repo.
13
+ * Pure UI hint since v1.19.0 the EXTERNAL badge tells users that
14
+ * upgrades go through `claude plugins update`, not us. */
24
15
  external?: boolean;
25
16
  }
26
17
  export interface Catalog {
27
18
  generatedAt: string;
28
- /** Workflow content version baked from `CLAUDE.md`'s `# auriga Workflow (vX.Y.Z)`
29
- * header at build time. MUST live here rather than be read at runtime
30
- * because `CLAUDE.md` is NOT in the npm tarball — `package.json` `files`
31
- * allowlists only `dist/`. Empty string when the header is unparseable;
32
- * the scanner then degrades to "trust whatever the user has" rather than
33
- * forcing phantom update-available against an empty expected value. */
34
- workflowVersion: string;
35
19
  workflowSkills: CatalogEntry[];
36
20
  recommendedSkills: CatalogEntry[];
37
21
  plugins: CatalogEntry[];
package/dist/catalog.json CHANGED
@@ -1,6 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-05-12T15:14:34.188Z",
3
- "workflowVersion": "1.7.0",
2
+ "generatedAt": "2026-05-13T02:06:00.781Z",
4
3
  "workflowSkills": [
5
4
  {
6
5
  "name": "brainstorming",
@@ -104,8 +103,7 @@
104
103
  "agents": [
105
104
  "claude",
106
105
  "codex"
107
- ],
108
- "expectedVersion": "1.1.0"
106
+ ]
109
107
  },
110
108
  {
111
109
  "name": "auriga-git-guards",
@@ -113,8 +111,7 @@
113
111
  "agents": [
114
112
  "claude",
115
113
  "codex"
116
- ],
117
- "expectedVersion": "1.1.0"
114
+ ]
118
115
  },
119
116
  {
120
117
  "name": "deep-review",
@@ -122,16 +119,14 @@
122
119
  "agents": [
123
120
  "claude",
124
121
  "codex"
125
- ],
126
- "expectedVersion": "0.3.1"
122
+ ]
127
123
  },
128
124
  {
129
125
  "name": "session-instructions-loader",
130
126
  "description": "(Codex) Injects extra instruction files on session start",
131
127
  "agents": [
132
128
  "codex"
133
- ],
134
- "expectedVersion": "1.0.0"
129
+ ]
135
130
  }
136
131
  ],
137
132
  "hooks": [
package/dist/cli.js CHANGED
@@ -576,7 +576,29 @@ async function runUi(p, version) {
576
576
  const { buildScanCatalog } = await import("./scan-catalog.js");
577
577
  const { ensureUiBundle } = await import("./ui-fetch.js");
578
578
  const cwd = process.cwd();
579
- const packageRoot = getPackageRoot();
579
+ // Two roots, two responsibilities (mirrors the TTY install path's pattern):
580
+ // - tarballRoot: where `dist/catalog.json` + the bundled DEV ui/dist live.
581
+ // Always read from the installed npm package; can't be fetched because
582
+ // dist/ is built artifact, not git content.
583
+ // - contentRoot: where the runtime install recipes live (CLAUDE.md,
584
+ // .claude/plugins.json, .claude/hooks/hooks.json, .agents/plugins/
585
+ // install.json + marketplace.json, skills-lock.json). These files are
586
+ // NOT in the npm tarball — the `files` allowlist only ships `dist/*`
587
+ // + npm defaults. They are fetched from GitHub, pinned to the CLI
588
+ // version tag, by fetchContentRoot(). Under DEV=1 fetchContentRoot
589
+ // short-circuits to the repo root so this is a no-op there.
590
+ // Without the contentRoot fix, tarball-installed Web UI users hit ENOENT
591
+ // on any Codex plugin install (apply handlers read .agents/plugins/
592
+ // install.json from packageRoot).
593
+ const tarballRoot = getPackageRoot();
594
+ let contentRoot;
595
+ try {
596
+ contentRoot = await fetchContentRoot();
597
+ }
598
+ catch (e) {
599
+ log.error(`Failed to fetch content: ${e.message}`);
600
+ return 1;
601
+ }
580
602
  // 1. Resolve UI bundle directory.
581
603
  let uiDir;
582
604
  if (p.uiDir) {
@@ -588,7 +610,7 @@ async function runUi(p, version) {
588
610
  }
589
611
  else if (process.env.DEV === "1") {
590
612
  // Dev convenience: prefer the locally-built ui/dist over a network fetch.
591
- const localDist = path.join(packageRoot, "ui", "dist");
613
+ const localDist = path.join(tarballRoot, "ui", "dist");
592
614
  if (fs.existsSync(path.join(localDist, "index.html"))) {
593
615
  uiDir = localDist;
594
616
  }
@@ -613,7 +635,9 @@ async function runUi(p, version) {
613
635
  // 2. Build scan catalog → ApplyCatalog + pluginAgentsByName.
614
636
  let scanCatalog;
615
637
  try {
616
- scanCatalog = await buildScanCatalog(packageRoot);
638
+ // dist/catalog.json ships in the tarball — read from tarballRoot, not
639
+ // the fetched content root (which doesn't carry build artifacts).
640
+ scanCatalog = await buildScanCatalog(tarballRoot);
617
641
  }
618
642
  catch (e) {
619
643
  log.error(`Failed to build catalog: ${e.message}`);
@@ -635,7 +659,10 @@ async function runUi(p, version) {
635
659
  pluginAgentsByName.set(name, def.agents);
636
660
  }
637
661
  const applyHandlers = buildDefaultApplyHandlers({
638
- packageRoot,
662
+ // contentRoot: install handlers read CLAUDE.md, plugins.json,
663
+ // hooks.json, install.json, marketplace.json — all CONTENT_FILES.
664
+ // Routing them at tarballRoot fails ENOENT for npm-installed users.
665
+ packageRoot: contentRoot,
639
666
  cwd,
640
667
  pluginAgentsByName,
641
668
  });
@@ -656,7 +683,11 @@ async function runUi(p, version) {
656
683
  port,
657
684
  token,
658
685
  cwd,
659
- packageRoot,
686
+ // server reads dist/catalog.json (tarball-shipped) via
687
+ // buildScanCatalog on each /api/state call; install-time content
688
+ // (install.json, plugins.json, CLAUDE.md, …) was already injected
689
+ // into applyHandlers above with contentRoot.
690
+ packageRoot: tarballRoot,
660
691
  heartbeatTimeoutMs: UI_HEARTBEAT_TIMEOUT_MS,
661
692
  applyHandlers,
662
693
  applyCatalog,
@@ -1,53 +1,20 @@
1
1
  // Build the scan-time Catalog (the shape src/state.ts consumes) from the
2
- // build-time `dist/catalog.json`. This module is intentionally a *thin
3
- // adapter* it must NOT read any file outside `dist/catalog.json`, because
4
- // the npm tarball's `files` field allowlists only `dist/`. Reading from
5
- // `packageRoot/CLAUDE.md`, `packageRoot/.claude/plugins.json`, or
6
- // `packageRoot/.agents/skills/<name>/SKILL.md` succeeds in dev (where
7
- // packageRoot === repoRoot) but silently returns empty for npm-installed
8
- // users, leaving the scanner unable to surface real update signals.
9
- //
10
- // Anything the scanner needs beyond what's already in catalog.json must
11
- // first be baked at build time in `src/build/generate-catalog.ts`.
12
- //
13
- // Scope of the current bake (covered fields):
14
- // - workflowVersion — from CLAUDE.md header
15
- // - plugin agents map — from .claude/plugins.json ∪ .agents/plugins/install.json
16
- // - plugin expectedVersion — from plugins/<name>/.claude-plugin/plugin.json
17
- // - plugin external flag — derived (no in-tree manifest = external)
18
- //
19
- // Out of scope for v1.18.4 (follow-up PRs):
20
- // - hook expectedEvent / expectedMatcher / expectedIf (from .claude/hooks/hooks.json)
21
- // - apply-time installer config (the install path reads .claude/plugins.json
22
- // directly — that needs runWebUi → fetchContentRoot rewire, not bake).
23
- import { readFile } from "node:fs/promises";
24
- import path from "node:path";
2
+ // build-time `dist/catalog.json`. Thin adapter reads dist/catalog.json
3
+ // only. v1.19.0 dropped all version / hash / event comparison from the
4
+ // scanner, so the build-time catalog is reduced to {description, agents?,
5
+ // external?} per entry; runtime reads outside dist/ are no longer needed.
25
6
  import { loadCatalog } from "./catalog.js";
26
- async function tryReadFile(p) {
27
- try {
28
- return await readFile(p, "utf8");
29
- }
30
- catch {
31
- return null;
32
- }
33
- }
34
7
  export async function buildScanCatalog(packageRoot) {
35
8
  const dist = loadCatalog(packageRoot);
36
- // Workflow version baked from CLAUDE.md header at build time. See
37
- // module comment for the "no runtime reads outside dist/" rule.
38
- const workflowVersion = dist.workflowVersion ?? "";
39
- // Skills: drift detection deliberately deferred to `npx skills update
40
- // --project`, which already compares against the skill's own upstream
41
- // repo HEAD. Our catalog snapshot would only know "what auriga-cli
42
- // shipped at this CLI release" — at best a stale proxy that mis-reports
43
- // legitimate user-side updates as drift. Setting expectedHash to "" puts
44
- // classifySkillByFile into wildcard mode: row reports installed if
45
- // SKILL.md exists, not-installed otherwise; never update-available.
9
+ // v1.19.0 dropped update-available status. The scanner is now presence-
10
+ // only: skills / hooks / plugins / workflow all report installed iff their
11
+ // truth source exists, not-installed otherwise. No version / hash / event
12
+ // comparison happens, so the build-time catalog is reduced to the bare
13
+ // {description, agents?, external?} shape per entry.
46
14
  const skills = {};
47
15
  for (const entry of dist.workflowSkills) {
48
16
  skills[entry.name] = {
49
17
  description: entry.description,
50
- expectedHash: "",
51
18
  isWorkflow: true,
52
19
  };
53
20
  }
@@ -55,16 +22,8 @@ export async function buildScanCatalog(packageRoot) {
55
22
  for (const entry of dist.recommendedSkills) {
56
23
  recommendedSkills[entry.name] = {
57
24
  description: entry.description,
58
- expectedHash: "",
59
25
  };
60
26
  }
61
- // Plugins: agents + expectedVersion + external all come from
62
- // dist/catalog.json now (baked in src/build/generate-catalog.ts). The
63
- // previous version of this module read .claude/plugins.json +
64
- // .agents/plugins/install.json at runtime — those files are NOT in the
65
- // npm tarball, so for installed users every plugin defaulted to a
66
- // ["claude"] agent classification (root cause of dual-Agent plugin
67
- // mis-classification in v1.18.x).
68
27
  const plugins = {};
69
28
  for (const entry of dist.plugins) {
70
29
  const agents = Array.isArray(entry.agents) && entry.agents.length > 0
@@ -73,47 +32,12 @@ export async function buildScanCatalog(packageRoot) {
73
32
  plugins[entry.name] = {
74
33
  description: entry.description,
75
34
  agents,
76
- ...(typeof entry.expectedVersion === "string" && entry.expectedVersion.length > 0
77
- ? { expectedVersion: entry.expectedVersion }
78
- : {}),
79
35
  ...(entry.external === true ? { external: true } : {}),
80
36
  };
81
37
  }
82
- // Hooks: TODO follow-up — bake expectedEvent / expectedMatcher / expectedIf
83
- // into dist/catalog.json the same way agents are baked. Currently the
84
- // runtime read of packageRoot/.claude/hooks/hooks.json works in dev
85
- // (packageRoot === repoRoot) but fails silently for npm-installed users
86
- // — `package.json` `files` allowlist doesn't ship `.claude/`. So hook
87
- // drift detection is correct in dev and degraded (always "installed" if
88
- // marker present) in production. Follow-up bake closes the dev/prod gap.
89
- const hooksJsonPath = path.join(packageRoot, ".claude", "hooks", "hooks.json");
90
- const hooksJsonRaw = await tryReadFile(hooksJsonPath);
91
- const hooksJson = hooksJsonRaw ? JSON.parse(hooksJsonRaw) : {};
92
- const settingsEventsByName = new Map();
93
- for (const h of hooksJson.hooks ?? []) {
94
- if (typeof h.name === "string" && h.settingsEvents?.length) {
95
- settingsEventsByName.set(h.name, h.settingsEvents[0]);
96
- }
97
- }
98
38
  const hooks = {};
99
39
  for (const entry of dist.hooks) {
100
- const ev = settingsEventsByName.get(entry.name);
101
- hooks[entry.name] = {
102
- description: entry.description,
103
- // Empty expectedHash flips state.ts into wildcard mode — drift judged
104
- // purely from the structured expected* fields below, not from a
105
- // settings-entry content hash.
106
- expectedHash: "",
107
- ...(typeof ev?.event === "string" ? { expectedEvent: ev.event } : {}),
108
- ...(typeof ev?.matcher === "string" ? { expectedMatcher: ev.matcher } : {}),
109
- ...(typeof ev?.if === "string" ? { expectedIf: ev.if } : {}),
110
- };
40
+ hooks[entry.name] = { description: entry.description };
111
41
  }
112
- return {
113
- workflowVersion,
114
- skills,
115
- recommendedSkills,
116
- plugins,
117
- hooks,
118
- };
42
+ return { skills, recommendedSkills, plugins, hooks };
119
43
  }
package/dist/server.js CHANGED
@@ -171,7 +171,7 @@ const VALID_CATEGORIES = new Set([
171
171
  "plugin",
172
172
  "hook",
173
173
  ]);
174
- const VALID_ACTIONS = new Set(["install", "update", "uninstall"]);
174
+ const VALID_ACTIONS = new Set(["install", "uninstall"]);
175
175
  const VALID_SCOPES = new Set(["project", "user"]);
176
176
  const VALID_LANGS = new Set(["en", "zh-CN"]);
177
177
  function parseApplyRequest(raw) {
package/dist/state.d.ts CHANGED
@@ -1,57 +1,37 @@
1
1
  import type { PluginState, ScanScope, StateReport } from "./api-types.js";
2
2
  export interface Catalog {
3
- workflowVersion: string;
4
3
  skills: Record<string, {
5
4
  description: string;
6
- expectedHash: string;
7
5
  isWorkflow: boolean;
8
6
  }>;
9
7
  recommendedSkills: Record<string, {
10
8
  description: string;
11
- expectedHash: string;
12
9
  }>;
13
10
  plugins: Record<string, {
14
11
  description: string;
15
12
  /** Agents this plugin can install into. Length 1 or 2. */
16
13
  agents: ("claude" | "codex")[];
17
- expectedVersion?: string;
18
- /** When true, this plugin is published in an UPSTREAM marketplace
19
- * (skill-creator / claude-md-management / codex), not in this repo.
20
- * Classifier MUST NOT report `update-available` for external plugins
21
- * those upgrade through `claude plugins update`, not us. The UI surfaces
22
- * an EXTERNAL badge so users know to defer to the upstream tool. */
14
+ /** True for plugins whose source lives in an upstream marketplace
15
+ * (skill-creator / claude-md-management / codex). Drives the UI's
16
+ * EXTERNAL badge — upgrades go through `claude plugins update`, not us.
17
+ * Pure UI hint since v1.19.0 (used to also gate update-available
18
+ * reporting; that surface was removed). */
23
19
  external?: boolean;
24
20
  }>;
25
21
  hooks: Record<string, {
26
22
  description: string;
27
- /** Coarse drift signal. The current production scan-catalog still
28
- * populates this with sha256(index.mjs) for back-compat with the v0.x
29
- * scanner that hashed the user's installed script. The new
30
- * settings.json-based scanner ignores it for drift comparison unless
31
- * the catalog also exposes the structured expected* fields below. */
32
- expectedHash: string;
33
- /** Settings.json event name (e.g. "PostToolUse", "Notification"). When
34
- * set, the scanner flags drift if the on-disk settings entry registers
35
- * under a different event. Optional; left undefined the scanner trusts
36
- * whatever event the marker was found under. */
37
- expectedEvent?: string;
38
- /** Settings.json `matcher` field (e.g. "Write|Edit" for PostToolUse).
39
- * Empty string means "no matcher" (e.g. Notification hooks). When set,
40
- * the scanner flags drift if the on-disk value differs. */
41
- expectedMatcher?: string;
42
- /** Settings.json `if` field (Claude-Code-specific filter expression).
43
- * Same drift semantics as expectedMatcher. */
44
- expectedIf?: string;
45
23
  }>;
46
24
  }
47
25
  export interface ScanOptions {
48
26
  /** Run `claude plugins list` for the given scope. The scope argument is
49
27
  * required so server.ts can pass --user / --project through to the CLI
50
28
  * per opts.scopes.plugins. Implementations may accept a zero-arg legacy
51
- * form for back-compat but MUST honor a scope argument when given. */
29
+ * form for back-compat but MUST honor a scope argument when given.
30
+ *
31
+ * Returns just the installed records — v1.19.0 dropped the parallel
32
+ * `--available` fetch since the scanner no longer compares versions. */
52
33
  execPluginList?: (scope: ScanScope) => Promise<{
53
34
  installed: any[];
54
- available: any[];
55
35
  }>;
56
36
  readCodexConfig?: () => Promise<string | null>;
57
37
  readCodexPluginsDir?: () => Promise<Map<string, string>>;
@@ -71,32 +51,16 @@ export interface ScanOptions {
71
51
  homeDir?: string;
72
52
  }
73
53
  export declare function scanState(projectRoot: string, catalog: Catalog, opts?: ScanOptions): Promise<StateReport>;
74
- /**
75
- * Dedupe plugins by `id`, merging dual-Agent records into a single
76
- * multi-agent row. Aggregation rules:
77
- *
78
- * agents: union of all agent arrays for this id (claude before codex).
79
- * status: installed ⇔ every agent's record is installed
80
- * not-installed ⇔ every agent's record is not-installed
81
- * otherwise → update-available (partial install or any agent
82
- * with a pending update). One Apply covers all
83
- * gaps because the handler iterates `agents`.
84
- *
85
- * Non-status fields (description, currentVersion, expectedVersion,
86
- * versionSource) come from the first record we see. Today both sides report
87
- * the same description (catalog-driven) and the same versions for any
88
- * registry-pinned plugin, so this is safe; if a future divergence appears
89
- * we'll need a deliberate merge policy.
90
- */
91
54
  export declare function mergePluginsById(records: PluginState[]): PluginState[];
92
55
  /** Default: run `claude plugins list --json` (no scope flag — the CLI
93
- * doesn't expose one) plus the `--available` variant, then filter the
94
- * installed records to the requested scope (and current projectRoot for
95
- * project-scope) client-side. Server.ts decides whether to pass this
96
- * function based on `which claude`. */
56
+ * doesn't expose one), then filter the installed records to the requested
57
+ * scope (and current projectRoot for project-scope) client-side. Server.ts
58
+ * decides whether to pass this function based on `which claude`.
59
+ *
60
+ * v1.19.0 dropped the parallel `--available` fetch — the scanner no longer
61
+ * compares versions, so there's no use for the upstream-live ref. */
97
62
  export declare function defaultExecPluginList(scope?: ScanScope, projectRoot?: string): Promise<{
98
63
  installed: any[];
99
- available: any[];
100
64
  }>;
101
65
  export declare function defaultReadCodexConfig(): Promise<string | null>;
102
66
  /** Walk `~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/`, returning