auriga-cli 1.20.3 → 1.20.4

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/catalog.d.ts CHANGED
@@ -2,11 +2,11 @@ export interface CatalogEntry {
2
2
  name: string;
3
3
  description: string;
4
4
  /** Build-time-baked agent map for plugin entries. Derived from
5
- * `.claude/plugins.json` `.agents/plugins/install.json` — those config
6
- * files are NOT shipped in the npm tarball, so the scanner can't read
7
- * them at runtime. Baking here lets `/api/state` correctly classify
8
- * dual-Agent plugins as `["claude","codex"]` for installed users.
9
- * Absent on skill / hook entries. */
5
+ * repo marketplace manifests plus `extra_plugin_configs.json` — those
6
+ * config files are NOT shipped in the npm tarball, so the scanner can't
7
+ * read them at runtime. Baking here lets `/api/state` correctly classify
8
+ * dual-Agent plugins as `["claude","codex"]` for installed users. Absent
9
+ * on skill / hook entries. */
10
10
  agents?: ("claude" | "codex")[];
11
11
  /** True for plugins whose source lives in an UPSTREAM marketplace
12
12
  * (skill-creator / claude-md-management / codex), not in this repo.
package/dist/catalog.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-05-14T09:20:36.350Z",
2
+ "generatedAt": "2026-05-14T14:34:30.876Z",
3
3
  "workflowSkills": [
4
4
  {
5
5
  "name": "brainstorming",
@@ -61,30 +61,6 @@
61
61
  }
62
62
  ],
63
63
  "plugins": [
64
- {
65
- "name": "skill-creator",
66
- "description": "Create and manage custom skills",
67
- "agents": [
68
- "claude"
69
- ],
70
- "external": true
71
- },
72
- {
73
- "name": "claude-md-management",
74
- "description": "Audit and improve CLAUDE.md files",
75
- "agents": [
76
- "claude"
77
- ],
78
- "external": true
79
- },
80
- {
81
- "name": "codex",
82
- "description": "Cross-model collaboration with Codex",
83
- "agents": [
84
- "claude"
85
- ],
86
- "external": true
87
- },
88
64
  {
89
65
  "name": "auriga-go",
90
66
  "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.",
@@ -95,7 +71,7 @@
95
71
  },
96
72
  {
97
73
  "name": "auriga-git-guards",
98
- "description": "(Claude/Codex) Git lifecycle guardrails: commit-reminder + PR-create snapshot inject + PR-ready structural block. Bundles the git-workflow skill (Claude Code + Codex).",
74
+ "description": "(Claude/Codex) Git lifecycle guardrails: commit-reminder (PostToolUse on Edit/Write/MultiEdit/apply_patch) + pr-create-guard with PR-body snapshot and Conventional Commits title check (PostToolUse on `gh pr create`) + pr-ready-guard structural block at PR Ready transitions, firing on BOTH `gh pr ready` AND `gh pr create` without `--draft` (PreToolUse). Bundles the git-workflow skill. Dual-Agent compatible (Claude Code + Codex).",
99
75
  "agents": [
100
76
  "claude",
101
77
  "codex"
@@ -103,7 +79,7 @@
103
79
  },
104
80
  {
105
81
  "name": "auriga-workflow-skills",
106
- "description": "(Claude/Codex) Bundles the auriga-owned workflow execution skills: incremental-impl, test-designer, and session-compound.",
82
+ "description": "(Claude/Codex) Bundles the auriga-owned workflow execution skills: incremental-impl, test-designer, and session-compound. Dual-Agent compatible (Claude Code + Codex).",
107
83
  "agents": [
108
84
  "claude",
109
85
  "codex"
@@ -111,19 +87,43 @@
111
87
  },
112
88
  {
113
89
  "name": "auriga-notify",
114
- "description": "(opt-in) Opt-in macOS native notification hook for Claude Code Notification events; migrates legacy notify config and icon on install.",
90
+ "description": "(opt-in) Opt-in macOS native notification hook for Claude Code Notification events.",
115
91
  "agents": [
116
92
  "claude"
117
93
  ]
118
94
  },
119
95
  {
120
96
  "name": "deep-review",
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.",
97
+ "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
98
  "agents": [
123
99
  "claude",
124
100
  "codex"
125
101
  ]
126
102
  },
103
+ {
104
+ "name": "skill-creator",
105
+ "description": "Create and manage custom skills",
106
+ "agents": [
107
+ "claude"
108
+ ],
109
+ "external": true
110
+ },
111
+ {
112
+ "name": "claude-md-management",
113
+ "description": "Audit and improve CLAUDE.md files",
114
+ "agents": [
115
+ "claude"
116
+ ],
117
+ "external": true
118
+ },
119
+ {
120
+ "name": "codex",
121
+ "description": "Cross-model collaboration with Codex",
122
+ "agents": [
123
+ "claude"
124
+ ],
125
+ "external": true
126
+ },
127
127
  {
128
128
  "name": "session-instructions-loader",
129
129
  "description": "(Codex) Injects extra instruction files on session start",
package/dist/cli.js CHANGED
@@ -593,15 +593,15 @@ async function runUi(p, version) {
593
593
  // Always read from the installed npm package; can't be fetched because
594
594
  // dist/ is built artifact, not git content.
595
595
  // - contentRoot: where the runtime install recipes live (CLAUDE.md,
596
- // .claude/plugins.json, .claude/hooks/hooks.json, .agents/plugins/
597
- // install.json + marketplace.json, skills-lock.json). These files are
596
+ // marketplace manifests, extra_plugin_configs.json, skills-lock.json).
597
+ // These files are
598
598
  // NOT in the npm tarball — the `files` allowlist only ships `dist/*`
599
599
  // + npm defaults. They are fetched from GitHub, pinned to the CLI
600
600
  // version tag, by fetchContentRoot(). Under DEV=1 fetchContentRoot
601
601
  // short-circuits to the repo root so this is a no-op there.
602
602
  // Without the contentRoot fix, tarball-installed Web UI users hit ENOENT
603
- // on any Codex plugin install (apply handlers read .agents/plugins/
604
- // install.json from packageRoot).
603
+ // on any plugin install (apply handlers read non-tarball install inputs
604
+ // from packageRoot).
605
605
  const tarballRoot = getPackageRoot();
606
606
  let contentRoot;
607
607
  try {
@@ -671,8 +671,8 @@ async function runUi(p, version) {
671
671
  pluginAgentsByName.set(name, def.agents);
672
672
  }
673
673
  const applyHandlers = buildDefaultApplyHandlers({
674
- // contentRoot: install handlers read CLAUDE.md, plugins.json,
675
- // hooks.json, install.json, marketplace.json — all CONTENT_FILES.
674
+ // contentRoot: install handlers read CLAUDE.md, marketplace manifests,
675
+ // extra_plugin_configs.json, and skills-lock.json — all CONTENT_FILES.
676
676
  // Routing them at tarballRoot fails ENOENT for npm-installed users.
677
677
  packageRoot: contentRoot,
678
678
  cwd,
@@ -697,7 +697,7 @@ async function runUi(p, version) {
697
697
  cwd,
698
698
  // server reads dist/catalog.json (tarball-shipped) via
699
699
  // buildScanCatalog on each /api/state call; install-time content
700
- // (install.json, plugins.json, CLAUDE.md, …) was already injected
700
+ // (marketplace manifests, extra plugin config, CLAUDE.md, …) was already injected
701
701
  // into applyHandlers above with contentRoot.
702
702
  packageRoot: tarballRoot,
703
703
  heartbeatTimeoutMs: UI_HEARTBEAT_TIMEOUT_MS,
@@ -1,4 +1,3 @@
1
- import { type MarketplaceRef } from "./marketplace.js";
2
1
  export interface CodexMarketplacePlugin {
3
2
  name: string;
4
3
  source?: {
@@ -10,16 +9,6 @@ export interface CodexMarketplace {
10
9
  name: string;
11
10
  plugins: CodexMarketplacePlugin[];
12
11
  }
13
- export interface CodexInstallPlugin {
14
- name: string;
15
- description?: string;
16
- defaultOn?: boolean;
17
- marketplace?: MarketplaceRef;
18
- }
19
- export interface CodexInstallConfig {
20
- plugins: CodexInstallPlugin[];
21
- }
22
12
  export declare function validateCodexMarketplace(raw: unknown): asserts raw is CodexMarketplace;
23
- export declare function validateCodexInstallConfig(raw: unknown): asserts raw is CodexInstallConfig;
24
13
  export declare function codexLocalPluginPath(plugin: CodexMarketplacePlugin): string | undefined;
25
14
  export declare function codexManifestPath(plugin: CodexMarketplacePlugin): string | undefined;
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { MARKETPLACE_NAME_RE, validateMarketplaceField, } from "./marketplace.js";
2
+ import { MARKETPLACE_NAME_RE } from "./marketplace.js";
3
3
  const PLUGIN_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
4
4
  export function validateCodexMarketplace(raw) {
5
5
  if (!raw || typeof raw !== "object") {
@@ -22,33 +22,6 @@ export function validateCodexMarketplace(raw) {
22
22
  }
23
23
  }
24
24
  }
25
- export function validateCodexInstallConfig(raw) {
26
- if (!raw || typeof raw !== "object") {
27
- throw new Error("Codex install.json: root must be an object");
28
- }
29
- const cfg = raw;
30
- if (!Array.isArray(cfg.plugins)) {
31
- throw new Error("Codex install.json: .plugins must be an array");
32
- }
33
- for (const [i, plugin] of cfg.plugins.entries()) {
34
- if (!plugin || typeof plugin !== "object") {
35
- throw new Error(`Codex install.json: plugins[${i}] must be an object`);
36
- }
37
- const p = plugin;
38
- if (typeof p.name !== "string" || !PLUGIN_NAME_RE.test(p.name)) {
39
- throw new Error(`Codex install.json: plugins[${i}].name ${JSON.stringify(p.name)} does not match ${PLUGIN_NAME_RE}`);
40
- }
41
- if (p.description !== undefined && typeof p.description !== "string") {
42
- throw new Error(`Codex install.json: plugins[${i}].description must be a string`);
43
- }
44
- if (p.defaultOn !== undefined && typeof p.defaultOn !== "boolean") {
45
- throw new Error(`Codex install.json: plugins[${i}].defaultOn must be a boolean`);
46
- }
47
- if (p.marketplace !== undefined) {
48
- validateMarketplaceField(`Codex install.json: plugins[${i}]`, p.marketplace);
49
- }
50
- }
51
- }
52
25
  export function codexLocalPluginPath(plugin) {
53
26
  const sourcePath = typeof plugin.source === "object" && plugin.source?.source === "local"
54
27
  ? plugin.source.path
package/dist/guide.js CHANGED
@@ -93,8 +93,8 @@ Exit codes:
93
93
 
94
94
  ${h("## Step 4 — Reload session (REQUIRED when installed non-interactively)")}
95
95
 
96
- ${warn("⚠")} CLAUDE.md, .agents/skills/, .claude/plugins.json, Codex plugin
97
- config, and hook/plugin registrations are loaded at session startup. If you ran
96
+ ${warn("⚠")} CLAUDE.md, .agents/skills/, plugin enablement, and hook/plugin
97
+ registrations are loaded at session startup. If you ran
98
98
  \`npx -y auriga-cli install\` inside an existing Claude Code or Codex session
99
99
  (e.g., \`claude -p\` / \`claude -p --worktree\` / \`codex exec\`), the current session
100
100
  will NOT see the new harness.
@@ -106,11 +106,11 @@ Action:
106
106
 
107
107
  ${h("## Step 5 — Verify install")}
108
108
 
109
- Expected artifacts:
109
+ Expected artifacts/checks:
110
110
  - CLAUDE.md (workflow manifesto)
111
111
  - AGENTS.md -> CLAUDE.md (symlink)
112
112
  - .agents/skills/<name>/ (one per installed skill)
113
- - .claude/plugins.json
113
+ - claude plugins list (shows Claude plugins, if Claude plugins selected)
114
114
  - ~/.codex/config.toml (Codex plugin enablement, if Codex plugins selected)
115
115
  - .claude/settings.json (updated hook/plugin registrations, if selected)
116
116
  - .claude/auriga-notify/ (project notify config, if auriga-notify selected)
package/dist/hooks.js CHANGED
@@ -5,11 +5,11 @@ import path from "node:path";
5
5
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
6
6
  import { atomicWriteFile, exec, fetchExtraContentBinary, log, withEsc, } from "./utils.js";
7
7
  // --- Registry validation ---
8
- // hooks.json is fetched at runtime from raw.githubusercontent.com, so any
9
- // downstream code that interpolates registry values into shell commands or
10
- // filesystem paths is one force-push away from RCE / arbitrary-file-write
11
- // for every user running `npx auriga-cli`. Validate every untrusted value
12
- // once at load time, then trust it through the rest of the install flow.
8
+ // The root hooks registry is legacy-only, but when present it may still be
9
+ // loaded from runtime-fetched content. Any downstream code that interpolates
10
+ // registry values into shell commands or filesystem paths is one bad config
11
+ // away from RCE / arbitrary-file-write. Validate every untrusted value once at
12
+ // load time, then trust it through the rest of the install flow.
13
13
  const HOOK_NAME_RE = /^[a-z][a-z0-9-]*$/;
14
14
  // Matches a flat brew formula name (`jq`, `pngquant`) OR a fully
15
15
  // qualified tap-prefixed name (`vjeantet/tap/alerter`) — up to 2
@@ -424,12 +424,11 @@ function preflightDeps(hook) {
424
424
  * Lazy-fetch a hook's payload files into `packageRoot` so they can be
425
425
  * copied from there into the user's target directory.
426
426
  *
427
- * IMPORTANT: in production, `packageRoot` is the temp dir created by
428
- * `fetchContentRoot()` (utils.ts) not the npm package install dir.
429
- * Only `.claude/hooks/hooks.json` is preloaded by `CONTENT_FILES`; we
430
- * fetch each hook's individual files on demand here so users who pick
431
- * no hooks pay no network cost. In DEV mode `packageRoot` is the live
432
- * repo root, so the files are already on disk and we skip the fetch.
427
+ * IMPORTANT: this is retained for legacy root-hook installs. New hooks should
428
+ * ship inside plugins instead. In production, `packageRoot` is the temp dir
429
+ * created by `fetchContentRoot()` (utils.ts) not the npm package install
430
+ * dir. In DEV mode `packageRoot` is the live repo root, so the files are
431
+ * already on disk and we skip the fetch.
433
432
  *
434
433
  * The hook payload list is owned by `hook.files` in `hooks.json`, which
435
434
  * loadHooksConfig already validated for path-traversal safety, so each
@@ -540,6 +539,8 @@ function writeMergedSettings(resolved, hook, parsed) {
540
539
  }
541
540
  export function loadHooksConfig(packageRoot) {
542
541
  const configPath = path.join(packageRoot, ".claude", "hooks", "hooks.json");
542
+ if (!fs.existsSync(configPath))
543
+ return { hooks: [] };
543
544
  const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
544
545
  if (!raw || !Array.isArray(raw.hooks)) {
545
546
  throw new Error(`${configPath} must have a "hooks" array at the top level`);
@@ -1,25 +1,23 @@
1
1
  // Shared shape + validators for cross-Agent marketplace references.
2
2
  // Used by:
3
- // - PluginDef.marketplace in src/utils.ts (Claude side, .claude/plugins.json)
4
- // - CodexInstallPlugin.marketplace in src/codex-plugin-config.ts
5
- // (Codex side, .agents/plugins/install.json)
3
+ // - extra_plugin_configs.json external plugin refs
4
+ // - Codex local marketplace materialization refs
6
5
  //
7
6
  // Both sides interpolate `<source>` into shell commands like
8
7
  // `codex plugin marketplace add https://github.com/<source>.git` and
9
8
  // `<plugin>@<name>` into TOML keys, so these regexes are the only thing
10
9
  // standing between a compromised metadata source and arbitrary command
11
10
  // execution. Tighten with care — a regression here is a shell-injection
12
- // vector. Both metadata files are fetched from raw GitHub at runtime.
11
+ // vector. The metadata files are fetched from raw GitHub at runtime.
13
12
  export const MARKETPLACE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
14
13
  // GitHub `owner/repo` shape: exactly one `/`, both segments kebab-case-ish
15
14
  // without leading punctuation. Tighter than the prior PLUGIN_SOURCE_RE
16
15
  // which permitted multi-slash / `..` / trailing `.git` patterns and would
17
16
  // compose into confusing git-layer errors like `https://github.com/a/../b.git`.
18
17
  export const MARKETPLACE_SOURCE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
19
- // Centralizes the marketplace field shape check so the Claude-side
20
- // validatePluginsConfig and the Codex-side validateCodexInstallConfig
21
- // stay in lockstep. `label` is interpolated into the thrown Error so
22
- // the caller's file context (e.g. `plugins.json: plugins[3]`) survives.
18
+ // Centralizes the marketplace field shape check so Claude and Codex plugin
19
+ // config paths stay in lockstep. `label` is interpolated into the thrown Error
20
+ // so the caller's file context survives.
23
21
  export function validateMarketplaceField(label, raw) {
24
22
  if (!raw || typeof raw !== "object") {
25
23
  throw new Error(`${label}.marketplace must be an object`);
package/dist/plugins.d.ts CHANGED
@@ -1,5 +1,41 @@
1
- import type { InstallOpts, PluginsConfig } from "./utils.js";
2
- export declare function validatePluginsConfig(raw: unknown): asserts raw is PluginsConfig;
1
+ import { type MarketplaceRef } from "./marketplace.js";
2
+ import type { InstallOpts } from "./utils.js";
3
+ export type PluginRuntime = "claude" | "codex";
4
+ export interface ClaudeMarketplacePlugin {
5
+ name: string;
6
+ description?: string;
7
+ source?: string;
8
+ }
9
+ export interface ClaudeMarketplace {
10
+ name: string;
11
+ plugins: ClaudeMarketplacePlugin[];
12
+ }
13
+ export interface ExtraPluginConfig {
14
+ name: string;
15
+ agents?: PluginRuntime[];
16
+ description?: string;
17
+ defaultOn?: boolean;
18
+ claude?: {
19
+ package?: string;
20
+ marketplace?: MarketplaceRef;
21
+ };
22
+ codex?: {
23
+ marketplace?: MarketplaceRef;
24
+ };
25
+ }
26
+ export interface ExtraPluginConfigs {
27
+ plugins: ExtraPluginConfig[];
28
+ }
29
+ export declare function validateExtraPluginConfigs(raw: unknown): asserts raw is ExtraPluginConfigs;
30
+ export declare function loadExtraPluginConfigs(packageRoot: string): ExtraPluginConfigs;
31
+ export declare function extraAppliesTo(extra: ExtraPluginConfig, runtime: PluginRuntime): boolean;
32
+ export declare function extraByNameForRuntime(extras: ExtraPluginConfigs, runtime: PluginRuntime): Map<string, ExtraPluginConfig>;
33
+ export declare function applyExtraPluginFields<T extends {
34
+ name: string;
35
+ description?: string;
36
+ defaultOn?: boolean;
37
+ }>(plugin: T, extra: ExtraPluginConfig | undefined): T;
38
+ export declare function loadClaudeMarketplace(packageRoot: string): ClaudeMarketplace | null;
3
39
  export declare function installPlugins(packageRoot: string, opts: InstallOpts): Promise<void>;
4
40
  /**
5
41
  * Uninstall a single plugin.
package/dist/plugins.js CHANGED
@@ -3,18 +3,19 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { checkbox, select } from "@inquirer/prompts";
5
5
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
6
- import { codexLocalPluginPath, codexManifestPath, validateCodexInstallConfig, validateCodexMarketplace, } from "./codex-plugin-config.js";
6
+ import { codexLocalPluginPath, codexManifestPath, validateCodexMarketplace, } from "./codex-plugin-config.js";
7
7
  import { validateMarketplaceField } from "./marketplace.js";
8
- import { atomicWriteFile, exec, execAsync, fetchExtraContent, log, withEsc } from "./utils.js";
8
+ import { atomicWriteFile, exec, execAsync, log, withEsc } from "./utils.js";
9
9
  // Plugin names and plugin-package names end up in `claude plugins ...`
10
- // shell commands via string interpolation. .claude/plugins.json is
11
- // fetched from raw GitHub at runtime, so every value must pass a
10
+ // shell commands via string interpolation. Marketplace and extra config
11
+ // files are fetched from raw GitHub at runtime, so every value must pass a
12
12
  // conservative whitelist before composing the command. Without this a
13
- // compromised plugins.json would execute arbitrary commands via shell
13
+ // compromised config would execute arbitrary commands via shell
14
14
  // metachar injection. Marketplace shape (name + source) lives in
15
15
  // `./marketplace.js` so Claude and Codex sides share one validator.
16
16
  const PLUGIN_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
17
17
  const PLUGIN_PACKAGE_RE = /^[A-Za-z0-9][A-Za-z0-9._@/-]{0,255}$/;
18
+ const LOCAL_MARKETPLACE_SOURCE = "Ben2pc/auriga-cli";
18
19
  const MIGRATED_WORKFLOW_SKILLS = [
19
20
  "incremental-impl",
20
21
  "test-designer",
@@ -24,30 +25,73 @@ const NOTIFY_PLUGIN_NAME = "auriga-notify";
24
25
  const WORKFLOW_SKILLS_PLUGIN_NAME = "auriga-workflow-skills";
25
26
  const LEGACY_NOTIFY_MARKER = "auriga:notify";
26
27
  const CODEX_PLUGIN_VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
27
- export function validatePluginsConfig(raw) {
28
+ function validateClaudeMarketplace(raw) {
28
29
  if (!raw || typeof raw !== "object") {
29
- throw new Error("plugins.json: root must be an object");
30
+ throw new Error("Claude marketplace.json: root must be an object");
30
31
  }
31
32
  const cfg = raw;
33
+ if (typeof cfg.name !== "string" || !PLUGIN_NAME_RE.test(cfg.name)) {
34
+ throw new Error("Claude marketplace.json: root must include a safe name");
35
+ }
32
36
  if (!Array.isArray(cfg.plugins)) {
33
- throw new Error("plugins.json: .plugins must be an array");
37
+ throw new Error("Claude marketplace.json: .plugins must be an array");
34
38
  }
35
39
  cfg.plugins.forEach((p, i) => {
36
40
  if (!p || typeof p !== "object") {
37
- throw new Error(`plugins.json: plugins[${i}] must be an object`);
41
+ throw new Error(`Claude marketplace.json: plugins[${i}] must be an object`);
38
42
  }
39
43
  const plugin = p;
40
44
  if (typeof plugin.name !== "string" || !PLUGIN_NAME_RE.test(plugin.name)) {
41
- throw new Error(`plugins.json: plugins[${i}].name ${JSON.stringify(plugin.name)} does not match ${PLUGIN_NAME_RE}`);
45
+ throw new Error(`Claude marketplace.json: plugins[${i}].name ${JSON.stringify(plugin.name)} does not match ${PLUGIN_NAME_RE}`);
42
46
  }
43
- if (typeof plugin.package !== "string" || !PLUGIN_PACKAGE_RE.test(plugin.package)) {
44
- throw new Error(`plugins.json: plugins[${i}].package ${JSON.stringify(plugin.package)} does not match ${PLUGIN_PACKAGE_RE}`);
47
+ if (plugin.description !== undefined && typeof plugin.description !== "string") {
48
+ throw new Error(`Claude marketplace.json: plugins[${i}].description must be a string`);
45
49
  }
46
- if (plugin.marketplace !== undefined) {
47
- validateMarketplaceField(`plugins.json: plugins[${i}]`, plugin.marketplace);
50
+ });
51
+ }
52
+ export function validateExtraPluginConfigs(raw) {
53
+ if (!raw || typeof raw !== "object") {
54
+ throw new Error("extra_plugin_configs.json: root must be an object");
55
+ }
56
+ const cfg = raw;
57
+ if (!Array.isArray(cfg.plugins)) {
58
+ throw new Error("extra_plugin_configs.json: .plugins must be an array");
59
+ }
60
+ cfg.plugins.forEach((p, i) => {
61
+ if (!p || typeof p !== "object") {
62
+ throw new Error(`extra_plugin_configs.json: plugins[${i}] must be an object`);
63
+ }
64
+ const plugin = p;
65
+ if (typeof plugin.name !== "string" || !PLUGIN_NAME_RE.test(plugin.name)) {
66
+ throw new Error(`extra_plugin_configs.json: plugins[${i}].name ${JSON.stringify(plugin.name)} does not match ${PLUGIN_NAME_RE}`);
67
+ }
68
+ if (plugin.agents !== undefined) {
69
+ if (!Array.isArray(plugin.agents) || !plugin.agents.every((agent) => agent === "claude" || agent === "codex")) {
70
+ throw new Error(`extra_plugin_configs.json: plugins[${i}].agents must contain only claude/codex`);
71
+ }
72
+ }
73
+ if (plugin.description !== undefined && typeof plugin.description !== "string") {
74
+ throw new Error(`extra_plugin_configs.json: plugins[${i}].description must be a string`);
48
75
  }
49
76
  if (plugin.defaultOn !== undefined && typeof plugin.defaultOn !== "boolean") {
50
- throw new Error(`plugins.json: plugins[${i}].defaultOn must be a boolean`);
77
+ throw new Error(`extra_plugin_configs.json: plugins[${i}].defaultOn must be a boolean`);
78
+ }
79
+ for (const runtime of ["claude", "codex"]) {
80
+ const runtimeCfg = plugin[runtime];
81
+ if (runtimeCfg === undefined)
82
+ continue;
83
+ if (!runtimeCfg || typeof runtimeCfg !== "object") {
84
+ throw new Error(`extra_plugin_configs.json: plugins[${i}].${runtime} must be an object`);
85
+ }
86
+ const runtimeRecord = runtimeCfg;
87
+ if (runtime === "claude" && runtimeRecord.package !== undefined) {
88
+ if (typeof runtimeRecord.package !== "string" || !PLUGIN_PACKAGE_RE.test(runtimeRecord.package)) {
89
+ throw new Error(`extra_plugin_configs.json: plugins[${i}].claude.package ${JSON.stringify(runtimeRecord.package)} does not match ${PLUGIN_PACKAGE_RE}`);
90
+ }
91
+ }
92
+ if (runtimeRecord.marketplace !== undefined) {
93
+ validateMarketplaceField(`extra_plugin_configs.json: plugins[${i}].${runtime}`, runtimeRecord.marketplace);
94
+ }
51
95
  }
52
96
  });
53
97
  }
@@ -100,6 +144,67 @@ function getInstalledMarketplaces() {
100
144
  return new Set();
101
145
  }
102
146
  }
147
+ export function loadExtraPluginConfigs(packageRoot) {
148
+ const configPath = path.join(packageRoot, "extra_plugin_configs.json");
149
+ if (!fs.existsSync(configPath))
150
+ return { plugins: [] };
151
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
152
+ validateExtraPluginConfigs(raw);
153
+ return raw;
154
+ }
155
+ export function extraAppliesTo(extra, runtime) {
156
+ return extra.agents === undefined || extra.agents.includes(runtime);
157
+ }
158
+ export function extraByNameForRuntime(extras, runtime) {
159
+ return new Map(extras.plugins
160
+ .filter((extra) => extraAppliesTo(extra, runtime))
161
+ .map((extra) => [extra.name, extra]));
162
+ }
163
+ export function applyExtraPluginFields(plugin, extra) {
164
+ if (!extra)
165
+ return plugin;
166
+ return {
167
+ ...plugin,
168
+ ...(extra.description !== undefined ? { description: extra.description } : {}),
169
+ ...(extra.defaultOn !== undefined ? { defaultOn: extra.defaultOn } : {}),
170
+ };
171
+ }
172
+ export function loadClaudeMarketplace(packageRoot) {
173
+ const marketplacePath = path.join(packageRoot, ".claude-plugin", "marketplace.json");
174
+ if (!fs.existsSync(marketplacePath))
175
+ return null;
176
+ const raw = JSON.parse(fs.readFileSync(marketplacePath, "utf-8"));
177
+ validateClaudeMarketplace(raw);
178
+ return raw;
179
+ }
180
+ function loadClaudePluginsConfig(packageRoot) {
181
+ const extras = loadExtraPluginConfigs(packageRoot);
182
+ const extraByName = extraByNameForRuntime(extras, "claude");
183
+ const marketplace = loadClaudeMarketplace(packageRoot);
184
+ const plugins = new Map();
185
+ if (marketplace) {
186
+ for (const plugin of marketplace.plugins) {
187
+ plugins.set(plugin.name, applyExtraPluginFields({
188
+ name: plugin.name,
189
+ package: `${plugin.name}@${marketplace.name}`,
190
+ description: plugin.description ?? plugin.name,
191
+ marketplace: { name: marketplace.name, source: LOCAL_MARKETPLACE_SOURCE },
192
+ }, extraByName.get(plugin.name)));
193
+ }
194
+ }
195
+ for (const extra of extras.plugins) {
196
+ if (!extraAppliesTo(extra, "claude") || !extra.claude?.package)
197
+ continue;
198
+ plugins.set(extra.name, {
199
+ name: extra.name,
200
+ package: extra.claude.package,
201
+ description: extra.description ?? extra.name,
202
+ ...(extra.defaultOn !== undefined ? { defaultOn: extra.defaultOn } : {}),
203
+ ...(extra.claude.marketplace ? { marketplace: extra.claude.marketplace } : {}),
204
+ });
205
+ }
206
+ return plugins.size > 0 ? { plugins: [...plugins.values()] } : null;
207
+ }
103
208
  function loadCodexMarketplace(packageRoot) {
104
209
  const marketplacePath = path.join(packageRoot, ".agents", "plugins", "marketplace.json");
105
210
  if (!fs.existsSync(marketplacePath))
@@ -109,12 +214,28 @@ function loadCodexMarketplace(packageRoot) {
109
214
  return raw;
110
215
  }
111
216
  function loadCodexInstallConfig(packageRoot) {
112
- const installPath = path.join(packageRoot, ".agents", "plugins", "install.json");
113
- if (!fs.existsSync(installPath))
114
- return null;
115
- const raw = JSON.parse(fs.readFileSync(installPath, "utf-8"));
116
- validateCodexInstallConfig(raw);
117
- return raw;
217
+ const extras = loadExtraPluginConfigs(packageRoot);
218
+ const extraByName = extraByNameForRuntime(extras, "codex");
219
+ const marketplace = loadCodexMarketplace(packageRoot);
220
+ const plugins = new Map();
221
+ if (marketplace) {
222
+ for (const plugin of marketplace.plugins) {
223
+ plugins.set(plugin.name, applyExtraPluginFields({
224
+ name: plugin.name,
225
+ }, extraByName.get(plugin.name)));
226
+ }
227
+ }
228
+ for (const extra of extras.plugins) {
229
+ if (!extraAppliesTo(extra, "codex") || !extra.codex?.marketplace)
230
+ continue;
231
+ plugins.set(extra.name, {
232
+ name: extra.name,
233
+ description: extra.description,
234
+ ...(extra.defaultOn !== undefined ? { defaultOn: extra.defaultOn } : {}),
235
+ marketplace: extra.codex.marketplace,
236
+ });
237
+ }
238
+ return plugins.size > 0 ? { plugins: [...plugins.values()] } : null;
118
239
  }
119
240
  function resolveCodexPluginSelection(all, selected) {
120
241
  if (!selected)
@@ -131,6 +252,17 @@ function resolveCodexPluginSelection(all, selected) {
131
252
  function codexHome() {
132
253
  return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
133
254
  }
255
+ function codexMarketplaceCacheRoot(marketplaceName) {
256
+ return path.join(codexHome(), ".tmp", "marketplaces", marketplaceName);
257
+ }
258
+ function resolveCodexMarketplaceContentRoot(packageRoot, marketplaceName) {
259
+ const cachedRoot = codexMarketplaceCacheRoot(marketplaceName);
260
+ if (fs.existsSync(path.join(cachedRoot, ".agents", "plugins", "marketplace.json"))) {
261
+ return cachedRoot;
262
+ }
263
+ throw new Error(`Codex marketplace ${marketplaceName} cache missing at ${cachedRoot}. ` +
264
+ "Run `codex plugin marketplace add/upgrade` successfully before materializing local plugins.");
265
+ }
134
266
  function shellQuote(value) {
135
267
  return `'${value.replace(/'/g, "'\\''")}'`;
136
268
  }
@@ -297,7 +429,7 @@ function codexMarketplaceAddCommand(packageRoot) {
297
429
  return "codex plugin marketplace add https://github.com/Ben2pc/auriga-cli.git";
298
430
  }
299
431
  function codexExternalMarketplaceAddCommand(source) {
300
- // `source` is validated by validateCodexInstallConfig against
432
+ // `source` is validated by validateExtraPluginConfigs against
301
433
  // MARKETPLACE_SOURCE_RE (alphanumerics + `._/-`) — no shell metachars
302
434
  // can reach this string. URL form deliberately mirrors
303
435
  // codexMarketplaceAddCommand's hardcoded production branch.
@@ -323,8 +455,10 @@ function escapeRegex(value) {
323
455
  function isCodexMarketplaceAlreadyAdded(error, marketplaceName) {
324
456
  const text = commandErrorText(error);
325
457
  const marketplacePattern = new RegExp(`marketplace ['"]?${escapeRegex(marketplaceName)}['"]? is already added`, "i");
326
- return marketplacePattern.test(text)
327
- || /already added from a different source/i.test(text);
458
+ return marketplacePattern.test(text);
459
+ }
460
+ function isCodexMarketplaceDifferentSource(error) {
461
+ return /already added from a different source/i.test(commandErrorText(error));
328
462
  }
329
463
  function pluginHasHooks(packageRoot, plugin) {
330
464
  const relativeManifestPath = codexManifestPath(plugin);
@@ -341,12 +475,12 @@ function resolveSelectedCodexMarketplacePlugins(localMarketplace, localSelected)
341
475
  return localSelected.map((p) => {
342
476
  const plugin = localMpByName.get(p.name);
343
477
  if (!plugin) {
344
- throw new Error(`Codex install.json: plugin ${p.name} is not present in marketplace.json`);
478
+ throw new Error(`Codex plugin ${p.name} is selected but not present in marketplace.json`);
345
479
  }
346
480
  return plugin;
347
481
  });
348
482
  }
349
- async function ensureCodexPluginManifests(packageRoot, plugins) {
483
+ function ensureCodexPluginManifests(packageRoot, plugins) {
350
484
  for (const plugin of plugins) {
351
485
  const manifestPath = codexManifestPath(plugin);
352
486
  if (!manifestPath) {
@@ -354,7 +488,7 @@ async function ensureCodexPluginManifests(packageRoot, plugins) {
354
488
  }
355
489
  if (fs.existsSync(path.join(packageRoot, manifestPath)))
356
490
  continue;
357
- await fetchExtraContent(packageRoot, manifestPath);
491
+ throw new Error(`Codex plugin ${plugin.name} manifest missing at ${manifestPath}`);
358
492
  }
359
493
  }
360
494
  function readCodexPluginVersion(packageRoot, plugin) {
@@ -489,7 +623,13 @@ async function addCodexMarketplaceWithRetry(marketplaceName, addCommand, opts, m
489
623
  return;
490
624
  }
491
625
  catch (e) {
492
- if (opts.interactive || isCodexMarketplaceAlreadyAdded(e, marketplaceName)) {
626
+ if (isCodexMarketplaceDifferentSource(e)) {
627
+ const msg = `Codex marketplace ${marketplaceName} is already added from a different source`;
628
+ log.error(`${msg}\n${commandErrorText(e)}`);
629
+ failures.push(msg);
630
+ return;
631
+ }
632
+ if (isCodexMarketplaceAlreadyAdded(e, marketplaceName)) {
493
633
  try {
494
634
  exec(codexMarketplaceUpgradeCommand(marketplaceName), marketplaceExecOpts);
495
635
  log.ok(`Codex marketplace ${marketplaceName} upgraded`);
@@ -510,20 +650,20 @@ async function addCodexMarketplaceWithRetry(marketplaceName, addCommand, opts, m
510
650
  }
511
651
  // Builds the `<name>@<marketplace>` config keys + decides whether
512
652
  // features.plugin_hooks needs to flip on. Local plugins resolve through
513
- // this repo's marketplace.json and require a manifest fetch + hooks
514
- // inspection; external plugins emit a key directly from install.json
653
+ // this repo's marketplace.json and require a manifest check + hooks
654
+ // inspection; external plugins emit a key directly from extra_plugin_configs.json
515
655
  // (Codex CLI fetches the upstream manifest itself). External plugins do
516
656
  // NOT flip plugin_hooks today — we don't have access to the upstream
517
657
  // manifest at install time. Acceptable while no external plugin ships
518
658
  // hooks; once one does, prefer fetching the manifest or adding an
519
- // explicit `requiresPluginHooks: true` field on the install.json entry.
520
- async function composeCodexPluginKeys(packageRoot, localMarketplace, selectedMarketplacePlugins, externalSelected) {
659
+ // explicit `requiresPluginHooks: true` field on the extra config entry.
660
+ async function composeCodexPluginKeys(pluginContentRoot, localMarketplace, selectedMarketplacePlugins, externalSelected) {
521
661
  const pluginKeys = [];
522
662
  let needsPluginHooks = false;
523
663
  if (localMarketplace) {
524
664
  for (const plugin of selectedMarketplacePlugins) {
525
665
  pluginKeys.push(`${plugin.name}@${localMarketplace.name}`);
526
- if (pluginHasHooks(packageRoot, plugin))
666
+ if (pluginHasHooks(pluginContentRoot, plugin))
527
667
  needsPluginHooks = true;
528
668
  }
529
669
  }
@@ -535,7 +675,7 @@ async function composeCodexPluginKeys(packageRoot, localMarketplace, selectedMar
535
675
  async function installCodexPlugins(packageRoot, opts) {
536
676
  const installConfig = loadCodexInstallConfig(packageRoot);
537
677
  if (!installConfig) {
538
- const msg = "No .agents/plugins/install.json found";
678
+ const msg = "No Codex plugins found in .agents/plugins/marketplace.json or extra_plugin_configs.json";
539
679
  if (!opts.interactive)
540
680
  throw new Error(msg);
541
681
  log.warn(msg);
@@ -555,11 +695,10 @@ async function installCodexPlugins(packageRoot, opts) {
555
695
  log.skip("No Codex plugins selected");
556
696
  return;
557
697
  }
558
- // Local plugins are described by this repo's .agents/plugins/marketplace.json
559
- // and need a manifest fetch + hooks-detection. External plugins point to a
560
- // different GitHub-hosted Codex marketplace and are resolved by Codex CLI
561
- // itself when the marketplace is added we only need to register the
562
- // marketplace and emit the right `<name>@<marketplace>` plugin key.
698
+ // Local plugins are described by this repo's .agents/plugins/marketplace.json.
699
+ // External plugins come from extra_plugin_configs.json and are resolved by
700
+ // Codex CLI itself when their marketplace is added we only need to
701
+ // register that marketplace and emit the right `<name>@<marketplace>` key.
563
702
  let localSelected = selected.filter((p) => p.marketplace === undefined);
564
703
  const externalSelected = selected.filter((p) => p.marketplace !== undefined);
565
704
  let localMarketplace = null;
@@ -599,14 +738,20 @@ async function installCodexPlugins(packageRoot, opts) {
599
738
  await addCodexMarketplaceWithRetry(mp.name, codexExternalMarketplaceAddCommand(mp.source), opts, marketplaceExecOpts, failures);
600
739
  }
601
740
  if (failures.length === 0) {
602
- const selectedMarketplacePlugins = localMarketplace
603
- ? resolveSelectedCodexMarketplacePlugins(localMarketplace, localSelected)
741
+ const localMarketplaceContentRoot = localMarketplace
742
+ ? resolveCodexMarketplaceContentRoot(packageRoot, localMarketplace.name)
743
+ : packageRoot;
744
+ const effectiveLocalMarketplace = localMarketplace
745
+ ? loadCodexMarketplace(localMarketplaceContentRoot) ?? localMarketplace
746
+ : null;
747
+ const selectedMarketplacePlugins = effectiveLocalMarketplace
748
+ ? resolveSelectedCodexMarketplacePlugins(effectiveLocalMarketplace, localSelected)
604
749
  : [];
605
- await ensureCodexPluginManifests(packageRoot, selectedMarketplacePlugins);
606
- if (localMarketplace) {
607
- materializeLocalCodexPluginCache(packageRoot, localMarketplace.name, selectedMarketplacePlugins);
750
+ ensureCodexPluginManifests(localMarketplaceContentRoot, selectedMarketplacePlugins);
751
+ if (effectiveLocalMarketplace) {
752
+ materializeLocalCodexPluginCache(localMarketplaceContentRoot, effectiveLocalMarketplace.name, selectedMarketplacePlugins);
608
753
  }
609
- const { pluginKeys, needsPluginHooks } = await composeCodexPluginKeys(packageRoot, localMarketplace, selectedMarketplacePlugins, externalSelected);
754
+ const { pluginKeys, needsPluginHooks } = await composeCodexPluginKeys(localMarketplaceContentRoot, effectiveLocalMarketplace, selectedMarketplacePlugins, externalSelected);
610
755
  enableCodexPluginConfig(path.join(codexHome(), "config.toml"), pluginKeys, needsPluginHooks);
611
756
  for (const plugin of [...localSelected, ...externalSelected]) {
612
757
  log.ok(`${plugin.name} enabled for Codex`);
@@ -659,21 +804,17 @@ export async function installPlugins(packageRoot, opts) {
659
804
  return;
660
805
  }
661
806
  const failures = [];
662
- const configPath = path.join(packageRoot, ".claude", "plugins.json");
663
- let config = null;
664
- if (!fs.existsSync(configPath)) {
665
- log.warn("No .claude/plugins.json found");
807
+ let config = loadClaudePluginsConfig(packageRoot);
808
+ if (!config) {
809
+ log.warn("No Claude plugins found in .claude-plugin/marketplace.json or extra_plugin_configs.json");
666
810
  if (agent === "both")
667
811
  failures.push("Claude Code plugins config missing");
668
812
  else
669
813
  return;
670
814
  }
671
815
  else {
672
- const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
673
- validatePluginsConfig(raw);
674
- config = raw;
675
816
  if (config.plugins.length === 0) {
676
- log.warn("No plugins defined in plugins.json");
817
+ log.warn("No Claude plugins defined");
677
818
  if (agent === "both")
678
819
  failures.push("Claude Code plugins config empty");
679
820
  else
package/dist/utils.js CHANGED
@@ -148,53 +148,9 @@ function resolveContentRef() {
148
148
  const CONTENT_FILES = [
149
149
  "CLAUDE.md",
150
150
  "skills-lock.json",
151
- ".claude/plugins.json",
151
+ ".claude-plugin/marketplace.json",
152
152
  ".agents/plugins/marketplace.json",
153
- ".agents/plugins/install.json",
154
- ".claude/hooks/hooks.json",
155
- "plugins/auriga-go/.claude-plugin/plugin.json",
156
- "plugins/auriga-go/.codex-plugin/plugin.json",
157
- "plugins/auriga-go/README.md",
158
- "plugins/auriga-go/skills/auriga-go/SKILL.md",
159
- "plugins/auriga-go/skills/goalify/SKILL.md",
160
- "plugins/auriga-git-guards/.claude-plugin/plugin.json",
161
- "plugins/auriga-git-guards/.codex-plugin/plugin.json",
162
- "plugins/auriga-git-guards/README.md",
163
- "plugins/auriga-git-guards/hooks/hooks.json",
164
- "plugins/auriga-git-guards/scripts/commit-reminder.mjs",
165
- "plugins/auriga-git-guards/scripts/pr-create-guard.mjs",
166
- "plugins/auriga-git-guards/scripts/pr-ready-guard.mjs",
167
- "plugins/auriga-git-guards/skills/git-workflow/SKILL.md",
168
- "plugins/auriga-workflow-skills/.claude-plugin/plugin.json",
169
- "plugins/auriga-workflow-skills/.codex-plugin/plugin.json",
170
- "plugins/auriga-workflow-skills/README.md",
171
- "plugins/auriga-workflow-skills/skills/incremental-impl/SKILL.md",
172
- "plugins/auriga-workflow-skills/skills/session-compound/SKILL.md",
173
- "plugins/auriga-workflow-skills/skills/session-compound/analyzers/claude-code.mjs",
174
- "plugins/auriga-workflow-skills/skills/session-compound/analyzers/codex.mjs",
175
- "plugins/auriga-workflow-skills/skills/session-compound/template.html",
176
- "plugins/auriga-workflow-skills/skills/test-designer/SKILL.md",
177
- "plugins/session-instructions-loader/.codex-plugin/plugin.json",
178
- "plugins/session-instructions-loader/README.md",
179
- "plugins/session-instructions-loader/hooks/hooks.json",
180
- "plugins/session-instructions-loader/scripts/session-start.mjs",
181
- "plugins/deep-review/.claude-plugin/plugin.json",
182
- "plugins/deep-review/.codex-plugin/plugin.json",
183
- "plugins/deep-review/README.md",
184
- "plugins/deep-review/skills/deep-review/SKILL.md",
185
- "plugins/deep-review/skills/deep-review/references/reviewers/code-quality.md",
186
- "plugins/deep-review/skills/deep-review/references/reviewers/correctness.md",
187
- "plugins/deep-review/skills/deep-review/references/reviewers/docs-sync.md",
188
- "plugins/deep-review/skills/deep-review/references/reviewers/performance.md",
189
- "plugins/deep-review/skills/deep-review/references/reviewers/robustness.md",
190
- "plugins/deep-review/skills/deep-review/references/reviewers/security.md",
191
- "plugins/deep-review/skills/deep-review/references/reviewers/skill-plugin-quality.md",
192
- "plugins/deep-review/skills/deep-review/references/reviewers/spec-conformance.md",
193
- "plugins/deep-review/skills/deep-review/references/reviewers/structure.md",
194
- "plugins/deep-review/skills/deep-review/references/reviewers/test-quality.md",
195
- "plugins/deep-review/skills/deep-review/references/reviewers/ux.md",
196
- "plugins/deep-review/skills/reviewer-creator/SKILL.md",
197
- "plugins/deep-review/skills/reviewer-creator/references/template.md",
153
+ "extra_plugin_configs.json",
198
154
  ];
199
155
  async function fetchFile(file) {
200
156
  const ref = resolveContentRef();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auriga-cli",
3
- "version": "1.20.3",
3
+ "version": "1.20.4",
4
4
  "description": "Interactive CLI to install Claude Code harness modules (Workflow, Skills, Recommended Skills, Plugins, Hooks)",
5
5
  "license": "MIT",
6
6
  "repository": {