auriga-cli 1.18.3 → 1.18.5
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 +27 -4
- package/dist/catalog.d.ts +20 -0
- package/dist/catalog.json +32 -4
- package/dist/scan-catalog.js +50 -98
- package/dist/state.d.ts +6 -17
- package/dist/state.js +126 -45
- package/package.json +3 -3
package/dist/api-types.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export type ItemStatus = "installed" | "update-available" | "not-installed"
|
|
1
|
+
export type ItemStatus = "installed" | "update-available" | "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). Distinct
|
|
4
|
+
* from `update-available` because the action is "install on the missing
|
|
5
|
+
* side", not "upgrade to a newer version". The missing agents are
|
|
6
|
+
* enumerated in `PluginState.missingAgents`. */
|
|
7
|
+
| "partial-install";
|
|
2
8
|
/**
|
|
3
9
|
* Per-category scan scope. Each category (workflow / skills / plugins / hooks)
|
|
4
10
|
* can be independently scanned in either user scope (~/.claude/, ~/.codex/)
|
|
@@ -53,9 +59,15 @@ export interface PluginState {
|
|
|
53
59
|
* `agents.length === 2` the UI shows a BOTH badge and Apply installs to
|
|
54
60
|
* each agent in turn. Status is aggregated across all targeted agents:
|
|
55
61
|
* `installed` ⇔ all agents installed; `not-installed` ⇔ all not-installed;
|
|
56
|
-
*
|
|
57
|
-
*
|
|
62
|
+
* partial state (some installed, some not) → `partial-install`; agent-
|
|
63
|
+
* uniform version drift → `update-available`. */
|
|
58
64
|
agents: ApplyAgent[];
|
|
65
|
+
/** Agents that target this plugin but don't have it installed. Populated
|
|
66
|
+
* iff `status === "partial-install"`. Lets the UI render per-agent
|
|
67
|
+
* ✓/✗ marks and tell the user exactly which side needs `auriga-cli`
|
|
68
|
+
* to backfill. Always omitted for `installed` / `not-installed` /
|
|
69
|
+
* `update-available`. */
|
|
70
|
+
missingAgents?: ApplyAgent[];
|
|
59
71
|
currentVersion?: string;
|
|
60
72
|
expectedVersion?: string;
|
|
61
73
|
versionSource: "upstream-live" | "catalog";
|
|
@@ -63,6 +75,12 @@ export interface PluginState {
|
|
|
63
75
|
* "user" (Codex has no project-scope plugin concept). See WorkflowState
|
|
64
76
|
* comment on why this is typed optional. */
|
|
65
77
|
observedScope?: ScanScope;
|
|
78
|
+
/** True for plugins whose source lives in an upstream marketplace, not in
|
|
79
|
+
* this repo (skill-creator / claude-md-management / codex). The scanner
|
|
80
|
+
* short-circuits update-available reporting for these — upgrades go
|
|
81
|
+
* through `claude plugins update`, not us. The UI renders an EXTERNAL
|
|
82
|
+
* badge to make the "not our jurisdiction" signal explicit. */
|
|
83
|
+
external?: boolean;
|
|
66
84
|
}
|
|
67
85
|
export interface HookState {
|
|
68
86
|
name: string;
|
|
@@ -74,7 +92,12 @@ export interface HookState {
|
|
|
74
92
|
observedScope?: ScanScope;
|
|
75
93
|
}
|
|
76
94
|
export interface StateWarning {
|
|
77
|
-
code: "claude-cli-missing" | "codex-cli-missing" | "marketplace-offline" | "claude-code-not-installed" | "settings-unreadable" | "skill-malformed"
|
|
95
|
+
code: "claude-cli-missing" | "codex-cli-missing" | "marketplace-offline" | "claude-code-not-installed" | "settings-unreadable" | "skill-malformed"
|
|
96
|
+
/** Project-scope CLAUDE.md (or the user-scope fallback when scanning
|
|
97
|
+
* user scope) is present but has no recognizable `# auriga Workflow (vX.Y.Z)`
|
|
98
|
+
* header. The row reports `not-installed`; install will back up the
|
|
99
|
+
* existing file to `CLAUDE.md.bak` and write ours. */
|
|
100
|
+
| "workflow-foreign-claudemd";
|
|
78
101
|
message: string;
|
|
79
102
|
}
|
|
80
103
|
export type ApplyCategory = "workflow" | "skill" | "recommended-skill" | "plugin" | "hook";
|
package/dist/catalog.d.ts
CHANGED
|
@@ -9,9 +9,29 @@ export interface CatalogEntry {
|
|
|
9
9
|
* because `plugins/<name>/.claude-plugin/plugin.json` is NOT shipped in
|
|
10
10
|
* the npm tarball (see `package.json` `files` field). */
|
|
11
11
|
expectedVersion?: string;
|
|
12
|
+
/** Build-time-baked agent map for plugin entries. Derived from
|
|
13
|
+
* `.claude/plugins.json` ∪ `.agents/plugins/install.json` — those config
|
|
14
|
+
* files are NOT shipped in the npm tarball, so the scanner can't read
|
|
15
|
+
* them at runtime. Baking here lets `/api/state` correctly classify
|
|
16
|
+
* dual-Agent plugins as `["claude","codex"]` for installed users.
|
|
17
|
+
* Absent on skill / hook entries. */
|
|
18
|
+
agents?: ("claude" | "codex")[];
|
|
19
|
+
/** 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. */
|
|
24
|
+
external?: boolean;
|
|
12
25
|
}
|
|
13
26
|
export interface Catalog {
|
|
14
27
|
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;
|
|
15
35
|
workflowSkills: CatalogEntry[];
|
|
16
36
|
recommendedSkills: CatalogEntry[];
|
|
17
37
|
plugins: CatalogEntry[];
|
package/dist/catalog.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-05-
|
|
2
|
+
"generatedAt": "2026-05-12T16:35:23.439Z",
|
|
3
|
+
"workflowVersion": "1.7.0",
|
|
3
4
|
"workflowSkills": [
|
|
4
5
|
{
|
|
5
6
|
"name": "brainstorming",
|
|
@@ -75,34 +76,61 @@
|
|
|
75
76
|
"plugins": [
|
|
76
77
|
{
|
|
77
78
|
"name": "skill-creator",
|
|
78
|
-
"description": "Create and manage custom skills"
|
|
79
|
+
"description": "Create and manage custom skills",
|
|
80
|
+
"agents": [
|
|
81
|
+
"claude"
|
|
82
|
+
],
|
|
83
|
+
"external": true
|
|
79
84
|
},
|
|
80
85
|
{
|
|
81
86
|
"name": "claude-md-management",
|
|
82
|
-
"description": "Audit and improve CLAUDE.md files"
|
|
87
|
+
"description": "Audit and improve CLAUDE.md files",
|
|
88
|
+
"agents": [
|
|
89
|
+
"claude"
|
|
90
|
+
],
|
|
91
|
+
"external": true
|
|
83
92
|
},
|
|
84
93
|
{
|
|
85
94
|
"name": "codex",
|
|
86
|
-
"description": "Cross-model collaboration with Codex"
|
|
95
|
+
"description": "Cross-model collaboration with Codex",
|
|
96
|
+
"agents": [
|
|
97
|
+
"claude"
|
|
98
|
+
],
|
|
99
|
+
"external": true
|
|
87
100
|
},
|
|
88
101
|
{
|
|
89
102
|
"name": "auriga-go",
|
|
90
103
|
"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.",
|
|
104
|
+
"agents": [
|
|
105
|
+
"claude",
|
|
106
|
+
"codex"
|
|
107
|
+
],
|
|
91
108
|
"expectedVersion": "1.1.0"
|
|
92
109
|
},
|
|
93
110
|
{
|
|
94
111
|
"name": "auriga-git-guards",
|
|
95
112
|
"description": "(Claude/Codex) Git lifecycle guardrails: commit-reminder + PR-create snapshot inject + PR-ready structural block. Bundles the git-workflow skill (Claude Code + Codex).",
|
|
113
|
+
"agents": [
|
|
114
|
+
"claude",
|
|
115
|
+
"codex"
|
|
116
|
+
],
|
|
96
117
|
"expectedVersion": "1.1.0"
|
|
97
118
|
},
|
|
98
119
|
{
|
|
99
120
|
"name": "deep-review",
|
|
100
121
|
"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.",
|
|
122
|
+
"agents": [
|
|
123
|
+
"claude",
|
|
124
|
+
"codex"
|
|
125
|
+
],
|
|
101
126
|
"expectedVersion": "0.3.1"
|
|
102
127
|
},
|
|
103
128
|
{
|
|
104
129
|
"name": "session-instructions-loader",
|
|
105
130
|
"description": "(Codex) Injects extra instruction files on session start",
|
|
131
|
+
"agents": [
|
|
132
|
+
"codex"
|
|
133
|
+
],
|
|
106
134
|
"expectedVersion": "1.0.0"
|
|
107
135
|
}
|
|
108
136
|
],
|
package/dist/scan-catalog.js
CHANGED
|
@@ -1,37 +1,28 @@
|
|
|
1
|
-
// Build the scan-time Catalog (the shape src/state.ts consumes) from
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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.
|
|
5
9
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// skills-lock.json — expected SHA256 for every vendored skill
|
|
9
|
-
// .claude/plugins.json — Claude plugin entries (agent = "claude")
|
|
10
|
-
// .agents/plugins/install.json — Codex plugin entries (agent = "codex")
|
|
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
|
|
14
|
-
// CLAUDE.md — `# auriga Workflow (vX.Y.Z)` provides
|
|
15
|
-
// workflowVersion
|
|
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`.
|
|
16
12
|
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
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).
|
|
22
23
|
import { readFile } from "node:fs/promises";
|
|
23
24
|
import path from "node:path";
|
|
24
25
|
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
|
-
const WORKFLOW_VERSION_RE = /^#\s*auriga Workflow\s*\(v([\d.]+)\)/m;
|
|
35
26
|
async function tryReadFile(p) {
|
|
36
27
|
try {
|
|
37
28
|
return await readFile(p, "utf8");
|
|
@@ -42,24 +33,21 @@ async function tryReadFile(p) {
|
|
|
42
33
|
}
|
|
43
34
|
export async function buildScanCatalog(packageRoot) {
|
|
44
35
|
const dist = loadCatalog(packageRoot);
|
|
45
|
-
// Workflow version
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
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");
|
|
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.
|
|
58
46
|
const skills = {};
|
|
59
47
|
for (const entry of dist.workflowSkills) {
|
|
60
48
|
skills[entry.name] = {
|
|
61
49
|
description: entry.description,
|
|
62
|
-
expectedHash:
|
|
50
|
+
expectedHash: "",
|
|
63
51
|
isWorkflow: true,
|
|
64
52
|
};
|
|
65
53
|
}
|
|
@@ -67,73 +55,37 @@ export async function buildScanCatalog(packageRoot) {
|
|
|
67
55
|
for (const entry of dist.recommendedSkills) {
|
|
68
56
|
recommendedSkills[entry.name] = {
|
|
69
57
|
description: entry.description,
|
|
70
|
-
expectedHash:
|
|
58
|
+
expectedHash: "",
|
|
71
59
|
};
|
|
72
60
|
}
|
|
73
|
-
// Plugins:
|
|
74
|
-
//
|
|
75
|
-
//
|
|
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).
|
|
76
68
|
const plugins = {};
|
|
77
|
-
const claudePluginsText = await tryReadFile(path.join(packageRoot, ".claude", "plugins.json"));
|
|
78
|
-
const claudeNames = new Set();
|
|
79
|
-
if (claudePluginsText) {
|
|
80
|
-
try {
|
|
81
|
-
const parsed = JSON.parse(claudePluginsText);
|
|
82
|
-
for (const p of parsed.plugins ?? []) {
|
|
83
|
-
if (p.name)
|
|
84
|
-
claudeNames.add(p.name);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
/* ignore */
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const codexInstallText = await tryReadFile(path.join(packageRoot, ".agents", "plugins", "install.json"));
|
|
92
|
-
const codexNames = new Set();
|
|
93
|
-
if (codexInstallText) {
|
|
94
|
-
try {
|
|
95
|
-
const parsed = JSON.parse(codexInstallText);
|
|
96
|
-
for (const p of parsed.plugins ?? []) {
|
|
97
|
-
if (p.name)
|
|
98
|
-
codexNames.add(p.name);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
/* ignore */
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
69
|
for (const entry of dist.plugins) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// Apply installs to each side.
|
|
110
|
-
const agents = [];
|
|
111
|
-
if (claudeNames.has(entry.name))
|
|
112
|
-
agents.push("claude");
|
|
113
|
-
if (codexNames.has(entry.name))
|
|
114
|
-
agents.push("codex");
|
|
115
|
-
if (agents.length === 0)
|
|
116
|
-
agents.push("claude"); // unknown defaults to claude
|
|
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.
|
|
70
|
+
const agents = Array.isArray(entry.agents) && entry.agents.length > 0
|
|
71
|
+
? [...entry.agents]
|
|
72
|
+
: ["claude"]; // safety fallback: unknown shape defaults to claude
|
|
123
73
|
plugins[entry.name] = {
|
|
124
74
|
description: entry.description,
|
|
125
75
|
agents,
|
|
126
76
|
...(typeof entry.expectedVersion === "string" && entry.expectedVersion.length > 0
|
|
127
77
|
? { expectedVersion: entry.expectedVersion }
|
|
128
78
|
: {}),
|
|
79
|
+
...(entry.external === true ? { external: true } : {}),
|
|
129
80
|
};
|
|
130
81
|
}
|
|
131
|
-
// Hooks:
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
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.
|
|
137
89
|
const hooksJsonPath = path.join(packageRoot, ".claude", "hooks", "hooks.json");
|
|
138
90
|
const hooksJsonRaw = await tryReadFile(hooksJsonPath);
|
|
139
91
|
const hooksJson = hooksJsonRaw ? JSON.parse(hooksJsonRaw) : {};
|
package/dist/state.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export interface Catalog {
|
|
|
15
15
|
/** Agents this plugin can install into. Length 1 or 2. */
|
|
16
16
|
agents: ("claude" | "codex")[];
|
|
17
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. */
|
|
23
|
+
external?: boolean;
|
|
18
24
|
}>;
|
|
19
25
|
hooks: Record<string, {
|
|
20
26
|
description: string;
|
|
@@ -65,23 +71,6 @@ export interface ScanOptions {
|
|
|
65
71
|
homeDir?: string;
|
|
66
72
|
}
|
|
67
73
|
export declare function scanState(projectRoot: string, catalog: Catalog, opts?: ScanOptions): Promise<StateReport>;
|
|
68
|
-
/**
|
|
69
|
-
* Dedupe plugins by `id`, merging dual-Agent records into a single
|
|
70
|
-
* multi-agent row. Aggregation rules:
|
|
71
|
-
*
|
|
72
|
-
* agents: union of all agent arrays for this id (claude before codex).
|
|
73
|
-
* status: installed ⇔ every agent's record is installed
|
|
74
|
-
* not-installed ⇔ every agent's record is not-installed
|
|
75
|
-
* otherwise → update-available (partial install or any agent
|
|
76
|
-
* with a pending update). One Apply covers all
|
|
77
|
-
* gaps because the handler iterates `agents`.
|
|
78
|
-
*
|
|
79
|
-
* Non-status fields (description, currentVersion, expectedVersion,
|
|
80
|
-
* versionSource) come from the first record we see. Today both sides report
|
|
81
|
-
* the same description (catalog-driven) and the same versions for any
|
|
82
|
-
* registry-pinned plugin, so this is safe; if a future divergence appears
|
|
83
|
-
* we'll need a deliberate merge policy.
|
|
84
|
-
*/
|
|
85
74
|
export declare function mergePluginsById(records: PluginState[]): PluginState[];
|
|
86
75
|
/** Default: run `claude plugins list --json` (no scope flag — the CLI
|
|
87
76
|
* doesn't expose one) plus the `--available` variant, then filter the
|
package/dist/state.js
CHANGED
|
@@ -94,31 +94,27 @@ function dirExists(p) {
|
|
|
94
94
|
return false;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Dedupe plugins by `id`, merging dual-Agent records into a single
|
|
99
|
-
* multi-agent row. Aggregation rules:
|
|
100
|
-
*
|
|
101
|
-
* agents: union of all agent arrays for this id (claude before codex).
|
|
102
|
-
* status: installed ⇔ every agent's record is installed
|
|
103
|
-
* not-installed ⇔ every agent's record is not-installed
|
|
104
|
-
* otherwise → update-available (partial install or any agent
|
|
105
|
-
* with a pending update). One Apply covers all
|
|
106
|
-
* gaps because the handler iterates `agents`.
|
|
107
|
-
*
|
|
108
|
-
* Non-status fields (description, currentVersion, expectedVersion,
|
|
109
|
-
* versionSource) come from the first record we see. Today both sides report
|
|
110
|
-
* the same description (catalog-driven) and the same versions for any
|
|
111
|
-
* registry-pinned plugin, so this is safe; if a future divergence appears
|
|
112
|
-
* we'll need a deliberate merge policy.
|
|
113
|
-
*/
|
|
114
97
|
export function mergePluginsById(records) {
|
|
115
98
|
const byId = new Map();
|
|
116
|
-
|
|
99
|
+
// Per-agent (agent, status, version) tuples preserved across the fold so
|
|
100
|
+
// the aggregation step can emit `partial-install` + `missingAgents` when
|
|
101
|
+
// one side is installed and the other isn't, AND pick the *stale* side's
|
|
102
|
+
// currentVersion when status === "update-available" (otherwise the merge
|
|
103
|
+
// inherited Claude's version, producing the misleading `vX → vX` display
|
|
104
|
+
// in the v1.18.4 verification).
|
|
105
|
+
const perAgentByIdEntries = new Map();
|
|
117
106
|
for (const rec of records) {
|
|
107
|
+
// Pre-merge records are per-agent: their `agents[]` array contains the
|
|
108
|
+
// single Agent this row was scanned for. The merge step below unions
|
|
109
|
+
// those into the final dual-Agent record.
|
|
110
|
+
const recAgent = rec.agents[0];
|
|
111
|
+
const perAgentEntry = recAgent
|
|
112
|
+
? { agent: recAgent, status: rec.status, currentVersion: rec.currentVersion }
|
|
113
|
+
: null;
|
|
118
114
|
const existing = byId.get(rec.id);
|
|
119
115
|
if (!existing) {
|
|
120
116
|
byId.set(rec.id, { ...rec });
|
|
121
|
-
|
|
117
|
+
perAgentByIdEntries.set(rec.id, perAgentEntry ? [perAgentEntry] : []);
|
|
122
118
|
continue;
|
|
123
119
|
}
|
|
124
120
|
// Union agents preserving order: existing first, then any new ones.
|
|
@@ -129,28 +125,58 @@ export function mergePluginsById(records) {
|
|
|
129
125
|
seen.add(a);
|
|
130
126
|
}
|
|
131
127
|
}
|
|
132
|
-
|
|
128
|
+
if (perAgentEntry) {
|
|
129
|
+
perAgentByIdEntries.get(rec.id).push(perAgentEntry);
|
|
130
|
+
}
|
|
133
131
|
}
|
|
134
|
-
// Fold
|
|
135
|
-
|
|
132
|
+
// Fold per-agent tuples into the aggregated status, missingAgents, and
|
|
133
|
+
// (when applicable) the corrected currentVersion.
|
|
134
|
+
for (const [id, perAgent] of perAgentByIdEntries) {
|
|
136
135
|
const rec = byId.get(id);
|
|
137
136
|
if (!rec)
|
|
138
137
|
continue;
|
|
139
|
-
|
|
138
|
+
const aggregated = aggregateStatus(perAgent);
|
|
139
|
+
rec.status = aggregated.status;
|
|
140
|
+
if (aggregated.missingAgents && aggregated.missingAgents.length > 0) {
|
|
141
|
+
rec.missingAgents = aggregated.missingAgents;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
delete rec.missingAgents;
|
|
145
|
+
}
|
|
146
|
+
// When status is update-available, surface the version of the *stale*
|
|
147
|
+
// agent (one whose own status was update-available). Otherwise we'd
|
|
148
|
+
// keep whichever agent's version was merged first — Claude's, which
|
|
149
|
+
// may already be at the expected version, producing a `vX → vX`
|
|
150
|
+
// pseudo-upgrade display.
|
|
151
|
+
if (rec.status === "update-available") {
|
|
152
|
+
const stale = perAgent.find((r) => r.status === "update-available");
|
|
153
|
+
if (stale?.currentVersion) {
|
|
154
|
+
rec.currentVersion = stale.currentVersion;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
140
157
|
}
|
|
141
158
|
return Array.from(byId.values());
|
|
142
159
|
}
|
|
143
|
-
function aggregateStatus(
|
|
144
|
-
if (
|
|
145
|
-
return "not-installed";
|
|
146
|
-
if (
|
|
147
|
-
return "installed";
|
|
148
|
-
if (
|
|
149
|
-
return "not-installed";
|
|
150
|
-
//
|
|
151
|
-
// —
|
|
152
|
-
//
|
|
153
|
-
|
|
160
|
+
function aggregateStatus(records) {
|
|
161
|
+
if (records.length === 0)
|
|
162
|
+
return { status: "not-installed" };
|
|
163
|
+
if (records.every((r) => r.status === "installed"))
|
|
164
|
+
return { status: "installed" };
|
|
165
|
+
if (records.every((r) => r.status === "not-installed"))
|
|
166
|
+
return { status: "not-installed" };
|
|
167
|
+
// Mixed. If ANY agent reports not-installed, the row is partially installed
|
|
168
|
+
// — the user-facing action is "install on the missing side". Surfaces as a
|
|
169
|
+
// distinct status from version-drift `update-available` so the UI doesn't
|
|
170
|
+
// render misleading `vX → vX` upgrades (the v1.18.4 deep-review symptom).
|
|
171
|
+
const missingAgents = records
|
|
172
|
+
.filter((r) => r.status === "not-installed")
|
|
173
|
+
.map((r) => r.agent);
|
|
174
|
+
if (missingAgents.length > 0) {
|
|
175
|
+
return { status: "partial-install", missingAgents };
|
|
176
|
+
}
|
|
177
|
+
// Otherwise version drift on at least one targeted agent — single Apply
|
|
178
|
+
// upgrades the stale side(s).
|
|
179
|
+
return { status: "update-available" };
|
|
154
180
|
}
|
|
155
181
|
// ---------------------------------------------------------------------------
|
|
156
182
|
// Workflow
|
|
@@ -160,11 +186,12 @@ function workflowPathsForScope(scope, projectRoot, home) {
|
|
|
160
186
|
if (scope === "user") {
|
|
161
187
|
return [path.join(home, ".claude", "CLAUDE.md")];
|
|
162
188
|
}
|
|
163
|
-
// Project:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
189
|
+
// Project: only `<proj>/CLAUDE.md` — the auriga workflow installer
|
|
190
|
+
// (src/workflow.ts) writes here and never to `<proj>/.claude/CLAUDE.md`.
|
|
191
|
+
// The old fallback collapsed onto `$HOME/.claude/CLAUDE.md` when
|
|
192
|
+
// projectRoot === $HOME (user runs `web-ui` from home dir), leaking
|
|
193
|
+
// user-scope content into the project-scope row.
|
|
194
|
+
return [path.join(projectRoot, "CLAUDE.md")];
|
|
168
195
|
}
|
|
169
196
|
function scanWorkflow(scope, projectRoot, home, catalog, warnings) {
|
|
170
197
|
const expectedVersion = catalog.workflowVersion;
|
|
@@ -197,14 +224,18 @@ function scanWorkflow(scope, projectRoot, home, catalog, warnings) {
|
|
|
197
224
|
if (line.trim().length > 0)
|
|
198
225
|
break;
|
|
199
226
|
}
|
|
200
|
-
// CLAUDE.md exists but no recognizable auriga marker.
|
|
201
|
-
//
|
|
202
|
-
//
|
|
227
|
+
// CLAUDE.md exists but no recognizable auriga marker. The file is foreign
|
|
228
|
+
// — not our workflow. Report `not-installed` honestly; the install path
|
|
229
|
+
// (src/workflow.ts) already protects user content by backing it up to
|
|
230
|
+
// `CLAUDE.md.bak` before overwriting. Conflating "something exists here"
|
|
231
|
+
// with "auriga workflow installed" caused the v1.18.4 verification bug
|
|
232
|
+
// where running web-ui from `~` reported the user's `# Global`-headed
|
|
233
|
+
// `~/.claude/CLAUDE.md` as an installed workflow.
|
|
203
234
|
warnings.push({
|
|
204
|
-
code: "workflow-
|
|
205
|
-
message: `CLAUDE.md
|
|
235
|
+
code: "workflow-foreign-claudemd",
|
|
236
|
+
message: `Foreign CLAUDE.md detected at the workflow path — no auriga-workflow header. Install will back up to CLAUDE.md.bak.`,
|
|
206
237
|
});
|
|
207
|
-
return { status: "installed", expectedVersion, observedScope: scope };
|
|
238
|
+
return { status: "not-installed", expectedVersion, observedScope: scope };
|
|
208
239
|
}
|
|
209
240
|
// ---------------------------------------------------------------------------
|
|
210
241
|
// Skills + recommendedSkills
|
|
@@ -373,9 +404,13 @@ function degradedClaudeRow(id, def, scope) {
|
|
|
373
404
|
expectedVersion: def.expectedVersion,
|
|
374
405
|
versionSource: "upstream-live",
|
|
375
406
|
observedScope: scope,
|
|
407
|
+
...(def.external === true ? { external: true } : {}),
|
|
376
408
|
};
|
|
377
409
|
}
|
|
378
410
|
function classifyClaudePlugin(id, def, installed, available, scope) {
|
|
411
|
+
// `external` propagates onto every return below so the UI can surface the
|
|
412
|
+
// EXTERNAL badge regardless of install state.
|
|
413
|
+
const externalFlag = def.external === true ? { external: true } : {};
|
|
379
414
|
if (!installed || typeof installed.version !== "string") {
|
|
380
415
|
return {
|
|
381
416
|
id,
|
|
@@ -385,9 +420,31 @@ function classifyClaudePlugin(id, def, installed, available, scope) {
|
|
|
385
420
|
expectedVersion: typeof available?.source?.ref === "string" ? available.source.ref : def.expectedVersion,
|
|
386
421
|
versionSource: "upstream-live",
|
|
387
422
|
observedScope: scope,
|
|
423
|
+
...externalFlag,
|
|
388
424
|
};
|
|
389
425
|
}
|
|
390
426
|
const installedVersion = installed.version;
|
|
427
|
+
// External plugin short-circuit: we don't own these, so we don't claim
|
|
428
|
+
// authority on "what version they should be at". `claude plugins update`
|
|
429
|
+
// is the right channel — the scanner just confirms presence. Status stays
|
|
430
|
+
// "installed" even if installed.version differs from any signal we have.
|
|
431
|
+
// The catalog deliberately omits `expectedVersion` for externals, but we
|
|
432
|
+
// double-down with this guard so a future regression that accidentally
|
|
433
|
+
// populates expectedVersion still can't flip externals to update-available.
|
|
434
|
+
if (def.external === true) {
|
|
435
|
+
return {
|
|
436
|
+
id,
|
|
437
|
+
description: def.description,
|
|
438
|
+
status: "installed",
|
|
439
|
+
agents: ["claude"],
|
|
440
|
+
currentVersion: installedVersion,
|
|
441
|
+
// Don't surface any "expected" on externals — the upstream tool owns
|
|
442
|
+
// the version conversation.
|
|
443
|
+
versionSource: "upstream-live",
|
|
444
|
+
observedScope: scope,
|
|
445
|
+
...externalFlag,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
391
448
|
const ref = available?.source?.ref;
|
|
392
449
|
const normalizedAvailable = parseRef(typeof ref === "string" ? ref : undefined);
|
|
393
450
|
const normalizedInstalled = parseRef(installedVersion);
|
|
@@ -421,6 +478,7 @@ function classifyClaudePlugin(id, def, installed, available, scope) {
|
|
|
421
478
|
expectedVersion: expectedRaw,
|
|
422
479
|
versionSource,
|
|
423
480
|
observedScope: scope,
|
|
481
|
+
...externalFlag,
|
|
424
482
|
};
|
|
425
483
|
}
|
|
426
484
|
if (normalizedInstalled !== null && normalizedInstalled === expectedNormalized) {
|
|
@@ -433,6 +491,7 @@ function classifyClaudePlugin(id, def, installed, available, scope) {
|
|
|
433
491
|
expectedVersion: expectedRaw,
|
|
434
492
|
versionSource,
|
|
435
493
|
observedScope: scope,
|
|
494
|
+
...externalFlag,
|
|
436
495
|
};
|
|
437
496
|
}
|
|
438
497
|
return {
|
|
@@ -444,6 +503,7 @@ function classifyClaudePlugin(id, def, installed, available, scope) {
|
|
|
444
503
|
expectedVersion: expectedRaw,
|
|
445
504
|
versionSource,
|
|
446
505
|
observedScope: scope,
|
|
506
|
+
...externalFlag,
|
|
447
507
|
};
|
|
448
508
|
}
|
|
449
509
|
// ---------------------------------------------------------------------------
|
|
@@ -529,10 +589,12 @@ function degradedCodexRow(id, def) {
|
|
|
529
589
|
expectedVersion: def.expectedVersion,
|
|
530
590
|
versionSource: "catalog",
|
|
531
591
|
observedScope: "user",
|
|
592
|
+
...(def.external === true ? { external: true } : {}),
|
|
532
593
|
};
|
|
533
594
|
}
|
|
534
595
|
function classifyCodexPlugin(id, def, enabled, fsVersion) {
|
|
535
596
|
const expectedVersion = def.expectedVersion;
|
|
597
|
+
const externalFlag = def.external === true ? { external: true } : {};
|
|
536
598
|
if (!enabled) {
|
|
537
599
|
return {
|
|
538
600
|
id,
|
|
@@ -542,6 +604,7 @@ function classifyCodexPlugin(id, def, enabled, fsVersion) {
|
|
|
542
604
|
expectedVersion,
|
|
543
605
|
versionSource: "catalog",
|
|
544
606
|
observedScope: "user",
|
|
607
|
+
...externalFlag,
|
|
545
608
|
};
|
|
546
609
|
}
|
|
547
610
|
if (!fsVersion) {
|
|
@@ -553,6 +616,22 @@ function classifyCodexPlugin(id, def, enabled, fsVersion) {
|
|
|
553
616
|
expectedVersion,
|
|
554
617
|
versionSource: "catalog",
|
|
555
618
|
observedScope: "user",
|
|
619
|
+
...externalFlag,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
// External plugin short-circuit, same rationale as classifyClaudePlugin:
|
|
623
|
+
// we defer authority to `codex plugin marketplace update` and never flag
|
|
624
|
+
// update-available for upstream-owned plugins.
|
|
625
|
+
if (def.external === true) {
|
|
626
|
+
return {
|
|
627
|
+
id,
|
|
628
|
+
description: def.description,
|
|
629
|
+
status: "installed",
|
|
630
|
+
agents: ["codex"],
|
|
631
|
+
currentVersion: fsVersion,
|
|
632
|
+
versionSource: "catalog",
|
|
633
|
+
observedScope: "user",
|
|
634
|
+
...externalFlag,
|
|
556
635
|
};
|
|
557
636
|
}
|
|
558
637
|
if (!expectedVersion || fsVersion === expectedVersion) {
|
|
@@ -565,6 +644,7 @@ function classifyCodexPlugin(id, def, enabled, fsVersion) {
|
|
|
565
644
|
expectedVersion,
|
|
566
645
|
versionSource: "catalog",
|
|
567
646
|
observedScope: "user",
|
|
647
|
+
...externalFlag,
|
|
568
648
|
};
|
|
569
649
|
}
|
|
570
650
|
return {
|
|
@@ -576,6 +656,7 @@ function classifyCodexPlugin(id, def, enabled, fsVersion) {
|
|
|
576
656
|
expectedVersion,
|
|
577
657
|
versionSource: "catalog",
|
|
578
658
|
observedScope: "user",
|
|
659
|
+
...externalFlag,
|
|
579
660
|
};
|
|
580
661
|
}
|
|
581
662
|
/** Return the set of plugin ids whose `[plugins."<id>"]` table has
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auriga-cli",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.5",
|
|
4
4
|
"description": "Interactive CLI to install Claude Code harness modules (Workflow, Skills, Recommended Skills, Plugins, Hooks)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -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/hooks.test.js dist-test/tests/hooks-uninstall.test.js 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/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-uninstall.test.js",
|
|
29
|
-
"test:watch": "tsc -p tsconfig.test.json --watch & node --test --watch --experimental-test-module-mocks dist-test/tests/hooks.test.js dist-test/tests/hooks-uninstall.test.js 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/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-uninstall.test.js",
|
|
28
|
+
"test": "tsc -p tsconfig.test.json && DEV=1 node --test --experimental-test-module-mocks dist-test/tests/hooks.test.js dist-test/tests/hooks-uninstall.test.js 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/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-uninstall.test.js dist-test/tests/tarball-shape.test.js",
|
|
29
|
+
"test:watch": "tsc -p tsconfig.test.json --watch & node --test --watch --experimental-test-module-mocks dist-test/tests/hooks.test.js dist-test/tests/hooks-uninstall.test.js 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/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-uninstall.test.js dist-test/tests/tarball-shape.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",
|