auriga-cli 1.29.1 → 1.30.0
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 +2 -3
- package/README.zh-CN.md +2 -3
- package/dist/catalog.json +2 -6
- package/dist/plugins.d.ts +11 -15
- package/dist/plugins.js +102 -246
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,7 +113,6 @@ Installs selected skills via `npx skills add`, targeting both Claude Code and Co
|
|
|
113
113
|
| codex-agent | [Ben2pc/g-claude-code-plugins](https://github.com/Ben2pc/g-claude-code-plugins) | Delegate to Codex sessions for cross-model coverage |
|
|
114
114
|
| deprecation-and-migration | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | Sunset, replace, or migrate legacy code — deprecation discipline |
|
|
115
115
|
| design-taste-frontend | [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill) | Senior UI/UX engineer with metric-based design rules and strict component architecture |
|
|
116
|
-
| documentation-and-adrs | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | Record architectural decisions and the *why* — context for future engineers / agents |
|
|
117
116
|
| frontend-design | [anthropics/skills](https://github.com/anthropics/skills) | Distinctive, production-grade frontend UI generation that avoids generic AI aesthetics |
|
|
118
117
|
| make-interfaces-feel-better | [jakubkrehel/make-interfaces-feel-better](https://github.com/jakubkrehel/make-interfaces-feel-better) | Polish principles — animations, surfaces, typography, performance |
|
|
119
118
|
|
|
@@ -121,7 +120,7 @@ Supports both project and global installation scopes.
|
|
|
121
120
|
|
|
122
121
|
### Plugins
|
|
123
122
|
|
|
124
|
-
Installs selected plugins for Claude Code, Codex, or both. Claude Code uses `claude plugins install` and honors `--scope project|user`; Codex
|
|
123
|
+
Installs selected plugins for Claude Code, Codex, or both. Claude Code uses `claude plugins install` and honors `--scope project|user`; Codex registers the marketplace via `codex plugin marketplace add/upgrade` (the right one is picked by reading `~/.codex/config.toml`), then installs each selected plugin with the native `codex plugin add <plugin>@<marketplace>` command. The Codex path requires a Codex CLI new enough to expose `codex plugin add`; on older versions the Codex-side install aborts with an upgrade hint.
|
|
125
124
|
|
|
126
125
|
Examples:
|
|
127
126
|
|
|
@@ -137,7 +136,7 @@ npx -y auriga-cli install plugins --agent codex --plugin session-instructions-lo
|
|
|
137
136
|
| claude-md-management | Claude Code / Codex | Audit and improve AGENTS.md / CLAUDE.md |
|
|
138
137
|
| playground | Claude Code / Codex | Build interactive HTML playgrounds |
|
|
139
138
|
| codex | Claude Code | Codex cross-model collaboration |
|
|
140
|
-
| auriga-workflow | Claude Code / Codex | The auriga workflow plugin — workflow skills plus the git lifecycle hooks that enforce them. Skills: `incremental-impl`, `test-designer`, `spec-design`, `arch-design`, `code-simplify`, `session-compound`, `goalify` (plans an autonomous goal and dispatches it via the built-in `/goal` command), `deep-review` (multi-dimensional PR review orchestrator — parallel per-dimension reviewers synthesized into an actionable punch list), `reviewer-creator` (scaffolds project-level custom reviewers under `docs/rules/review/`),
|
|
139
|
+
| auriga-workflow | Claude Code / Codex | The auriga workflow plugin — workflow skills plus the git lifecycle hooks that enforce them. Skills: `incremental-impl`, `test-designer`, `spec-design`, `arch-design`, `code-simplify`, `session-compound`, `goalify` (plans an autonomous goal and dispatches it via the built-in `/goal` command), `deep-review` (multi-dimensional PR review orchestrator — parallel per-dimension reviewers synthesized into an actionable punch list), `reviewer-creator` (scaffolds project-level custom reviewers under `docs/rules/review/`), `git-workflow` (git lifecycle skill), and `documentation-and-adrs` (architecture decision records and project documentation discipline — ADRs archived under `docs/architecture/`). Hooks: `commit-reminder` (PostToolUse on file edits — `Edit` / `Write` / `MultiEdit` in Claude Code, `apply_patch` in Codex — nudges to commit at the next semantic boundary when uncommitted diff vs `HEAD` exceeds 200 lines or 8 files), `pr-create-guard` (PostToolUse on `gh pr create` → injects a PR-body snapshot for five-element self-verification and flags non-Conventional-Commits titles), `pr-ready-guard` (PreToolUse on `gh pr ready` and non-draft `gh pr create` → blocks on stray planning docs, unfinalized active specs under `docs/specs/`, or unpushed commits), and `pr-merge-guard` (PreToolUse on `gh pr merge` → blocks while the PR body's Acceptance criteria section still has unchecked checklist items). The two PostToolUse hooks reach full Claude Code / Codex parity; Codex currently fails open on `pr-ready-guard`'s PreToolUse `additionalContext` informational path (block path identical). Installed by default through the plugin path. |
|
|
141
140
|
| auriga-notify *(opt-in)* | Claude Code | macOS native notification plugin for Claude Code `Notification` events. Focus-aware sound-only mode, click-to-activate, per-project notification grouping, and migrated `config.json` / `icon.png` support. Not installed by `install --all`; install explicitly with `install plugins --plugin auriga-notify`. |
|
|
142
141
|
| session-instructions-loader | Codex | Codex-only SessionStart plugin that injects ancestor `AGENTS.md` files plus repo-configured extra instruction files. |
|
|
143
142
|
|
package/README.zh-CN.md
CHANGED
|
@@ -113,7 +113,6 @@ npx auriga-cli
|
|
|
113
113
|
| codex-agent | [Ben2pc/g-claude-code-plugins](https://github.com/Ben2pc/g-claude-code-plugins) | 委派给 Codex 会话,做跨模型覆盖 |
|
|
114
114
|
| deprecation-and-migration | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | 废弃与迁移流程 —— 安全地下线、替换或迁移遗留代码 |
|
|
115
115
|
| design-taste-frontend | [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill) | 高阶 UI/UX 工程师 —— 度量化设计规则与严格的组件架构约束 |
|
|
116
|
-
| documentation-and-adrs | [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | 记录架构决策与"为什么" —— 为后来的工程师和 Agent 沉淀上下文 |
|
|
117
116
|
| frontend-design | [anthropics/skills](https://github.com/anthropics/skills) | 生成有辨识度、production 级的前端界面,避开常见 AI 同质化美学 |
|
|
118
117
|
| make-interfaces-feel-better | [jakubkrehel/make-interfaces-feel-better](https://github.com/jakubkrehel/make-interfaces-feel-better) | 界面打磨原则 —— 动画、表面、排版、性能 |
|
|
119
118
|
|
|
@@ -121,7 +120,7 @@ npx auriga-cli
|
|
|
121
120
|
|
|
122
121
|
### Plugins
|
|
123
122
|
|
|
124
|
-
可以把选中的插件安装到 Claude Code、Codex 或两者都装。Claude Code 路径使用 `claude plugins install`,并遵守 `--scope project|user`;Codex 路径根据 `~/.codex/config.toml` 中是否已注册同名 marketplace 自动选择 `codex plugin marketplace add` 或 `upgrade
|
|
123
|
+
可以把选中的插件安装到 Claude Code、Codex 或两者都装。Claude Code 路径使用 `claude plugins install`,并遵守 `--scope project|user`;Codex 路径根据 `~/.codex/config.toml` 中是否已注册同名 marketplace 自动选择 `codex plugin marketplace add` 或 `upgrade` 注册 marketplace,再用原生的 `codex plugin add <plugin>@<marketplace>` 命令安装每个选中的插件。Codex 路径要求 Codex CLI 版本新到支持 `codex plugin add`;旧版本会中止 Codex 侧安装并提示升级。
|
|
125
124
|
|
|
126
125
|
示例:
|
|
127
126
|
|
|
@@ -137,7 +136,7 @@ npx -y auriga-cli install plugins --agent codex --plugin session-instructions-lo
|
|
|
137
136
|
| claude-md-management | Claude Code / Codex | 审计和改进 AGENTS.md / CLAUDE.md |
|
|
138
137
|
| playground | Claude Code / Codex | 构建交互式 HTML playground |
|
|
139
138
|
| codex | Claude Code | Codex 跨模型协作 |
|
|
140
|
-
| auriga-workflow | Claude Code / Codex | auriga 工作流插件 —— 工作流 skill 加上强制执行工作流的 git 生命周期 hook。Skills:`incremental-impl`、`test-designer`、`spec-design`、`arch-design`、`code-simplify`、`session-compound`、`goalify`(plan 出自驱 goal 并通过内置 `/goal` 命令分发执行)、`deep-review`(多维度 PR review 编排器——并行派发各维度 reviewer,汇总成可执行的 punch list)、`reviewer-creator`(在 `docs/rules/review/` 下生成项目级自定义 reviewer)、`git-workflow`(git 生命周期 skill
|
|
139
|
+
| auriga-workflow | Claude Code / Codex | auriga 工作流插件 —— 工作流 skill 加上强制执行工作流的 git 生命周期 hook。Skills:`incremental-impl`、`test-designer`、`spec-design`、`arch-design`、`code-simplify`、`session-compound`、`goalify`(plan 出自驱 goal 并通过内置 `/goal` 命令分发执行)、`deep-review`(多维度 PR review 编排器——并行派发各维度 reviewer,汇总成可执行的 punch list)、`reviewer-creator`(在 `docs/rules/review/` 下生成项目级自定义 reviewer)、`git-workflow`(git 生命周期 skill)、`documentation-and-adrs`(架构决策记录与项目文档规范,ADR 归档到 `docs/architecture/`)。Hooks:`commit-reminder`(文件编辑的 PostToolUse —— Claude Code 匹配 `Edit` / `Write` / `MultiEdit`,Codex 匹配 `apply_patch` —— 未提交 diff 对比 `HEAD` 超过 200 行或 8 个文件时,提醒在下一个语义边界 commit)、`pr-create-guard`(`gh pr create` 的 PostToolUse —— 注入 PR body 快照供五要素自检,并对不符合 Conventional Commits 的标题提示)、`pr-ready-guard`(`gh pr ready` 与非 draft `gh pr create` 的 PreToolUse —— 拦截游离规划文档、`docs/specs/` 内未结案的活跃 spec、未 push commits)、`pr-merge-guard`(`gh pr merge` 的 PreToolUse —— PR body 的验收标准章节仍有未勾选清单项时拦截合并)。两个 PostToolUse hook 在 Claude Code / Codex 上完全对齐;Codex 仅对 `pr-ready-guard` 的 PreToolUse `additionalContext` 信息路径 fail-open(block 路径两边一致)。默认通过插件路径安装。 |
|
|
141
140
|
| auriga-notify *(opt-in)* | Claude Code | Claude Code `Notification` 事件的 macOS 原生通知插件。支持焦点感知仅提示音、点击唤起终端、按项目分组通知,并迁移旧 `config.json` / `icon.png`。不随 `install --all` 默认安装,需要显式执行 `install plugins --plugin auriga-notify`。 |
|
|
142
141
|
| session-instructions-loader | Codex | Codex-only SessionStart 插件,注入上层目录的 `AGENTS.md` 和仓库配置的额外 instruction 文件。 |
|
|
143
142
|
|
package/dist/catalog.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-05-
|
|
2
|
+
"generatedAt": "2026-05-19T07:23:20.929Z",
|
|
3
3
|
"workflowSkills": [
|
|
4
4
|
{
|
|
5
5
|
"name": "planning-with-files",
|
|
@@ -39,10 +39,6 @@
|
|
|
39
39
|
"name": "design-taste-frontend",
|
|
40
40
|
"description": "Senior UI/UX Engineer. Architect digital interfaces overriding default LLM biases. Enforces metric-based rules, strict component architecture, CSS hardware acceleration, and balanced design engineering."
|
|
41
41
|
},
|
|
42
|
-
{
|
|
43
|
-
"name": "documentation-and-adrs",
|
|
44
|
-
"description": "Records decisions and documentation. Use when making architectural decisions, changing public APIs, shipping features, or when you need to record context that future engineers and agents will need to understand the codebase."
|
|
45
|
-
},
|
|
46
42
|
{
|
|
47
43
|
"name": "frontend-design",
|
|
48
44
|
"description": "Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics."
|
|
@@ -55,7 +51,7 @@
|
|
|
55
51
|
"plugins": [
|
|
56
52
|
{
|
|
57
53
|
"name": "auriga-workflow",
|
|
58
|
-
"description": "(Claude/Codex) The auriga workflow plugin: workflow skills — incremental-impl (implementation slicing), test-designer (independent test design), spec-design (requirement clarification), arch-design (architecture design), code-simplify (code simplification), session-compound (session compounding), goalify (autonomous /goal planning), deep-review (multi-dimensional PR review orchestrator), reviewer-creator (custom reviewer scaffolding), git-workflow (git lifecycle skill) — plus the git lifecycle hooks that enforce them: commit-reminder, pr-create-guard, pr-ready-guard, pr-merge-guard. Dual-Agent compatible (Claude Code + Codex).",
|
|
54
|
+
"description": "(Claude/Codex) The auriga workflow plugin: workflow skills — incremental-impl (implementation slicing), test-designer (independent test design), spec-design (requirement clarification), arch-design (architecture design), code-simplify (code simplification), session-compound (session compounding), goalify (autonomous /goal planning), deep-review (multi-dimensional PR review orchestrator), reviewer-creator (custom reviewer scaffolding), git-workflow (git lifecycle skill), documentation-and-adrs (architecture decision records and documentation discipline) — plus the git lifecycle hooks that enforce them: commit-reminder, pr-create-guard, pr-ready-guard, pr-merge-guard. Dual-Agent compatible (Claude Code + Codex).",
|
|
59
55
|
"agents": [
|
|
60
56
|
"claude",
|
|
61
57
|
"codex"
|
package/dist/plugins.d.ts
CHANGED
|
@@ -54,22 +54,18 @@ export declare function installPlugins(packageRoot: string, opts: InstallOpts):
|
|
|
54
54
|
* surfaces nuanced failure modes (marketplace gone, network) that
|
|
55
55
|
* the caller needs to see verbatim.
|
|
56
56
|
*
|
|
57
|
-
* Codex side:
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
57
|
+
* Codex side: shells out to `codex plugin remove <id>`, which deletes
|
|
58
|
+
* the plugin from Codex's local config and cache. We deliberately do
|
|
59
|
+
* NOT remove the marketplace itself — a single marketplace may host
|
|
60
|
+
* multiple plugins, and tearing it down because one plugin left would
|
|
61
|
+
* break the others. The user can `codex plugin marketplace remove`
|
|
62
|
+
* separately when they want. Errors are propagated like the Claude
|
|
63
|
+
* side; idempotency (removing an already-absent plugin) is the Codex
|
|
64
|
+
* CLI's responsibility.
|
|
65
65
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* `codex plugin marketplace remove` separately when they want.
|
|
70
|
-
*
|
|
71
|
-
* Validation happens before any I/O — a malformed id throws cleanly with
|
|
72
|
-
* no side effects, so retries are safe.
|
|
66
|
+
* `parsePluginId` validates the id shape — and rejects shell
|
|
67
|
+
* metacharacters in either segment — before any command runs, so a
|
|
68
|
+
* malformed id throws cleanly with no side effects and retries are safe.
|
|
73
69
|
*/
|
|
74
70
|
export declare function uninstallPlugin(id: string, agent: "claude" | "codex", opts: {
|
|
75
71
|
cwd: string;
|
package/dist/plugins.js
CHANGED
|
@@ -2,8 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { checkbox, select } from "@inquirer/prompts";
|
|
5
|
-
import { parse as parseToml
|
|
6
|
-
import {
|
|
5
|
+
import { parse as parseToml } from "smol-toml";
|
|
6
|
+
import { codexManifestPath, validateCodexMarketplace, } from "./codex-plugin-config.js";
|
|
7
7
|
import { validateMarketplaceField } from "./marketplace.js";
|
|
8
8
|
import { atomicWriteFile, exec, execAsync, log, withEsc } from "./utils.js";
|
|
9
9
|
// Plugin names and plugin-package names end up in `claude plugins ...`
|
|
@@ -24,7 +24,6 @@ const MIGRATED_WORKFLOW_SKILLS = [
|
|
|
24
24
|
const NOTIFY_PLUGIN_NAME = "auriga-notify";
|
|
25
25
|
const WORKFLOW_SKILLS_PLUGIN_NAME = "auriga-workflow";
|
|
26
26
|
const LEGACY_NOTIFY_MARKER = "auriga:notify";
|
|
27
|
-
const CODEX_PLUGIN_VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
|
|
28
27
|
function validateClaudeMarketplace(raw) {
|
|
29
28
|
if (!raw || typeof raw !== "object") {
|
|
30
29
|
throw new Error("Claude marketplace.json: root must be an object");
|
|
@@ -264,17 +263,6 @@ function resolveCodexPluginSelection(all, selected) {
|
|
|
264
263
|
function codexHome() {
|
|
265
264
|
return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
266
265
|
}
|
|
267
|
-
function codexMarketplaceCacheRoot(marketplaceName) {
|
|
268
|
-
return path.join(codexHome(), ".tmp", "marketplaces", marketplaceName);
|
|
269
|
-
}
|
|
270
|
-
function resolveCodexMarketplaceContentRoot(packageRoot, marketplaceName) {
|
|
271
|
-
const cachedRoot = codexMarketplaceCacheRoot(marketplaceName);
|
|
272
|
-
if (fs.existsSync(path.join(cachedRoot, ".agents", "plugins", "marketplace.json"))) {
|
|
273
|
-
return cachedRoot;
|
|
274
|
-
}
|
|
275
|
-
throw new Error(`Codex marketplace ${marketplaceName} cache missing at ${cachedRoot}. ` +
|
|
276
|
-
"Run `codex plugin marketplace add/upgrade` successfully before materializing local plugins.");
|
|
277
|
-
}
|
|
278
266
|
function shellQuote(value) {
|
|
279
267
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
280
268
|
}
|
|
@@ -462,6 +450,35 @@ function codexExternalMarketplaceAddCommand(source) {
|
|
|
462
450
|
function codexMarketplaceUpgradeCommand(marketplaceName) {
|
|
463
451
|
return `codex plugin marketplace upgrade ${shellQuote(marketplaceName)}`;
|
|
464
452
|
}
|
|
453
|
+
// Capability probe for the native `codex plugin add` command. Older Codex
|
|
454
|
+
// versions expose `codex plugin marketplace` but not `add`; probing the
|
|
455
|
+
// subcommand's `--help` is version-number-agnostic — it needs no knowledge
|
|
456
|
+
// of which Codex release introduced `add`, so it can't rot the way a
|
|
457
|
+
// hardcoded minimum-version compare would. `--help` prints and exits 0
|
|
458
|
+
// when the subcommand exists, and exits non-zero (throwing here) when it
|
|
459
|
+
// doesn't.
|
|
460
|
+
function codexSupportsPluginAdd() {
|
|
461
|
+
try {
|
|
462
|
+
exec("codex plugin add --help");
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// `--enable plugins` turns on the global Codex plugins feature; `--enable
|
|
470
|
+
// plugin_hooks` is appended only for plugins that ship hooks. We pass the
|
|
471
|
+
// feature flags explicitly rather than relying on `codex plugin add` to
|
|
472
|
+
// flip them — both flags are idempotent, so re-passing them across
|
|
473
|
+
// multiple `add` calls is harmless. The plugin key (`<name>@<marketplace>`)
|
|
474
|
+
// is validated upstream (PLUGIN_NAME_RE / MARKETPLACE_NAME_RE), so no
|
|
475
|
+
// shell metacharacter can reach this interpolated command.
|
|
476
|
+
function codexPluginAddCommand(pluginKey, hasHooks) {
|
|
477
|
+
const enable = hasHooks
|
|
478
|
+
? "--enable plugins --enable plugin_hooks"
|
|
479
|
+
: "--enable plugins";
|
|
480
|
+
return `codex plugin add ${pluginKey} ${enable}`;
|
|
481
|
+
}
|
|
465
482
|
function commandErrorText(error) {
|
|
466
483
|
if (!(error instanceof Error))
|
|
467
484
|
return String(error);
|
|
@@ -538,131 +555,6 @@ function ensureCodexPluginManifests(packageRoot, plugins) {
|
|
|
538
555
|
throw new Error(`Codex plugin ${plugin.name} manifest missing at ${manifestPath}`);
|
|
539
556
|
}
|
|
540
557
|
}
|
|
541
|
-
function readCodexPluginVersion(packageRoot, plugin) {
|
|
542
|
-
const manifestPath = codexManifestPath(plugin);
|
|
543
|
-
if (!manifestPath) {
|
|
544
|
-
throw new Error(`Codex marketplace.json: plugin ${plugin.name} must use a local source.path`);
|
|
545
|
-
}
|
|
546
|
-
const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, manifestPath), "utf-8"));
|
|
547
|
-
if (typeof manifest.version !== "string" || !CODEX_PLUGIN_VERSION_RE.test(manifest.version)) {
|
|
548
|
-
throw new Error(`Codex plugin ${plugin.name} manifest must include a safe string version`);
|
|
549
|
-
}
|
|
550
|
-
return manifest.version;
|
|
551
|
-
}
|
|
552
|
-
function materializeLocalCodexPluginCache(packageRoot, marketplaceName, plugins) {
|
|
553
|
-
const cacheRoot = path.join(codexHome(), "plugins", "cache");
|
|
554
|
-
for (const plugin of plugins) {
|
|
555
|
-
const sourcePath = codexLocalPluginPath(plugin);
|
|
556
|
-
if (!sourcePath) {
|
|
557
|
-
throw new Error(`Codex marketplace.json: plugin ${plugin.name} must use a local source.path`);
|
|
558
|
-
}
|
|
559
|
-
const version = readCodexPluginVersion(packageRoot, plugin);
|
|
560
|
-
const sourceDir = path.join(packageRoot, sourcePath);
|
|
561
|
-
const destDir = path.join(cacheRoot, marketplaceName, plugin.name, version);
|
|
562
|
-
const tmpDir = `${destDir}.tmp-${process.pid}-${Date.now()}`;
|
|
563
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
564
|
-
fs.mkdirSync(path.dirname(destDir), { recursive: true });
|
|
565
|
-
fs.cpSync(sourceDir, tmpDir, { recursive: true });
|
|
566
|
-
fs.rmSync(destDir, { recursive: true, force: true });
|
|
567
|
-
fs.renameSync(tmpDir, destDir);
|
|
568
|
-
if (!fs.existsSync(path.join(destDir, ".codex-plugin", "plugin.json"))) {
|
|
569
|
-
throw new Error(`Codex plugin ${plugin.name} cache materialization did not produce plugin.json`);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
function ensureTomlBoolean(content, section, key, value) {
|
|
574
|
-
const line = `${key} = ${value ? "true" : "false"}`;
|
|
575
|
-
const header = `[${section}]`;
|
|
576
|
-
const lines = content.length > 0 ? content.split(/\r?\n/) : [];
|
|
577
|
-
const start = lines.findIndex((l) => l.trim() === header);
|
|
578
|
-
if (start === -1) {
|
|
579
|
-
const prefix = content.trimEnd();
|
|
580
|
-
return `${prefix}${prefix ? "\n\n" : ""}${header}\n${line}\n`;
|
|
581
|
-
}
|
|
582
|
-
let end = lines.length;
|
|
583
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
584
|
-
if (/^\s*\[/.test(lines[i])) {
|
|
585
|
-
end = i;
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
const keyRe = new RegExp(`^\\s*${key}\\s*=`);
|
|
590
|
-
for (let i = start + 1; i < end; i += 1) {
|
|
591
|
-
if (keyRe.test(lines[i])) {
|
|
592
|
-
lines[i] = line;
|
|
593
|
-
return lines.join("\n");
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
lines.splice(end, 0, line);
|
|
597
|
-
return lines.join("\n");
|
|
598
|
-
}
|
|
599
|
-
function parseCodexConfigToml(content, configPath) {
|
|
600
|
-
if (content.trim().length === 0)
|
|
601
|
-
return {};
|
|
602
|
-
try {
|
|
603
|
-
return parseToml(content);
|
|
604
|
-
}
|
|
605
|
-
catch (e) {
|
|
606
|
-
throw new Error(`Codex config.toml is invalid TOML at ${configPath}: ${e.message}`);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
function isTomlTable(value) {
|
|
610
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
611
|
-
}
|
|
612
|
-
function getOrCreateTomlTable(parent, key, pathLabel) {
|
|
613
|
-
const existing = parent[key];
|
|
614
|
-
if (existing === undefined) {
|
|
615
|
-
const table = {};
|
|
616
|
-
parent[key] = table;
|
|
617
|
-
return table;
|
|
618
|
-
}
|
|
619
|
-
if (!isTomlTable(existing)) {
|
|
620
|
-
throw new Error(`Codex config.toml: ${pathLabel} must be a TOML table`);
|
|
621
|
-
}
|
|
622
|
-
return existing;
|
|
623
|
-
}
|
|
624
|
-
function buildCodexPluginConfigToml(originalContent, configPath, pluginKeys, needsPluginHooks) {
|
|
625
|
-
const parsed = parseCodexConfigToml(originalContent, configPath);
|
|
626
|
-
const features = getOrCreateTomlTable(parsed, "features", "features");
|
|
627
|
-
features.plugins = true;
|
|
628
|
-
if (needsPluginHooks) {
|
|
629
|
-
features.plugin_hooks = true;
|
|
630
|
-
}
|
|
631
|
-
const plugins = getOrCreateTomlTable(parsed, "plugins", "plugins");
|
|
632
|
-
for (const pluginKey of pluginKeys) {
|
|
633
|
-
const plugin = getOrCreateTomlTable(plugins, pluginKey, `plugins.${JSON.stringify(pluginKey)}`);
|
|
634
|
-
plugin.enabled = true;
|
|
635
|
-
}
|
|
636
|
-
return stringifyToml(parsed);
|
|
637
|
-
}
|
|
638
|
-
function tryMinimalCodexPluginConfigToml(originalContent, configPath, pluginKeys, needsPluginHooks) {
|
|
639
|
-
let content = originalContent;
|
|
640
|
-
content = ensureTomlBoolean(content, "features", "plugins", true);
|
|
641
|
-
if (needsPluginHooks) {
|
|
642
|
-
content = ensureTomlBoolean(content, "features", "plugin_hooks", true);
|
|
643
|
-
}
|
|
644
|
-
for (const pluginKey of pluginKeys) {
|
|
645
|
-
content = ensureTomlBoolean(content, `plugins."${pluginKey}"`, "enabled", true);
|
|
646
|
-
}
|
|
647
|
-
try {
|
|
648
|
-
parseToml(content);
|
|
649
|
-
return content;
|
|
650
|
-
}
|
|
651
|
-
catch {
|
|
652
|
-
// Existing configs may use legal TOML forms such as inline tables
|
|
653
|
-
// (`features = { plugins = false }`). In that case, a local section
|
|
654
|
-
// insertion would redefine the table, so fall back to structured output.
|
|
655
|
-
parseCodexConfigToml(originalContent, configPath);
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
function enableCodexPluginConfig(configPath, pluginKeys, needsPluginHooks) {
|
|
660
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
661
|
-
const originalContent = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf-8") : "";
|
|
662
|
-
const minimalContent = tryMinimalCodexPluginConfigToml(originalContent, configPath, pluginKeys, needsPluginHooks);
|
|
663
|
-
const content = minimalContent ?? buildCodexPluginConfigToml(originalContent, configPath, pluginKeys, needsPluginHooks);
|
|
664
|
-
atomicWriteFile(configPath, content.endsWith("\n") ? content : `${content}\n`);
|
|
665
|
-
}
|
|
666
558
|
async function addCodexMarketplaceWithRetry(marketplaceName, addCommand, expectedSource, opts, marketplaceExecOpts, failures) {
|
|
667
559
|
const registeredSource = readCodexMarketplaceSource(marketplaceName);
|
|
668
560
|
if (registeredSource !== null) {
|
|
@@ -706,29 +598,32 @@ async function addCodexMarketplaceWithRetry(marketplaceName, addCommand, expecte
|
|
|
706
598
|
failures.push(`codex marketplace ${marketplaceName}`);
|
|
707
599
|
}
|
|
708
600
|
}
|
|
709
|
-
// Builds the
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
//
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
// manifest
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
async function composeCodexPluginKeys(pluginContentRoot, localMarketplace, selectedMarketplacePlugins, externalSelected) {
|
|
719
|
-
const pluginKeys = [];
|
|
720
|
-
let needsPluginHooks = false;
|
|
601
|
+
// Builds the `codex plugin add` work list: one entry per selected plugin.
|
|
602
|
+
// Local plugins resolve their hooks flag from this repo's manifest;
|
|
603
|
+
// external plugins emit a key straight from extra_plugin_configs.json
|
|
604
|
+
// (Codex CLI fetches the upstream manifest itself) and never set
|
|
605
|
+
// `hasHooks` — we don't have their manifest at install time. Acceptable
|
|
606
|
+
// while no external plugin ships hooks; once one does, prefer fetching the
|
|
607
|
+
// manifest or adding an explicit flag to the extra config entry.
|
|
608
|
+
function composeCodexPluginAdds(pluginContentRoot, localMarketplace, selectedMarketplacePlugins, externalSelected) {
|
|
609
|
+
const adds = [];
|
|
721
610
|
if (localMarketplace) {
|
|
722
611
|
for (const plugin of selectedMarketplacePlugins) {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
612
|
+
adds.push({
|
|
613
|
+
key: `${plugin.name}@${localMarketplace.name}`,
|
|
614
|
+
name: plugin.name,
|
|
615
|
+
hasHooks: pluginHasHooks(pluginContentRoot, plugin),
|
|
616
|
+
});
|
|
726
617
|
}
|
|
727
618
|
}
|
|
728
619
|
for (const p of externalSelected) {
|
|
729
|
-
|
|
620
|
+
adds.push({
|
|
621
|
+
key: `${p.name}@${p.marketplace.name}`,
|
|
622
|
+
name: p.name,
|
|
623
|
+
hasHooks: false,
|
|
624
|
+
});
|
|
730
625
|
}
|
|
731
|
-
return
|
|
626
|
+
return adds;
|
|
732
627
|
}
|
|
733
628
|
async function installCodexPlugins(packageRoot, opts) {
|
|
734
629
|
const installConfig = loadCodexInstallConfig(packageRoot);
|
|
@@ -756,6 +651,20 @@ async function installCodexPlugins(packageRoot, opts) {
|
|
|
756
651
|
log.skip("No Codex plugins selected");
|
|
757
652
|
return;
|
|
758
653
|
}
|
|
654
|
+
// Version gate: native `codex plugin add` materializes the plugin cache
|
|
655
|
+
// and writes the enable config itself. Without it there is no supported
|
|
656
|
+
// install path — fail fast with an actionable upgrade hint rather than
|
|
657
|
+
// falling back to a hand-rolled cache/config mechanism. Under `--agent
|
|
658
|
+
// both` this throw is caught by installPlugins' aggregator, so the
|
|
659
|
+
// Claude-side install still completes (the Codex side is recorded as a
|
|
660
|
+
// failure).
|
|
661
|
+
if (!codexSupportsPluginAdd()) {
|
|
662
|
+
const msg = "Codex CLI does not support `codex plugin add` — upgrade the Codex CLI and retry";
|
|
663
|
+
if (!opts.interactive)
|
|
664
|
+
throw new Error(msg);
|
|
665
|
+
log.error(msg);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
759
668
|
// Local plugins are described by this repo's .agents/plugins/marketplace.json.
|
|
760
669
|
// External plugins come from extra_plugin_configs.json and are resolved by
|
|
761
670
|
// Codex CLI itself when their marketplace is added — we only need to
|
|
@@ -799,24 +708,26 @@ async function installCodexPlugins(packageRoot, opts) {
|
|
|
799
708
|
await addCodexMarketplaceWithRetry(mp.name, codexExternalMarketplaceAddCommand(mp.source), codexExternalMarketplaceSource(mp.source), opts, marketplaceExecOpts, failures);
|
|
800
709
|
}
|
|
801
710
|
if (failures.length === 0) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
? resolveSelectedCodexMarketplacePlugins(effectiveLocalMarketplace, localSelected)
|
|
711
|
+
// Hooks detection reads this repo's manifest directly: local plugins
|
|
712
|
+
// listed in .agents/plugins/marketplace.json are sourced from this
|
|
713
|
+
// repo, so packageRoot is the authoritative manifest location. The
|
|
714
|
+
// plugin payload itself is materialized by `codex plugin add` from the
|
|
715
|
+
// marketplace snapshot registered above — no manual cache copy.
|
|
716
|
+
const selectedMarketplacePlugins = localMarketplace
|
|
717
|
+
? resolveSelectedCodexMarketplacePlugins(localMarketplace, localSelected)
|
|
810
718
|
: [];
|
|
811
|
-
ensureCodexPluginManifests(
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
719
|
+
ensureCodexPluginManifests(packageRoot, selectedMarketplacePlugins);
|
|
720
|
+
const pluginAdds = composeCodexPluginAdds(packageRoot, localMarketplace, selectedMarketplacePlugins, externalSelected);
|
|
721
|
+
for (const entry of pluginAdds) {
|
|
722
|
+
try {
|
|
723
|
+
exec(codexPluginAddCommand(entry.key, entry.hasHooks), marketplaceExecOpts);
|
|
724
|
+
log.ok(`Codex plugin ${entry.key} added`);
|
|
725
|
+
runPostInstallMigration(entry.name, opts, ["codex"]);
|
|
726
|
+
}
|
|
727
|
+
catch (e) {
|
|
728
|
+
log.error(`Failed to add Codex plugin ${entry.key}\n${commandErrorText(e)}`);
|
|
729
|
+
failures.push(`codex plugin ${entry.key}`);
|
|
730
|
+
}
|
|
820
731
|
}
|
|
821
732
|
}
|
|
822
733
|
if (failures.length > 0 && !opts.interactive) {
|
|
@@ -1046,23 +957,6 @@ function parsePluginId(id) {
|
|
|
1046
957
|
}
|
|
1047
958
|
return { plugin: m[1], marketplace: m[2] };
|
|
1048
959
|
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Remove `[plugins."<id>"]` from a parsed Codex config TOML tree.
|
|
1051
|
-
* Returns true if anything was removed. Idempotent: missing key → false.
|
|
1052
|
-
*
|
|
1053
|
-
* Pure function operating on the parsed tree — no I/O. Lets the test
|
|
1054
|
-
* harness assert tree shape without touching disk + lets the I/O wrapper
|
|
1055
|
-
* skip the atomic write when nothing changed.
|
|
1056
|
-
*/
|
|
1057
|
-
function removeCodexPluginFromConfig(parsed, pluginId) {
|
|
1058
|
-
const plugins = parsed.plugins;
|
|
1059
|
-
if (!isTomlTable(plugins))
|
|
1060
|
-
return false;
|
|
1061
|
-
if (!(pluginId in plugins))
|
|
1062
|
-
return false;
|
|
1063
|
-
delete plugins[pluginId];
|
|
1064
|
-
return true;
|
|
1065
|
-
}
|
|
1066
960
|
/**
|
|
1067
961
|
* Uninstall a single plugin.
|
|
1068
962
|
*
|
|
@@ -1071,25 +965,21 @@ function removeCodexPluginFromConfig(parsed, pluginId) {
|
|
|
1071
965
|
* surfaces nuanced failure modes (marketplace gone, network) that
|
|
1072
966
|
* the caller needs to see verbatim.
|
|
1073
967
|
*
|
|
1074
|
-
* Codex side:
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1083
|
-
* Caveat: we deliberately do NOT remove the marketplace itself. A
|
|
1084
|
-
* single marketplace may host multiple plugins; tearing it down
|
|
1085
|
-
* because one plugin left would break others. The user can
|
|
1086
|
-
* `codex plugin marketplace remove` separately when they want.
|
|
968
|
+
* Codex side: shells out to `codex plugin remove <id>`, which deletes
|
|
969
|
+
* the plugin from Codex's local config and cache. We deliberately do
|
|
970
|
+
* NOT remove the marketplace itself — a single marketplace may host
|
|
971
|
+
* multiple plugins, and tearing it down because one plugin left would
|
|
972
|
+
* break the others. The user can `codex plugin marketplace remove`
|
|
973
|
+
* separately when they want. Errors are propagated like the Claude
|
|
974
|
+
* side; idempotency (removing an already-absent plugin) is the Codex
|
|
975
|
+
* CLI's responsibility.
|
|
1087
976
|
*
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
977
|
+
* `parsePluginId` validates the id shape — and rejects shell
|
|
978
|
+
* metacharacters in either segment — before any command runs, so a
|
|
979
|
+
* malformed id throws cleanly with no side effects and retries are safe.
|
|
1090
980
|
*/
|
|
1091
981
|
export async function uninstallPlugin(id, agent, opts) {
|
|
1092
|
-
|
|
982
|
+
parsePluginId(id);
|
|
1093
983
|
const emit = (line) => { opts.onLog?.(line); };
|
|
1094
984
|
if (agent === "claude") {
|
|
1095
985
|
// Note: scope is intentionally NOT specified. `claude plugins
|
|
@@ -1101,44 +991,10 @@ export async function uninstallPlugin(id, agent, opts) {
|
|
|
1101
991
|
emit(`uninstalled ${id} from Claude Code`);
|
|
1102
992
|
return;
|
|
1103
993
|
}
|
|
1104
|
-
// Codex path
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
// filesystem (cache dir removal also gets skipped) so a damaged
|
|
1111
|
-
// config doesn't end up half-uninstalled. The test "config.toml
|
|
1112
|
-
// damaged → throw before mutation" locks this in.
|
|
1113
|
-
const parsed = parseCodexConfigToml(content, configPath);
|
|
1114
|
-
const removed = removeCodexPluginFromConfig(parsed, id);
|
|
1115
|
-
if (removed) {
|
|
1116
|
-
const next = stringifyToml(parsed);
|
|
1117
|
-
atomicWriteFile(configPath, next.endsWith("\n") ? next : `${next}\n`);
|
|
1118
|
-
log.ok(`${id} disabled in Codex config.toml`);
|
|
1119
|
-
emit(`removed ${id} from Codex config.toml`);
|
|
1120
|
-
}
|
|
1121
|
-
else {
|
|
1122
|
-
log.skip(`${id} not present in Codex config.toml`);
|
|
1123
|
-
emit(`${id} not present in Codex config.toml`);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
else {
|
|
1127
|
-
log.skip(`Codex config.toml not present`);
|
|
1128
|
-
emit(`Codex config.toml not present`);
|
|
1129
|
-
}
|
|
1130
|
-
// Cache dir: ~/.codex/plugins/cache/<marketplace>/<plugin>/
|
|
1131
|
-
// PLUGIN_ID_RE constrains both segments to a safe charset, so the
|
|
1132
|
-
// path can't escape via injection. rmSync with recursive+force is
|
|
1133
|
-
// the standard rm-rf idiom; missing dir is a no-op.
|
|
1134
|
-
const cacheDir = path.join(home, "plugins", "cache", marketplace, plugin);
|
|
1135
|
-
if (fs.existsSync(cacheDir)) {
|
|
1136
|
-
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
1137
|
-
log.ok(`${id} cache directory removed`);
|
|
1138
|
-
emit(`removed Codex cache directory for ${id}`);
|
|
1139
|
-
}
|
|
1140
|
-
else {
|
|
1141
|
-
log.skip(`${id} cache directory not present`);
|
|
1142
|
-
emit(`Codex cache directory for ${id} not present`);
|
|
1143
|
-
}
|
|
994
|
+
// Codex path: `codex plugin remove` deletes the plugin from Codex's
|
|
995
|
+
// local config and cache. `id` was validated above, so it carries no
|
|
996
|
+
// shell metacharacter.
|
|
997
|
+
exec(`codex plugin remove ${id}`, { cwd: opts.cwd, inherit: true });
|
|
998
|
+
log.ok(`${id} removed from Codex`);
|
|
999
|
+
emit(`removed ${id} from Codex`);
|
|
1144
1000
|
}
|