skill-codex 0.3.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -15
- package/commands/codex-do.md +4 -3
- package/commands/codex-review.md +17 -6
- package/dist/bin/skill-codex.js +109 -17
- package/dist/bin/skill-codex.js.map +1 -1
- package/dist/index.js +439 -48
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/skills/codex-bridge/SKILL.md +217 -0
package/README.md
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/skill-codex)
|
|
4
4
|
[](https://www.npmjs.com/package/skill-codex)
|
|
5
5
|

|
|
6
|
+
<!-- After enabling the repo on codecov.io (see DISTRIBUTION.md), uncomment:
|
|
7
|
+
[](https://codecov.io/gh/Arystos/skill-codex) -->
|
|
8
|
+

|
|
6
9
|

|
|
7
10
|

|
|
8
11
|

|
|
@@ -12,6 +15,12 @@
|
|
|
12
15
|
|
|
13
16
|
A cross-platform [Claude Code](https://code.claude.com) skill that integrates [OpenAI Codex CLI](https://github.com/openai/codex) for code review, task delegation, and consultation. Uses your existing Codex subscription -- no API key required.
|
|
14
17
|
|
|
18
|
+
> **Two models rarely make the same mistake.** skill-codex lets Claude and Codex check each other's work -- Claude plans and builds fast, Codex reviews with fresh eyes -- from a single terminal, using the Codex subscription you already pay for. No API key, no second window, no copy-paste.
|
|
19
|
+
|
|
20
|
+
<!-- Demo GIF: record a ~20s clip of `/codex-review` catching a real bug, save it to docs/demo.gif, then uncomment the next line:
|
|
21
|
+

|
|
22
|
+
-->
|
|
23
|
+
|
|
15
24
|
## Why?
|
|
16
25
|
|
|
17
26
|
Claude Code and Codex CLI have different strengths. Claude excels at reasoning, architecture, and complex refactors. Codex is fast, thorough, and great at focused execution and review. **skill-codex** lets them work together from a single terminal -- no second window, no copy-paste, no context loss.
|
|
@@ -19,10 +28,30 @@ Claude Code and Codex CLI have different strengths. Claude excels at reasoning,
|
|
|
19
28
|
* **`/codex-review`** -- Have Codex review your current changes as a second reviewer
|
|
20
29
|
* **`/codex-do`** -- Delegate well-scoped implementation tasks to Codex
|
|
21
30
|
* **`/codex-consult`** -- Get a second opinion on architecture or design decisions
|
|
31
|
+
* **`codex-bridge` agent skill** -- Auto-triggers on implementation/review/consult requests so Claude reaches for Codex without an explicit slash command
|
|
22
32
|
* **Auto-review hook** -- Smart PostToolUse hook suggests review after significant changes
|
|
33
|
+
* **Live progress** -- Streams Codex's activity (running commands, file edits, elapsed time) as MCP progress so long runs never look frozen; also mirrored to a tail-able log file (under the OS temp dir, path printed at run start -- never pollutes your repo)
|
|
23
34
|
* **Subscription-first** -- Works with `codex login`, no `OPENAI_API_KEY` needed
|
|
24
35
|
* **Edge case handling** -- Retry logic, timeout, anti-recursion, lock files, pre-flight checks
|
|
25
36
|
|
|
37
|
+
### Why not just use Claude's own subagents?
|
|
38
|
+
|
|
39
|
+
Because a subagent is the **same model**. When Claude reviews Claude, you inherit the same blind spots -- it's grading its own homework. A *different* model family was trained differently, so its mistakes don't correlate with Claude's: it catches what Claude is confidently wrong about, and Claude catches what Codex gets wrong. That uncorrelated, cross-model check is the entire point -- and skill-codex keeps Claude as the final judge, never blindly forwarding Codex's verdict.
|
|
40
|
+
|
|
41
|
+
### How it compares
|
|
42
|
+
|
|
43
|
+
| | skill-codex | Most Codex MCP bridges |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| Codex **subscription** auth (no `OPENAI_API_KEY`) | ✅ | ⚠️ often require an API key |
|
|
46
|
+
| **Windows** verified in CI (not just "should work") | ✅ 9-way matrix (Windows/macOS/Linux × Node 18/20/22) | ❌ usually Linux-only CI |
|
|
47
|
+
| **Live progress** so long runs never look frozen | ✅ MCP progress + tail-able log | ⚠️ varies |
|
|
48
|
+
| **Slash commands** (`/codex-review`, `/codex-do`, `/codex-consult`) | ✅ | ⚠️ some |
|
|
49
|
+
| **Auto-review hook** (PostToolUse) | ✅ | ❌ |
|
|
50
|
+
| **Agent skill** (auto-triggers, no command needed) | ✅ | ❌ |
|
|
51
|
+
| **Guardrails**: retry, timeout, anti-recursion, lock files | ✅ | ⚠️ partial |
|
|
52
|
+
|
|
53
|
+
*A snapshot, not a leaderboard -- the Codex MCP space moves fast, so check each tool's current state. The point isn't "skill-codex wins everything"; it's that the operational details (subscription auth, real Windows support, never-frozen runs, guardrails) are where it focuses.*
|
|
54
|
+
|
|
26
55
|
## Prerequisites
|
|
27
56
|
|
|
28
57
|
* [Node.js](https://nodejs.org) >= 18
|
|
@@ -49,7 +78,8 @@ The setup command:
|
|
|
49
78
|
1. Registers the MCP server in your Claude Code config (`~/.claude.json`)
|
|
50
79
|
2. Installs slash commands globally (`~/.claude/commands/`)
|
|
51
80
|
3. Configures the auto-review PostToolUse hook
|
|
52
|
-
4.
|
|
81
|
+
4. Installs the `codex-bridge` agent skill (`~/.claude/skills/`)
|
|
82
|
+
5. Verifies everything works
|
|
53
83
|
|
|
54
84
|
> **Tip:** Add `.skill-codex.lock` to your `.gitignore`
|
|
55
85
|
|
|
@@ -58,46 +88,59 @@ The setup command:
|
|
|
58
88
|
```
|
|
59
89
|
You in Claude Code
|
|
60
90
|
|
|
|
61
|
-
|-- /codex-review --> MCP tool --> codex exec
|
|
62
|
-
|-- /codex-do "task" --> MCP tool --> codex exec --
|
|
63
|
-
+-- /codex-consult "q" --> MCP tool --> codex exec
|
|
91
|
+
|-- /codex-review --> MCP tool --> codex exec --sandbox read-only --> review findings
|
|
92
|
+
|-- /codex-do "task" --> MCP tool --> codex exec --sandbox workspace-write --> reviewed output
|
|
93
|
+
+-- /codex-consult "q" --> MCP tool --> codex exec --sandbox read-only --> synthesized opinion
|
|
64
94
|
```
|
|
65
95
|
|
|
66
96
|
The MCP server spawns `codex exec` as a subprocess, using your logged-in Codex session. Claude sees the output and critically evaluates it -- **Codex is treated as a peer, not an authority**.
|
|
67
97
|
|
|
98
|
+
### Advanced: sandbox, sessions, model & review
|
|
99
|
+
|
|
100
|
+
These `codex_exec` parameters are all optional -- omit them and Codex uses its defaults:
|
|
101
|
+
|
|
102
|
+
* **`sandbox`** -- the explicit Codex sandbox policy (`read-only`, `workspace-write`, or `danger-full-access`), overriding the `mode` default. Use `danger-full-access` only when you understand the risk.
|
|
103
|
+
* **`sessionId`** -- resume a previous Codex session for multi-round memory. Each response includes the session's thread id; pass it back so Codex retains context -- e.g. a follow-up review that checks whether *previously flagged* issues were fixed, instead of re-discovering them.
|
|
104
|
+
* **`model`** -- pick the Codex model (e.g. `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`). Route cheap tasks to a smaller model and escalate hard ones; omit to use your configured default.
|
|
105
|
+
* **`reasoningEffort`** -- how hard Codex thinks: `minimal` | `low` | `medium` | `high` | `xhigh`. Omit for the model's default.
|
|
106
|
+
* **`review`** -- run Codex's native diff-scoped reviewer (`codex exec review`) instead of a freeform prompt, optionally targeting a branch (`reviewBase`) or commit (`reviewCommit`). The prompt becomes optional focus instructions.
|
|
107
|
+
|
|
68
108
|
### Auto-review
|
|
69
109
|
|
|
70
110
|
After significant code changes (3+ files, 100+ lines, security-related paths), the PostToolUse hook suggests running `/codex-review`. Trivial changes (docs-only, < 5 lines, whitespace) are skipped to preserve your Codex quota.
|
|
71
111
|
|
|
72
|
-
###
|
|
112
|
+
### Agent skill (auto-trigger)
|
|
73
113
|
|
|
74
|
-
|
|
75
|
-
|
|
114
|
+
The slash commands are explicit, on-demand entry points. The `codex-bridge` agent skill is the implicit one: it installs to `~/.claude/skills/` and Claude loads it automatically when your request matches a delegation, review, or consult pattern (e.g. "implement X", "generate tests for", "review this diff", "second opinion on this approach"). It maps the same three workflows -- delegate, review, consult -- onto the `codex_exec` tool, so you get Codex collaboration without remembering a command. Trivial tasks (< 50 lines, faster done directly) are explicitly out of scope.
|
|
115
|
+
|
|
116
|
+
### Example Output
|
|
76
117
|
|
|
77
|
-
|
|
118
|
+
When Codex runs, the MCP tool returns a structured plain-text response:
|
|
78
119
|
|
|
79
|
-
|
|
120
|
+
```
|
|
121
|
+
[read-only │ D:\myproject │ 21538 tok in (15744 cached) → 129 out]
|
|
122
|
+
✔ exec: powershell -Command 'git diff --cached' (ok)
|
|
80
123
|
|
|
81
124
|
CRITICAL — src/auth/login.ts:42
|
|
82
125
|
Password comparison uses == instead of timing-safe comparison.
|
|
83
|
-
Claude's assessment: Agree. Use crypto.timingSafeEqual() instead.
|
|
84
126
|
|
|
85
127
|
MEDIUM — src/api/routes.ts:18
|
|
86
128
|
Missing rate limiting on login endpoint.
|
|
87
|
-
Claude's assessment: Agree. Add rate limiter middleware.
|
|
88
|
-
|
|
89
|
-
Shall I fix these issues?
|
|
90
129
|
```
|
|
91
130
|
|
|
131
|
+
The first line shows mode, working directory, and token usage. Activity lines show commands Codex executed (with status icons: ✔ ok, ✘ blocked/failed). The response content follows after a blank line.
|
|
132
|
+
|
|
92
133
|
## Configuration
|
|
93
134
|
|
|
94
135
|
Environment variables (all optional):
|
|
95
136
|
|
|
96
137
|
| Variable | Default | Description |
|
|
97
138
|
|----------|---------|-------------|
|
|
98
|
-
| `SKILL_CODEX_TIMEOUT_MS` | `
|
|
139
|
+
| `SKILL_CODEX_TIMEOUT_MS` | `300000` (5 min) | Subprocess timeout |
|
|
99
140
|
| `SKILL_CODEX_MAX_RETRIES` | `3` | Retry count for transient errors |
|
|
141
|
+
| `SKILL_CODEX_LOG` | `<os-temp>/skill-codex/<workspace>.log` | Absolute path override for the live, tail-able per-run log |
|
|
100
142
|
| `SKILL_CODEX_DEBUG` | -- | Enable debug logging to stderr |
|
|
143
|
+
| `SKILL_CODEX_WINDOWS_SANDBOX` | `unelevated` | Windows only — Codex `windows.sandbox` mode (`unelevated`/`elevated`) |
|
|
101
144
|
|
|
102
145
|
### Smart Filter Thresholds
|
|
103
146
|
|
|
@@ -146,6 +189,8 @@ skill-codex/
|
|
|
146
189
|
|-- hooks/
|
|
147
190
|
| |-- post-tool-use-review.sh # Auto-review hook (macOS/Linux)
|
|
148
191
|
| +-- post-tool-use-review.ps1 # Auto-review hook (Windows)
|
|
192
|
+
|-- skills/
|
|
193
|
+
| +-- codex-bridge/SKILL.md # Agent skill (auto-triggers delegation/review/consult)
|
|
149
194
|
|-- setup/ # npx skill-codex setup installer
|
|
150
195
|
|-- bin/ # CLI entry point
|
|
151
196
|
+-- __tests__/ # vitest test suite
|
|
@@ -165,6 +210,7 @@ cd skill-codex
|
|
|
165
210
|
npm install
|
|
166
211
|
npm run build
|
|
167
212
|
npm test
|
|
213
|
+
npm run test:coverage # enforces the same 80%+ gate as CI
|
|
168
214
|
```
|
|
169
215
|
|
|
170
216
|
## Troubleshooting
|
|
@@ -179,11 +225,14 @@ Restart Claude Code after running setup. The MCP server only loads on startup.
|
|
|
179
225
|
Increase the timeout: `export SKILL_CODEX_TIMEOUT_MS=1200000` (20 min). Large codebases can take longer.
|
|
180
226
|
|
|
181
227
|
**"Auth expired" but Codex works in another terminal**
|
|
182
|
-
The MCP server runs in its own process. Run `codex login` and restart Claude Code.
|
|
228
|
+
The MCP server runs in its own process. Run `codex login` and restart Claude Code. On Windows, the auth pre-check is skipped automatically (PowerShell profile errors can cause false negatives); auth is still verified when Codex actually runs.
|
|
183
229
|
|
|
184
230
|
**Lock file blocking runs**
|
|
185
231
|
If a previous run crashed, a stale `.skill-codex.lock` may remain. It auto-cleans after 15 minutes, or delete it manually.
|
|
186
232
|
|
|
233
|
+
**Windows: "windows sandbox failed: spawn setup refresh" / Codex commands all blocked**
|
|
234
|
+
Codex's default *elevated* Windows sandbox fails to spawn shells on many setups ([openai/codex#24098](https://github.com/openai/codex/issues/24098), [#24259](https://github.com/openai/codex/issues/24259)). skill-codex works around this by pinning `windows.sandbox=unelevated`, which spawns reliably. If your machine needs the elevated sandbox instead, set `SKILL_CODEX_WINDOWS_SANDBOX=elevated`.
|
|
235
|
+
|
|
187
236
|
## Inspired By
|
|
188
237
|
|
|
189
238
|
* [Dunqing/claude-codex-bridge](https://github.com/Dunqing/claude-codex-bridge) -- retry logic, anti-recursion, output parsing
|
package/commands/codex-do.md
CHANGED
|
@@ -10,7 +10,7 @@ You are delegating an implementation task to Codex. Follow these steps:
|
|
|
10
10
|
|
|
11
11
|
2. **Evaluate if the task is suitable for delegation**:
|
|
12
12
|
- **Good for Codex**: repetitive bulk changes, boilerplate generation, test writing for existing code, migration scripts, file format conversions, well-defined single-file edits
|
|
13
|
-
- **Keep for yourself**: architectural decisions, cross-module refactoring, tasks requiring deep conversation context, ambiguous requirements,
|
|
13
|
+
- **Keep for yourself**: architectural decisions, cross-module refactoring, tasks requiring deep conversation context, ambiguous requirements, and trivial edits you'd finish faster yourself than you can write a spec for
|
|
14
14
|
- If the task is poorly scoped, explain why and suggest how to break it down.
|
|
15
15
|
|
|
16
16
|
3. **Prepare a precise, self-contained prompt** for Codex. Include:
|
|
@@ -25,7 +25,8 @@ You are delegating an implementation task to Codex. Follow these steps:
|
|
|
25
25
|
- `requireGit`: true
|
|
26
26
|
|
|
27
27
|
5. **Review Codex's output critically**:
|
|
28
|
-
- Run `git
|
|
28
|
+
- Run `git status --short` **first** to see ALL changes, including **newly-created files** (lines starting with `??`). Codex in full-auto mode often creates new files, and `git diff` alone is blind to untracked files.
|
|
29
|
+
- Run `git diff` for modifications to tracked files, and **read any new untracked files directly** to review their contents.
|
|
29
30
|
- Check for introduced bugs, regressions, or style violations
|
|
30
31
|
- Verify it matches the requested changes
|
|
31
32
|
- Verify it doesn't modify files outside the requested scope
|
|
@@ -39,6 +40,6 @@ You are delegating an implementation task to Codex. Follow these steps:
|
|
|
39
40
|
## Important
|
|
40
41
|
|
|
41
42
|
- Codex runs in **full-auto mode** — it CAN modify files in the workspace
|
|
42
|
-
- Always `git diff` after Codex runs
|
|
43
|
+
- Always run `git status --short` **and** `git diff` after Codex runs — `git status` catches new files that `git diff` misses
|
|
43
44
|
- Codex is a **peer, not an authority** — review all output before accepting
|
|
44
45
|
- For complex multi-file tasks, consider doing it yourself instead
|
package/commands/codex-review.md
CHANGED
|
@@ -7,7 +7,7 @@ Review the current changes using Codex as a second reviewer.
|
|
|
7
7
|
You are invoking Codex for a second-opinion code review. Follow these steps:
|
|
8
8
|
|
|
9
9
|
1. **Determine what to review** based on `$ARGUMENTS`:
|
|
10
|
-
- If empty or "uncommitted":
|
|
10
|
+
- If empty or "uncommitted": first run `git status --short` to see ALL changes, including **new untracked files** (lines starting with `??`). Collect tracked modifications via `git diff` and staged changes via `git diff --cached`, **and** include the full contents of any new untracked files — read them directly, or use `git diff --no-index -- /dev/null <file>`. Plain `git diff` is blind to untracked files, so a review that skips this step will miss brand-new files entirely. If there are no changes at all, inform the user and stop.
|
|
11
11
|
- If it looks like a branch name: get changes via `git diff <branch>...HEAD`
|
|
12
12
|
- If it looks like a commit SHA: get changes via `git show <sha>`
|
|
13
13
|
|
|
@@ -20,16 +20,27 @@ You are invoking Codex for a second-opinion code review. Follow these steps:
|
|
|
20
20
|
- `mode`: "exec"
|
|
21
21
|
- `requireGit`: true
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**Option B (native Codex review):** Default to the Claude-assembled diff above, since the user prefers Claude-led review. You may instead call `codex_exec` with `review: true` to use `codex exec review`; add `reviewBase` for a branch or `reviewCommit` for a SHA. The prompt is optional focus instructions.
|
|
24
|
+
|
|
25
|
+
4. **Assign an overall verdict** from the findings (you are the final judge — weigh Codex's findings, don't just echo them):
|
|
26
|
+
- **BLOCKED** — one or more CRITICAL/HIGH issues that should be fixed before merge
|
|
27
|
+
- **WARNING** — only MEDIUM/LOW issues; safe to proceed with awareness
|
|
28
|
+
- **APPROVED** — no substantive issues
|
|
29
|
+
State it explicitly, e.g. `Verdict: BLOCKED — 2 CRITICAL, 1 MEDIUM`.
|
|
30
|
+
|
|
31
|
+
5. **Present findings** to the user:
|
|
24
32
|
- Group by severity (CRITICAL first)
|
|
25
|
-
- For each finding, add your own assessment: agree, disagree, or nuance
|
|
33
|
+
- For each finding, add your own assessment: agree, disagree (with reasoning), or add nuance
|
|
26
34
|
- Note anything Codex missed that you think is important
|
|
27
35
|
- Summarize actionable items at the end
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
6. **If BLOCKED or WARNING, offer a bounded fix loop** (only with the user's go-ahead for automated fixing):
|
|
38
|
+
- Fix the issues you agree are genuine. If you disagree with a finding, explain why instead of "fixing" it just to satisfy Codex.
|
|
39
|
+
- Re-run the review (call `codex_exec` again on the updated diff) so Codex confirms the fixes and checks for regressions.
|
|
40
|
+
- Repeat until the verdict is APPROVED or you have done **3 rounds**, then stop and summarize what remains. The hard cap keeps the loop from quietly burning your Codex quota.
|
|
30
41
|
|
|
31
42
|
## Important
|
|
32
43
|
|
|
33
44
|
- Codex runs in **read-only mode** — it cannot modify files
|
|
34
|
-
- Codex is a **peer, not an authority** — evaluate each finding critically
|
|
35
|
-
-
|
|
45
|
+
- Codex is a **peer, not an authority** — evaluate each finding critically and keep the final call yourself
|
|
46
|
+
- **Fail soft:** if `codex_exec` errors (Codex not installed, auth expired, offline), say so and fall back to your own review — a missing Codex should degrade to Claude-only review, never block the user
|
package/dist/bin/skill-codex.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/skill-codex.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs8 from "fs";
|
|
5
|
+
import path10 from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
8
8
|
// setup/setup.ts
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import fs7 from "fs";
|
|
10
|
+
import path9 from "path";
|
|
11
11
|
|
|
12
12
|
// setup/install-mcp.ts
|
|
13
13
|
import fs2 from "fs";
|
|
@@ -44,6 +44,9 @@ function getGlobalMcpConfigPath() {
|
|
|
44
44
|
function getGlobalCommandsDir() {
|
|
45
45
|
return path2.join(getClaudeDir(), "commands");
|
|
46
46
|
}
|
|
47
|
+
function getGlobalSkillsDir() {
|
|
48
|
+
return path2.join(getClaudeDir(), "skills");
|
|
49
|
+
}
|
|
47
50
|
|
|
48
51
|
// src/config/package-root.ts
|
|
49
52
|
import fs from "fs";
|
|
@@ -243,8 +246,79 @@ function installHook() {
|
|
|
243
246
|
};
|
|
244
247
|
}
|
|
245
248
|
|
|
246
|
-
// setup/
|
|
249
|
+
// setup/install-skill.ts
|
|
247
250
|
import fs5 from "fs";
|
|
251
|
+
import path7 from "path";
|
|
252
|
+
var SKILL_NAME = "codex-bridge";
|
|
253
|
+
function getSkillSourceDir() {
|
|
254
|
+
return path7.join(getPackageRoot(), "skills", SKILL_NAME);
|
|
255
|
+
}
|
|
256
|
+
function copyDirRecursive(source, target) {
|
|
257
|
+
const copied = [];
|
|
258
|
+
if (!fs5.existsSync(target)) {
|
|
259
|
+
fs5.mkdirSync(target, { recursive: true });
|
|
260
|
+
}
|
|
261
|
+
const entries = fs5.readdirSync(source, { withFileTypes: true });
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
const sourcePath = path7.join(source, entry.name);
|
|
264
|
+
const targetPath = path7.join(target, entry.name);
|
|
265
|
+
if (entry.isDirectory()) {
|
|
266
|
+
copied.push(...copyDirRecursive(sourcePath, targetPath));
|
|
267
|
+
} else if (entry.isFile()) {
|
|
268
|
+
const sourceContent = fs5.readFileSync(sourcePath);
|
|
269
|
+
let shouldWrite = true;
|
|
270
|
+
if (fs5.existsSync(targetPath)) {
|
|
271
|
+
const existing = fs5.readFileSync(targetPath);
|
|
272
|
+
shouldWrite = !existing.equals(sourceContent);
|
|
273
|
+
}
|
|
274
|
+
if (shouldWrite) {
|
|
275
|
+
fs5.writeFileSync(targetPath, sourceContent);
|
|
276
|
+
copied.push(entry.name);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return copied;
|
|
281
|
+
}
|
|
282
|
+
function installSkill() {
|
|
283
|
+
const sourceDir = getSkillSourceDir();
|
|
284
|
+
const targetDir = path7.join(getGlobalSkillsDir(), SKILL_NAME);
|
|
285
|
+
if (!fs5.existsSync(sourceDir)) {
|
|
286
|
+
return {
|
|
287
|
+
installed: false,
|
|
288
|
+
targetDir,
|
|
289
|
+
copiedFiles: [],
|
|
290
|
+
message: `Skill source not found at ${sourceDir}`
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const skillFile = path7.join(sourceDir, "SKILL.md");
|
|
294
|
+
if (!fs5.existsSync(skillFile)) {
|
|
295
|
+
return {
|
|
296
|
+
installed: false,
|
|
297
|
+
targetDir,
|
|
298
|
+
copiedFiles: [],
|
|
299
|
+
message: `SKILL.md missing in ${sourceDir}`
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const copied = copyDirRecursive(sourceDir, targetDir);
|
|
303
|
+
return {
|
|
304
|
+
installed: true,
|
|
305
|
+
targetDir,
|
|
306
|
+
copiedFiles: copied,
|
|
307
|
+
message: copied.length > 0 ? `Installed skill '${SKILL_NAME}' to ${targetDir} (${copied.length} file(s))` : `Skill '${SKILL_NAME}' already up to date`
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function uninstallSkill() {
|
|
311
|
+
const targetDir = path7.join(getGlobalSkillsDir(), SKILL_NAME);
|
|
312
|
+
if (!fs5.existsSync(targetDir)) {
|
|
313
|
+
return { removed: false, targetDir };
|
|
314
|
+
}
|
|
315
|
+
fs5.rmSync(targetDir, { recursive: true, force: true });
|
|
316
|
+
return { removed: true, targetDir };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// setup/verify.ts
|
|
320
|
+
import fs6 from "fs";
|
|
321
|
+
import path8 from "path";
|
|
248
322
|
import which from "which";
|
|
249
323
|
async function runVerification() {
|
|
250
324
|
const results = [];
|
|
@@ -264,9 +338,9 @@ async function runVerification() {
|
|
|
264
338
|
}
|
|
265
339
|
const mcpPath = getGlobalMcpConfigPath();
|
|
266
340
|
let mcpRegistered = false;
|
|
267
|
-
if (
|
|
341
|
+
if (fs6.existsSync(mcpPath)) {
|
|
268
342
|
try {
|
|
269
|
-
const raw =
|
|
343
|
+
const raw = fs6.readFileSync(mcpPath, "utf-8");
|
|
270
344
|
const config = JSON.parse(raw);
|
|
271
345
|
mcpRegistered = "skill-codex" in (config.mcpServers ?? {});
|
|
272
346
|
} catch {
|
|
@@ -280,13 +354,20 @@ async function runVerification() {
|
|
|
280
354
|
const commandsDir = getGlobalCommandsDir();
|
|
281
355
|
const expectedCommands = ["codex-review.md", "codex-do.md", "codex-consult.md"];
|
|
282
356
|
const missingCommands = expectedCommands.filter(
|
|
283
|
-
(cmd) => !
|
|
357
|
+
(cmd) => !fs6.existsSync(`${commandsDir}/${cmd}`)
|
|
284
358
|
);
|
|
285
359
|
results.push({
|
|
286
360
|
name: "Slash commands installed",
|
|
287
361
|
pass: missingCommands.length === 0,
|
|
288
362
|
detail: missingCommands.length === 0 ? `All 3 commands in ${commandsDir}` : `Missing: ${missingCommands.join(", ")}`
|
|
289
363
|
});
|
|
364
|
+
const skillFile = path8.join(getGlobalSkillsDir(), "codex-bridge", "SKILL.md");
|
|
365
|
+
const skillInstalled = fs6.existsSync(skillFile);
|
|
366
|
+
results.push({
|
|
367
|
+
name: "Agent skill installed",
|
|
368
|
+
pass: skillInstalled,
|
|
369
|
+
detail: skillInstalled ? skillFile : `Not found at ${skillFile}`
|
|
370
|
+
});
|
|
290
371
|
return {
|
|
291
372
|
results,
|
|
292
373
|
allPassed: results.every((r) => r.pass)
|
|
@@ -309,6 +390,9 @@ async function runSetup(options = {}) {
|
|
|
309
390
|
log(" ", "Registering auto-review hook...");
|
|
310
391
|
const hookResult = installHook();
|
|
311
392
|
log(hookResult.installed ? "[ok]" : "[--]", hookResult.message);
|
|
393
|
+
log(" ", "Installing agent skill...");
|
|
394
|
+
const skillResult = installSkill();
|
|
395
|
+
log(skillResult.installed ? "[ok]" : "[--]", skillResult.message);
|
|
312
396
|
log("\n ", "Verifying installation...\n");
|
|
313
397
|
const verification = await runVerification();
|
|
314
398
|
for (const check of verification.results) {
|
|
@@ -323,6 +407,8 @@ async function runSetup(options = {}) {
|
|
|
323
407
|
log(" ", " /codex-do - Delegate a task to Codex");
|
|
324
408
|
log(" ", " /codex-consult - Get a second opinion from Codex");
|
|
325
409
|
log("", "");
|
|
410
|
+
log(" ", "Agent skill installed: codex-bridge (auto-triggers on implementation/review requests)");
|
|
411
|
+
log("", "");
|
|
326
412
|
log(" ", "Tip: Add .skill-codex.lock to your .gitignore");
|
|
327
413
|
} else {
|
|
328
414
|
log("[!!]", "Setup completed with warnings. Fix the issues above and run: npx skill-codex verify\n");
|
|
@@ -333,12 +419,12 @@ async function runUninstall() {
|
|
|
333
419
|
log(">>", "skill-codex uninstall\n");
|
|
334
420
|
const mcpConfigPath = getGlobalMcpConfigPath();
|
|
335
421
|
try {
|
|
336
|
-
const raw =
|
|
422
|
+
const raw = fs7.readFileSync(mcpConfigPath, "utf-8");
|
|
337
423
|
const config = JSON.parse(raw);
|
|
338
424
|
const mcpServers = config.mcpServers;
|
|
339
425
|
if (mcpServers && "skill-codex" in mcpServers) {
|
|
340
426
|
delete mcpServers["skill-codex"];
|
|
341
|
-
|
|
427
|
+
fs7.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
342
428
|
log("[ok]", "Removed MCP server");
|
|
343
429
|
} else {
|
|
344
430
|
log("[--]", "MCP server not found");
|
|
@@ -349,17 +435,23 @@ async function runUninstall() {
|
|
|
349
435
|
const commandsDir = getGlobalCommandsDir();
|
|
350
436
|
const commandFiles = ["codex-review.md", "codex-do.md", "codex-consult.md"];
|
|
351
437
|
for (const file of commandFiles) {
|
|
352
|
-
const filePath =
|
|
438
|
+
const filePath = path9.join(commandsDir, file);
|
|
353
439
|
try {
|
|
354
|
-
|
|
440
|
+
fs7.unlinkSync(filePath);
|
|
355
441
|
log("[ok]", `Removed ${file}`);
|
|
356
442
|
} catch {
|
|
357
443
|
log("[--]", `${file} not found`);
|
|
358
444
|
}
|
|
359
445
|
}
|
|
446
|
+
const skillUninstall = uninstallSkill();
|
|
447
|
+
if (skillUninstall.removed) {
|
|
448
|
+
log("[ok]", `Removed skill: ${skillUninstall.targetDir}`);
|
|
449
|
+
} else {
|
|
450
|
+
log("[--]", "Agent skill not found");
|
|
451
|
+
}
|
|
360
452
|
const settingsPath = getClaudeSettingsPath();
|
|
361
453
|
try {
|
|
362
|
-
const raw =
|
|
454
|
+
const raw = fs7.readFileSync(settingsPath, "utf-8");
|
|
363
455
|
const settings = JSON.parse(raw);
|
|
364
456
|
const hooks = settings.hooks;
|
|
365
457
|
if (hooks) {
|
|
@@ -372,7 +464,7 @@ async function runUninstall() {
|
|
|
372
464
|
if (hooks[eventType].length < before) removed = true;
|
|
373
465
|
}
|
|
374
466
|
if (removed) {
|
|
375
|
-
|
|
467
|
+
fs7.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
376
468
|
log("[ok]", "Removed PostToolUse hook");
|
|
377
469
|
} else {
|
|
378
470
|
log("[--]", "PostToolUse hook not found");
|
|
@@ -391,10 +483,10 @@ async function runUninstall() {
|
|
|
391
483
|
var args = process.argv.slice(2);
|
|
392
484
|
var command = args[0];
|
|
393
485
|
function getVersion() {
|
|
394
|
-
const __dirname =
|
|
395
|
-
const pkgPath =
|
|
486
|
+
const __dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
487
|
+
const pkgPath = path10.resolve(__dirname, "..", "package.json");
|
|
396
488
|
try {
|
|
397
|
-
const pkg = JSON.parse(
|
|
489
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
398
490
|
return pkg.version ?? "0.0.0";
|
|
399
491
|
} catch {
|
|
400
492
|
return "0.0.0";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../bin/skill-codex.ts","../../setup/setup.ts","../../setup/install-mcp.ts","../../src/config/paths.ts","../../src/util/platform.ts","../../src/config/package-root.ts","../../setup/install-commands.ts","../../setup/install-hook.ts","../../setup/verify.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { runSetup, runUninstall } from \"../setup/setup.js\";\nimport { runVerification } from \"../setup/verify.js\";\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nfunction getVersion(): string {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const pkgPath = path.resolve(__dirname, \"..\", \"package.json\");\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nasync function main(): Promise<void> {\n if (command === \"--help\" || command === \"-h\") {\n const version = getVersion();\n process.stdout.write(`skill-codex v${version}\n\nUsage:\n skill-codex setup Install MCP server, commands, and hook\n skill-codex setup --force Overwrite existing configuration\n skill-codex verify Check installation status\n skill-codex uninstall Show uninstall instructions\n skill-codex --help, -h Show this help message\n skill-codex --version, -v Show version\n`);\n process.exit(0);\n return;\n }\n\n if (command === \"--version\" || command === \"-v\") {\n process.stdout.write(`${getVersion()}\\n`);\n process.exit(0);\n return;\n }\n\n switch (command) {\n case \"setup\": {\n const force = args.includes(\"--force\");\n const success = await runSetup({ force });\n process.exit(success ? 0 : 1);\n break;\n }\n\n case \"verify\": {\n const { results, allPassed } = await runVerification();\n for (const check of results) {\n const icon = check.pass ? \"[ok]\" : \"[!!]\";\n process.stdout.write(`${icon} ${check.name}: ${check.detail}\\n`);\n }\n process.exit(allPassed ? 0 : 1);\n break;\n }\n\n case \"uninstall\": {\n await runUninstall();\n break;\n }\n\n default: {\n const version = getVersion();\n process.stdout.write(`skill-codex v${version}\n\nUsage:\n skill-codex setup Install MCP server, commands, and hook\n skill-codex setup --force Overwrite existing configuration\n skill-codex verify Check installation status\n skill-codex uninstall Show uninstall instructions\n skill-codex --help, -h Show this help message\n skill-codex --version, -v Show version\n`);\n process.exit(command ? 1 : 0);\n }\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { installMcp } from \"./install-mcp.js\";\nimport { installCommands } from \"./install-commands.js\";\nimport { installHook } from \"./install-hook.js\";\nimport { runVerification } from \"./verify.js\";\nimport {\n getGlobalMcpConfigPath,\n getGlobalCommandsDir,\n getClaudeSettingsPath,\n} from \"../src/config/paths.js\";\n\nfunction log(icon: string, message: string): void {\n process.stdout.write(`${icon} ${message}\\n`);\n}\n\nexport async function runSetup(options: { force?: boolean } = {}): Promise<boolean> {\n log(\">>\", \"skill-codex setup\\n\");\n\n // Step 1: Install MCP server\n log(\" \", \"Registering MCP server...\");\n const mcpResult = installMcp({ force: options.force });\n log(mcpResult.installed ? \"[ok]\" : \"[--]\", mcpResult.message);\n\n // Step 2: Install slash commands\n log(\" \", \"Installing slash commands...\");\n const cmdResult = installCommands({ global: true });\n log(cmdResult.installed.length > 0 ? \"[ok]\" : \"[--]\", cmdResult.message);\n\n // Step 3: Install hook\n log(\" \", \"Registering auto-review hook...\");\n const hookResult = installHook();\n log(hookResult.installed ? \"[ok]\" : \"[--]\", hookResult.message);\n\n // Step 4: Verify\n log(\"\\n \", \"Verifying installation...\\n\");\n const verification = await runVerification();\n\n for (const check of verification.results) {\n const icon = check.pass ? \"[ok]\" : \"[!!]\";\n log(` ${icon}`, `${check.name}: ${check.detail}`);\n }\n\n // Summary\n log(\"\", \"\");\n if (verification.allPassed) {\n log(\"[ok]\", \"Setup complete! Restart Claude Code to activate.\\n\");\n log(\" \", \"Available commands:\");\n log(\" \", \" /codex-review - Code review by Codex\");\n log(\" \", \" /codex-do - Delegate a task to Codex\");\n log(\" \", \" /codex-consult - Get a second opinion from Codex\");\n log(\"\", \"\");\n log(\" \", \"Tip: Add .skill-codex.lock to your .gitignore\");\n } else {\n log(\"[!!]\", \"Setup completed with warnings. Fix the issues above and run: npx skill-codex verify\\n\");\n }\n\n return verification.allPassed;\n}\n\nexport async function runUninstall(): Promise<void> {\n log(\">>\", \"skill-codex uninstall\\n\");\n\n // Step 1: Remove MCP server from ~/.claude.json\n const mcpConfigPath = getGlobalMcpConfigPath();\n try {\n const raw = fs.readFileSync(mcpConfigPath, \"utf-8\");\n const config = JSON.parse(raw) as Record<string, unknown>;\n const mcpServers = (config as { mcpServers?: Record<string, unknown> }).mcpServers;\n if (mcpServers && \"skill-codex\" in mcpServers) {\n delete mcpServers[\"skill-codex\"];\n fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n log(\"[ok]\", \"Removed MCP server\");\n } else {\n log(\"[--]\", \"MCP server not found\");\n }\n } catch {\n log(\"[--]\", \"MCP server not found (could not read ~/.claude.json)\");\n }\n\n // Step 2: Remove slash commands from ~/.claude/commands/\n const commandsDir = getGlobalCommandsDir();\n const commandFiles = [\"codex-review.md\", \"codex-do.md\", \"codex-consult.md\"];\n for (const file of commandFiles) {\n const filePath = path.join(commandsDir, file);\n try {\n fs.unlinkSync(filePath);\n log(\"[ok]\", `Removed ${file}`);\n } catch {\n log(\"[--]\", `${file} not found`);\n }\n }\n\n // Step 3: Remove PostToolUse hook from ~/.claude/settings.json\n const settingsPath = getClaudeSettingsPath();\n try {\n const raw = fs.readFileSync(settingsPath, \"utf-8\");\n const settings = JSON.parse(raw) as Record<string, unknown>;\n type HookEntry = { command?: string; hooks?: HookEntry[] };\n type HooksMap = Record<string, HookEntry[]>;\n const hooks = settings.hooks as HooksMap | undefined;\n if (hooks) {\n let removed = false;\n for (const eventType of Object.keys(hooks)) {\n const before = hooks[eventType].length;\n hooks[eventType] = hooks[eventType].filter(\n (entry) => !(entry.command ?? \"\").includes(\"skill-codex\")\n );\n if (hooks[eventType].length < before) removed = true;\n }\n if (removed) {\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\", \"utf-8\");\n log(\"[ok]\", \"Removed PostToolUse hook\");\n } else {\n log(\"[--]\", \"PostToolUse hook not found\");\n }\n } else {\n log(\"[--]\", \"PostToolUse hook not found\");\n }\n } catch {\n log(\"[--]\", \"Could not read ~/.claude/settings.json\");\n }\n\n log(\"\", \"\");\n log(\"[ok]\", \"Uninstall complete. Restart Claude Code to apply changes.\");\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getGlobalMcpConfigPath } from \"../src/config/paths.js\";\nimport { getPackageRoot } from \"../src/config/package-root.js\";\n\ninterface McpConfig {\n mcpServers?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nfunction getServerEntryPath(): string {\n return path.join(getPackageRoot(), \"dist\", \"index.js\");\n}\n\nexport function installMcp(options: { force?: boolean } = {}): {\n installed: boolean;\n configPath: string;\n message: string;\n} {\n const configPath = getGlobalMcpConfigPath();\n let config: McpConfig = {};\n\n // Read existing config\n if (fs.existsSync(configPath)) {\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n config = JSON.parse(raw) as McpConfig;\n } catch {\n return {\n installed: false,\n configPath,\n message: `Failed to parse ${configPath}. Back it up and try again.`,\n };\n }\n }\n\n // Check if already registered\n const servers = config.mcpServers ?? {};\n if (\"skill-codex\" in servers && !options.force) {\n return {\n installed: false,\n configPath,\n message: \"skill-codex MCP server already registered. Use --force to overwrite.\",\n };\n }\n\n const entryPath = getServerEntryPath();\n\n // Create new config with merged MCP server\n const updatedConfig: McpConfig = {\n ...config,\n mcpServers: {\n ...servers,\n \"skill-codex\": {\n command: \"node\",\n args: [entryPath],\n env: {},\n },\n },\n };\n\n // Ensure directory exists\n const dir = path.dirname(configPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Atomic write: write to temp file then rename to prevent corruption\n const tmpPath = configPath + \".tmp\";\n fs.writeFileSync(tmpPath, JSON.stringify(updatedConfig, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmpPath, configPath);\n\n return {\n installed: true,\n configPath,\n message: `MCP server registered in ${configPath}`,\n };\n}\n","import path from \"node:path\";\nimport { getClaudeDir, getHomeDir } from \"../util/platform.js\";\n\nexport function getClaudeSettingsPath(): string {\n return path.join(getClaudeDir(), \"settings.json\");\n}\n\nexport function getGlobalMcpConfigPath(): string {\n return path.join(getHomeDir(), \".claude.json\");\n}\n\nexport function getGlobalCommandsDir(): string {\n return path.join(getClaudeDir(), \"commands\");\n}\n\nexport function getProjectCommandsDir(cwd: string): string {\n return path.join(cwd, \".claude\", \"commands\");\n}\n\nexport function getProjectMcpConfigPath(cwd: string): string {\n return path.join(cwd, \".mcp.json\");\n}\n","import os from \"node:os\";\nimport path from \"node:path\";\n\nexport type Platform = \"win32\" | \"darwin\" | \"linux\";\n\nexport function getPlatform(): Platform {\n const p = os.platform();\n if (p === \"win32\" || p === \"darwin\" || p === \"linux\") return p;\n return \"linux\"; // default fallback for other unix-like\n}\n\nexport function isWindows(): boolean {\n return getPlatform() === \"win32\";\n}\n\nexport function normalizePath(p: string): string {\n return p.replace(/\\\\/g, \"/\");\n}\n\nexport function getHomeDir(): string {\n return os.homedir();\n}\n\nexport function getClaudeDir(): string {\n return path.join(getHomeDir(), \".claude\");\n}\n\nexport function getTempDir(): string {\n return os.tmpdir();\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Resolves the package root directory by walking up from the current file\n * until we find package.json. Works both in dev (src/) and after bundling (dist/).\n */\nexport function getPackageRoot(): string {\n const thisFile = new URL(import.meta.url).pathname;\n const normalized = process.platform === \"win32\" && thisFile.startsWith(\"/\")\n ? thisFile.slice(1)\n : thisFile;\n\n let dir = path.dirname(normalized);\n for (let i = 0; i < 10; i++) {\n if (fs.existsSync(path.join(dir, \"package.json\"))) {\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n // Fallback: assume 2 levels up from dist/bin/\n return path.resolve(path.dirname(normalized), \"..\", \"..\");\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getGlobalCommandsDir } from \"../src/config/paths.js\";\nimport { getPackageRoot } from \"../src/config/package-root.js\";\n\nconst COMMAND_FILES = [\"codex-review.md\", \"codex-do.md\", \"codex-consult.md\"];\n\nfunction getCommandsSourceDir(): string {\n return path.join(getPackageRoot(), \"commands\");\n}\n\nexport function installCommands(options: { global?: boolean; projectDir?: string } = {}): {\n installed: string[];\n skipped: string[];\n targetDir: string;\n message: string;\n} {\n const targetDir = options.global !== false\n ? getGlobalCommandsDir()\n : path.join(options.projectDir ?? process.cwd(), \".claude\", \"commands\");\n\n const sourceDir = getCommandsSourceDir();\n\n // Ensure target directory exists\n if (!fs.existsSync(targetDir)) {\n fs.mkdirSync(targetDir, { recursive: true });\n }\n\n const installed: string[] = [];\n const skipped: string[] = [];\n\n for (const file of COMMAND_FILES) {\n const sourcePath = path.join(sourceDir, file);\n const targetPath = path.join(targetDir, file);\n\n if (!fs.existsSync(sourcePath)) {\n skipped.push(file);\n continue;\n }\n\n const sourceContent = fs.readFileSync(sourcePath, \"utf-8\");\n\n // Skip if identical content already exists\n if (fs.existsSync(targetPath)) {\n const existingContent = fs.readFileSync(targetPath, \"utf-8\");\n if (existingContent === sourceContent) {\n skipped.push(file);\n continue;\n }\n }\n\n fs.writeFileSync(targetPath, sourceContent, \"utf-8\");\n installed.push(file);\n }\n\n return {\n installed,\n skipped,\n targetDir,\n message: installed.length > 0\n ? `Installed ${installed.length} command(s) to ${targetDir}`\n : \"All commands already up to date\",\n };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getClaudeSettingsPath } from \"../src/config/paths.js\";\nimport { isWindows } from \"../src/util/platform.js\";\nimport { getPackageRoot } from \"../src/config/package-root.js\";\n\ninterface HookCommand {\n type: \"command\";\n command: string;\n}\n\ninterface HookEntry {\n matcher: string;\n hooks: HookCommand[];\n}\n\ninterface ClaudeSettings {\n hooks?: {\n PostToolUse?: HookEntry[];\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\nfunction getHookScriptPath(): string {\n const hooksDir = path.join(getPackageRoot(), \"hooks\");\n return isWindows()\n ? path.join(hooksDir, \"post-tool-use-review.ps1\")\n : path.join(hooksDir, \"post-tool-use-review.sh\");\n}\n\nfunction buildHookCommand(): { command: string; scriptPath: string } | null {\n const scriptPath = getHookScriptPath();\n if (!fs.existsSync(scriptPath)) {\n return null;\n }\n const command = isWindows()\n ? `powershell -ExecutionPolicy Bypass -File \"${scriptPath}\"`\n : `bash \"${scriptPath}\"`;\n return { command, scriptPath };\n}\n\nexport function installHook(): {\n installed: boolean;\n settingsPath: string;\n message: string;\n} {\n const settingsPath = getClaudeSettingsPath();\n let settings: ClaudeSettings = {};\n\n if (fs.existsSync(settingsPath)) {\n try {\n const raw = fs.readFileSync(settingsPath, \"utf-8\");\n settings = JSON.parse(raw) as ClaudeSettings;\n } catch {\n return {\n installed: false,\n settingsPath,\n message: `Failed to parse ${settingsPath}. Back it up and fix manually.`,\n };\n }\n }\n\n const hookResult = buildHookCommand();\n if (!hookResult) {\n return {\n installed: false,\n settingsPath,\n message: \"Hook script not found. Run `npm run build` first.\",\n };\n }\n\n // Correct Claude Code hook format: matcher + hooks array\n const hookEntry: HookEntry = {\n matcher: \"Write|Edit|MultiEdit|NotebookEdit\",\n hooks: [\n {\n type: \"command\",\n command: hookResult.command,\n },\n ],\n };\n\n const existingHooks = settings.hooks?.PostToolUse ?? [];\n\n // Check if already registered (search in nested hooks array)\n const alreadyExists = existingHooks.some((h) =>\n h.hooks?.some((inner) => inner.command.includes(\"skill-codex\")),\n );\n if (alreadyExists) {\n return {\n installed: false,\n settingsPath,\n message: \"skill-codex hook already registered.\",\n };\n }\n\n const updatedSettings: ClaudeSettings = {\n ...settings,\n hooks: {\n ...settings.hooks,\n PostToolUse: [...existingHooks, hookEntry],\n },\n };\n\n const dir = path.dirname(settingsPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Atomic write: temp file then rename\n const tmpPath = settingsPath + \".tmp\";\n fs.writeFileSync(tmpPath, JSON.stringify(updatedSettings, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmpPath, settingsPath);\n\n // Ensure bash script is executable on unix\n if (!isWindows()) {\n const shPath = path.resolve(path.dirname(getHookScriptPath()), \"post-tool-use-review.sh\");\n try {\n fs.chmodSync(shPath, 0o755);\n } catch {\n // Best effort\n }\n }\n\n return {\n installed: true,\n settingsPath,\n message: `PostToolUse hook registered in ${settingsPath}`,\n };\n}\n","import fs from \"node:fs\";\nimport which from \"which\";\nimport { getGlobalMcpConfigPath, getGlobalCommandsDir } from \"../src/config/paths.js\";\n\ninterface CheckResult {\n readonly name: string;\n readonly pass: boolean;\n readonly detail: string;\n}\n\nexport async function runVerification(): Promise<{\n results: CheckResult[];\n allPassed: boolean;\n}> {\n const results: CheckResult[] = [];\n\n // 1. Node.js version\n const nodeVersion = process.versions.node;\n const nodeMajor = parseInt(nodeVersion.split(\".\")[0] ?? \"0\", 10);\n results.push({\n name: \"Node.js >= 18\",\n pass: nodeMajor >= 18,\n detail: `v${nodeVersion}`,\n });\n\n // 2. Codex CLI on PATH\n let codexPath: string | null = null;\n try {\n codexPath = await which(\"codex\");\n results.push({ name: \"Codex CLI found\", pass: true, detail: codexPath });\n } catch {\n results.push({ name: \"Codex CLI found\", pass: false, detail: \"Not found. Run: npm i -g @openai/codex\" });\n }\n\n // 3. MCP server registered\n const mcpPath = getGlobalMcpConfigPath();\n let mcpRegistered = false;\n if (fs.existsSync(mcpPath)) {\n try {\n const raw = fs.readFileSync(mcpPath, \"utf-8\");\n const config = JSON.parse(raw);\n mcpRegistered = \"skill-codex\" in (config.mcpServers ?? {});\n } catch {\n // Parse error\n }\n }\n results.push({\n name: \"MCP server registered\",\n pass: mcpRegistered,\n detail: mcpRegistered ? mcpPath : `Not found in ${mcpPath}`,\n });\n\n // 4. Slash commands installed\n const commandsDir = getGlobalCommandsDir();\n const expectedCommands = [\"codex-review.md\", \"codex-do.md\", \"codex-consult.md\"];\n const missingCommands = expectedCommands.filter(\n (cmd) => !fs.existsSync(`${commandsDir}/${cmd}`),\n );\n results.push({\n name: \"Slash commands installed\",\n pass: missingCommands.length === 0,\n detail: missingCommands.length === 0\n ? `All 3 commands in ${commandsDir}`\n : `Missing: ${missingCommands.join(\", \")}`,\n });\n\n return {\n results,\n allPassed: results.every((r) => r.pass),\n };\n}\n"],"mappings":"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","names":["fs","path","fs","path","fs","path","path","path","path","path","fs","fs","path","path","fs","fs","path","path","fs","command","fs","fs","fs","path","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../../bin/skill-codex.ts","../../setup/setup.ts","../../setup/install-mcp.ts","../../src/config/paths.ts","../../src/util/platform.ts","../../src/config/package-root.ts","../../setup/install-commands.ts","../../setup/install-hook.ts","../../setup/install-skill.ts","../../setup/verify.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { runSetup, runUninstall } from \"../setup/setup.js\";\nimport { runVerification } from \"../setup/verify.js\";\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nfunction getVersion(): string {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const pkgPath = path.resolve(__dirname, \"..\", \"package.json\");\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nasync function main(): Promise<void> {\n if (command === \"--help\" || command === \"-h\") {\n const version = getVersion();\n process.stdout.write(`skill-codex v${version}\n\nUsage:\n skill-codex setup Install MCP server, commands, and hook\n skill-codex setup --force Overwrite existing configuration\n skill-codex verify Check installation status\n skill-codex uninstall Show uninstall instructions\n skill-codex --help, -h Show this help message\n skill-codex --version, -v Show version\n`);\n process.exit(0);\n return;\n }\n\n if (command === \"--version\" || command === \"-v\") {\n process.stdout.write(`${getVersion()}\\n`);\n process.exit(0);\n return;\n }\n\n switch (command) {\n case \"setup\": {\n const force = args.includes(\"--force\");\n const success = await runSetup({ force });\n process.exit(success ? 0 : 1);\n break;\n }\n\n case \"verify\": {\n const { results, allPassed } = await runVerification();\n for (const check of results) {\n const icon = check.pass ? \"[ok]\" : \"[!!]\";\n process.stdout.write(`${icon} ${check.name}: ${check.detail}\\n`);\n }\n process.exit(allPassed ? 0 : 1);\n break;\n }\n\n case \"uninstall\": {\n await runUninstall();\n break;\n }\n\n default: {\n const version = getVersion();\n process.stdout.write(`skill-codex v${version}\n\nUsage:\n skill-codex setup Install MCP server, commands, and hook\n skill-codex setup --force Overwrite existing configuration\n skill-codex verify Check installation status\n skill-codex uninstall Show uninstall instructions\n skill-codex --help, -h Show this help message\n skill-codex --version, -v Show version\n`);\n process.exit(command ? 1 : 0);\n }\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { installMcp } from \"./install-mcp.js\";\nimport { installCommands } from \"./install-commands.js\";\nimport { installHook } from \"./install-hook.js\";\nimport { installSkill, uninstallSkill } from \"./install-skill.js\";\nimport { runVerification } from \"./verify.js\";\nimport {\n getGlobalMcpConfigPath,\n getGlobalCommandsDir,\n getClaudeSettingsPath,\n} from \"../src/config/paths.js\";\n\nfunction log(icon: string, message: string): void {\n process.stdout.write(`${icon} ${message}\\n`);\n}\n\nexport async function runSetup(options: { force?: boolean } = {}): Promise<boolean> {\n log(\">>\", \"skill-codex setup\\n\");\n\n // Step 1: Install MCP server\n log(\" \", \"Registering MCP server...\");\n const mcpResult = installMcp({ force: options.force });\n log(mcpResult.installed ? \"[ok]\" : \"[--]\", mcpResult.message);\n\n // Step 2: Install slash commands\n log(\" \", \"Installing slash commands...\");\n const cmdResult = installCommands({ global: true });\n log(cmdResult.installed.length > 0 ? \"[ok]\" : \"[--]\", cmdResult.message);\n\n // Step 3: Install hook\n log(\" \", \"Registering auto-review hook...\");\n const hookResult = installHook();\n log(hookResult.installed ? \"[ok]\" : \"[--]\", hookResult.message);\n\n // Step 4: Install Agent Skill (auto-trigger discovery)\n log(\" \", \"Installing agent skill...\");\n const skillResult = installSkill();\n log(skillResult.installed ? \"[ok]\" : \"[--]\", skillResult.message);\n\n // Step 5: Verify\n log(\"\\n \", \"Verifying installation...\\n\");\n const verification = await runVerification();\n\n for (const check of verification.results) {\n const icon = check.pass ? \"[ok]\" : \"[!!]\";\n log(` ${icon}`, `${check.name}: ${check.detail}`);\n }\n\n // Summary\n log(\"\", \"\");\n if (verification.allPassed) {\n log(\"[ok]\", \"Setup complete! Restart Claude Code to activate.\\n\");\n log(\" \", \"Available commands:\");\n log(\" \", \" /codex-review - Code review by Codex\");\n log(\" \", \" /codex-do - Delegate a task to Codex\");\n log(\" \", \" /codex-consult - Get a second opinion from Codex\");\n log(\"\", \"\");\n log(\" \", \"Agent skill installed: codex-bridge (auto-triggers on implementation/review requests)\");\n log(\"\", \"\");\n log(\" \", \"Tip: Add .skill-codex.lock to your .gitignore\");\n } else {\n log(\"[!!]\", \"Setup completed with warnings. Fix the issues above and run: npx skill-codex verify\\n\");\n }\n\n return verification.allPassed;\n}\n\nexport async function runUninstall(): Promise<void> {\n log(\">>\", \"skill-codex uninstall\\n\");\n\n // Step 1: Remove MCP server from ~/.claude.json\n const mcpConfigPath = getGlobalMcpConfigPath();\n try {\n const raw = fs.readFileSync(mcpConfigPath, \"utf-8\");\n const config = JSON.parse(raw) as Record<string, unknown>;\n const mcpServers = (config as { mcpServers?: Record<string, unknown> }).mcpServers;\n if (mcpServers && \"skill-codex\" in mcpServers) {\n delete mcpServers[\"skill-codex\"];\n fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n log(\"[ok]\", \"Removed MCP server\");\n } else {\n log(\"[--]\", \"MCP server not found\");\n }\n } catch {\n log(\"[--]\", \"MCP server not found (could not read ~/.claude.json)\");\n }\n\n // Step 2: Remove slash commands from ~/.claude/commands/\n const commandsDir = getGlobalCommandsDir();\n const commandFiles = [\"codex-review.md\", \"codex-do.md\", \"codex-consult.md\"];\n for (const file of commandFiles) {\n const filePath = path.join(commandsDir, file);\n try {\n fs.unlinkSync(filePath);\n log(\"[ok]\", `Removed ${file}`);\n } catch {\n log(\"[--]\", `${file} not found`);\n }\n }\n\n // Step 3: Remove agent skill from ~/.claude/skills/codex-bridge\n const skillUninstall = uninstallSkill();\n if (skillUninstall.removed) {\n log(\"[ok]\", `Removed skill: ${skillUninstall.targetDir}`);\n } else {\n log(\"[--]\", \"Agent skill not found\");\n }\n\n // Step 4: Remove PostToolUse hook from ~/.claude/settings.json\n const settingsPath = getClaudeSettingsPath();\n try {\n const raw = fs.readFileSync(settingsPath, \"utf-8\");\n const settings = JSON.parse(raw) as Record<string, unknown>;\n type HookEntry = { command?: string; hooks?: HookEntry[] };\n type HooksMap = Record<string, HookEntry[]>;\n const hooks = settings.hooks as HooksMap | undefined;\n if (hooks) {\n let removed = false;\n for (const eventType of Object.keys(hooks)) {\n const before = hooks[eventType].length;\n hooks[eventType] = hooks[eventType].filter(\n (entry) => !(entry.command ?? \"\").includes(\"skill-codex\")\n );\n if (hooks[eventType].length < before) removed = true;\n }\n if (removed) {\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\", \"utf-8\");\n log(\"[ok]\", \"Removed PostToolUse hook\");\n } else {\n log(\"[--]\", \"PostToolUse hook not found\");\n }\n } else {\n log(\"[--]\", \"PostToolUse hook not found\");\n }\n } catch {\n log(\"[--]\", \"Could not read ~/.claude/settings.json\");\n }\n\n log(\"\", \"\");\n log(\"[ok]\", \"Uninstall complete. Restart Claude Code to apply changes.\");\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getGlobalMcpConfigPath } from \"../src/config/paths.js\";\nimport { getPackageRoot } from \"../src/config/package-root.js\";\n\ninterface McpConfig {\n mcpServers?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nfunction getServerEntryPath(): string {\n return path.join(getPackageRoot(), \"dist\", \"index.js\");\n}\n\nexport function installMcp(options: { force?: boolean } = {}): {\n installed: boolean;\n configPath: string;\n message: string;\n} {\n const configPath = getGlobalMcpConfigPath();\n let config: McpConfig = {};\n\n // Read existing config\n if (fs.existsSync(configPath)) {\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n config = JSON.parse(raw) as McpConfig;\n } catch {\n return {\n installed: false,\n configPath,\n message: `Failed to parse ${configPath}. Back it up and try again.`,\n };\n }\n }\n\n // Check if already registered\n const servers = config.mcpServers ?? {};\n if (\"skill-codex\" in servers && !options.force) {\n return {\n installed: false,\n configPath,\n message: \"skill-codex MCP server already registered. Use --force to overwrite.\",\n };\n }\n\n const entryPath = getServerEntryPath();\n\n // Create new config with merged MCP server\n const updatedConfig: McpConfig = {\n ...config,\n mcpServers: {\n ...servers,\n \"skill-codex\": {\n command: \"node\",\n args: [entryPath],\n env: {},\n },\n },\n };\n\n // Ensure directory exists\n const dir = path.dirname(configPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Atomic write: write to temp file then rename to prevent corruption\n const tmpPath = configPath + \".tmp\";\n fs.writeFileSync(tmpPath, JSON.stringify(updatedConfig, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmpPath, configPath);\n\n return {\n installed: true,\n configPath,\n message: `MCP server registered in ${configPath}`,\n };\n}\n","import path from \"node:path\";\nimport { getClaudeDir, getHomeDir } from \"../util/platform.js\";\n\nexport function getClaudeSettingsPath(): string {\n return path.join(getClaudeDir(), \"settings.json\");\n}\n\nexport function getGlobalMcpConfigPath(): string {\n return path.join(getHomeDir(), \".claude.json\");\n}\n\nexport function getGlobalCommandsDir(): string {\n return path.join(getClaudeDir(), \"commands\");\n}\n\nexport function getGlobalSkillsDir(): string {\n return path.join(getClaudeDir(), \"skills\");\n}\n\nexport function getProjectCommandsDir(cwd: string): string {\n return path.join(cwd, \".claude\", \"commands\");\n}\n\nexport function getProjectMcpConfigPath(cwd: string): string {\n return path.join(cwd, \".mcp.json\");\n}\n","import os from \"node:os\";\nimport path from \"node:path\";\n\nexport type Platform = \"win32\" | \"darwin\" | \"linux\";\n\nexport function getPlatform(): Platform {\n const p = os.platform();\n if (p === \"win32\" || p === \"darwin\" || p === \"linux\") return p;\n return \"linux\"; // default fallback for other unix-like\n}\n\nexport function isWindows(): boolean {\n return getPlatform() === \"win32\";\n}\n\nexport function normalizePath(p: string): string {\n return p.replace(/\\\\/g, \"/\");\n}\n\nexport function getHomeDir(): string {\n return os.homedir();\n}\n\nexport function getClaudeDir(): string {\n return path.join(getHomeDir(), \".claude\");\n}\n\nexport function getTempDir(): string {\n return os.tmpdir();\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Resolves the package root directory by walking up from the current file\n * until we find package.json. Works both in dev (src/) and after bundling (dist/).\n */\nexport function getPackageRoot(): string {\n const thisFile = new URL(import.meta.url).pathname;\n const normalized = process.platform === \"win32\" && thisFile.startsWith(\"/\")\n ? thisFile.slice(1)\n : thisFile;\n\n let dir = path.dirname(normalized);\n for (let i = 0; i < 10; i++) {\n if (fs.existsSync(path.join(dir, \"package.json\"))) {\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n // Fallback: assume 2 levels up from dist/bin/\n return path.resolve(path.dirname(normalized), \"..\", \"..\");\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getGlobalCommandsDir } from \"../src/config/paths.js\";\nimport { getPackageRoot } from \"../src/config/package-root.js\";\n\nconst COMMAND_FILES = [\"codex-review.md\", \"codex-do.md\", \"codex-consult.md\"];\n\nfunction getCommandsSourceDir(): string {\n return path.join(getPackageRoot(), \"commands\");\n}\n\nexport function installCommands(options: { global?: boolean; projectDir?: string } = {}): {\n installed: string[];\n skipped: string[];\n targetDir: string;\n message: string;\n} {\n const targetDir = options.global !== false\n ? getGlobalCommandsDir()\n : path.join(options.projectDir ?? process.cwd(), \".claude\", \"commands\");\n\n const sourceDir = getCommandsSourceDir();\n\n // Ensure target directory exists\n if (!fs.existsSync(targetDir)) {\n fs.mkdirSync(targetDir, { recursive: true });\n }\n\n const installed: string[] = [];\n const skipped: string[] = [];\n\n for (const file of COMMAND_FILES) {\n const sourcePath = path.join(sourceDir, file);\n const targetPath = path.join(targetDir, file);\n\n if (!fs.existsSync(sourcePath)) {\n skipped.push(file);\n continue;\n }\n\n const sourceContent = fs.readFileSync(sourcePath, \"utf-8\");\n\n // Skip if identical content already exists\n if (fs.existsSync(targetPath)) {\n const existingContent = fs.readFileSync(targetPath, \"utf-8\");\n if (existingContent === sourceContent) {\n skipped.push(file);\n continue;\n }\n }\n\n fs.writeFileSync(targetPath, sourceContent, \"utf-8\");\n installed.push(file);\n }\n\n return {\n installed,\n skipped,\n targetDir,\n message: installed.length > 0\n ? `Installed ${installed.length} command(s) to ${targetDir}`\n : \"All commands already up to date\",\n };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getClaudeSettingsPath } from \"../src/config/paths.js\";\nimport { isWindows } from \"../src/util/platform.js\";\nimport { getPackageRoot } from \"../src/config/package-root.js\";\n\ninterface HookCommand {\n type: \"command\";\n command: string;\n}\n\ninterface HookEntry {\n matcher: string;\n hooks: HookCommand[];\n}\n\ninterface ClaudeSettings {\n hooks?: {\n PostToolUse?: HookEntry[];\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\nfunction getHookScriptPath(): string {\n const hooksDir = path.join(getPackageRoot(), \"hooks\");\n return isWindows()\n ? path.join(hooksDir, \"post-tool-use-review.ps1\")\n : path.join(hooksDir, \"post-tool-use-review.sh\");\n}\n\nfunction buildHookCommand(): { command: string; scriptPath: string } | null {\n const scriptPath = getHookScriptPath();\n if (!fs.existsSync(scriptPath)) {\n return null;\n }\n const command = isWindows()\n ? `powershell -ExecutionPolicy Bypass -File \"${scriptPath}\"`\n : `bash \"${scriptPath}\"`;\n return { command, scriptPath };\n}\n\nexport function installHook(): {\n installed: boolean;\n settingsPath: string;\n message: string;\n} {\n const settingsPath = getClaudeSettingsPath();\n let settings: ClaudeSettings = {};\n\n if (fs.existsSync(settingsPath)) {\n try {\n const raw = fs.readFileSync(settingsPath, \"utf-8\");\n settings = JSON.parse(raw) as ClaudeSettings;\n } catch {\n return {\n installed: false,\n settingsPath,\n message: `Failed to parse ${settingsPath}. Back it up and fix manually.`,\n };\n }\n }\n\n const hookResult = buildHookCommand();\n if (!hookResult) {\n return {\n installed: false,\n settingsPath,\n message: \"Hook script not found. Run `npm run build` first.\",\n };\n }\n\n // Correct Claude Code hook format: matcher + hooks array\n const hookEntry: HookEntry = {\n matcher: \"Write|Edit|MultiEdit|NotebookEdit\",\n hooks: [\n {\n type: \"command\",\n command: hookResult.command,\n },\n ],\n };\n\n const existingHooks = settings.hooks?.PostToolUse ?? [];\n\n // Check if already registered (search in nested hooks array)\n const alreadyExists = existingHooks.some((h) =>\n h.hooks?.some((inner) => inner.command.includes(\"skill-codex\")),\n );\n if (alreadyExists) {\n return {\n installed: false,\n settingsPath,\n message: \"skill-codex hook already registered.\",\n };\n }\n\n const updatedSettings: ClaudeSettings = {\n ...settings,\n hooks: {\n ...settings.hooks,\n PostToolUse: [...existingHooks, hookEntry],\n },\n };\n\n const dir = path.dirname(settingsPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Atomic write: temp file then rename\n const tmpPath = settingsPath + \".tmp\";\n fs.writeFileSync(tmpPath, JSON.stringify(updatedSettings, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmpPath, settingsPath);\n\n // Ensure bash script is executable on unix\n if (!isWindows()) {\n const shPath = path.resolve(path.dirname(getHookScriptPath()), \"post-tool-use-review.sh\");\n try {\n fs.chmodSync(shPath, 0o755);\n } catch {\n // Best effort\n }\n }\n\n return {\n installed: true,\n settingsPath,\n message: `PostToolUse hook registered in ${settingsPath}`,\n };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getGlobalSkillsDir } from \"../src/config/paths.js\";\nimport { getPackageRoot } from \"../src/config/package-root.js\";\n\nconst SKILL_NAME = \"codex-bridge\";\n\ninterface InstallSkillResult {\n readonly installed: boolean;\n readonly targetDir: string;\n readonly copiedFiles: readonly string[];\n readonly message: string;\n}\n\nfunction getSkillSourceDir(): string {\n return path.join(getPackageRoot(), \"skills\", SKILL_NAME);\n}\n\nfunction copyDirRecursive(source: string, target: string): readonly string[] {\n const copied: string[] = [];\n\n if (!fs.existsSync(target)) {\n fs.mkdirSync(target, { recursive: true });\n }\n\n const entries = fs.readdirSync(source, { withFileTypes: true });\n for (const entry of entries) {\n const sourcePath = path.join(source, entry.name);\n const targetPath = path.join(target, entry.name);\n\n if (entry.isDirectory()) {\n copied.push(...copyDirRecursive(sourcePath, targetPath));\n } else if (entry.isFile()) {\n const sourceContent = fs.readFileSync(sourcePath);\n let shouldWrite = true;\n if (fs.existsSync(targetPath)) {\n const existing = fs.readFileSync(targetPath);\n shouldWrite = !existing.equals(sourceContent);\n }\n if (shouldWrite) {\n fs.writeFileSync(targetPath, sourceContent);\n copied.push(entry.name);\n }\n }\n }\n\n return copied;\n}\n\nexport function installSkill(): InstallSkillResult {\n const sourceDir = getSkillSourceDir();\n const targetDir = path.join(getGlobalSkillsDir(), SKILL_NAME);\n\n if (!fs.existsSync(sourceDir)) {\n return {\n installed: false,\n targetDir,\n copiedFiles: [],\n message: `Skill source not found at ${sourceDir}`,\n };\n }\n\n const skillFile = path.join(sourceDir, \"SKILL.md\");\n if (!fs.existsSync(skillFile)) {\n return {\n installed: false,\n targetDir,\n copiedFiles: [],\n message: `SKILL.md missing in ${sourceDir}`,\n };\n }\n\n const copied = copyDirRecursive(sourceDir, targetDir);\n\n return {\n installed: true,\n targetDir,\n copiedFiles: copied,\n message: copied.length > 0\n ? `Installed skill '${SKILL_NAME}' to ${targetDir} (${copied.length} file(s))`\n : `Skill '${SKILL_NAME}' already up to date`,\n };\n}\n\nexport function uninstallSkill(): { removed: boolean; targetDir: string } {\n const targetDir = path.join(getGlobalSkillsDir(), SKILL_NAME);\n if (!fs.existsSync(targetDir)) {\n return { removed: false, targetDir };\n }\n fs.rmSync(targetDir, { recursive: true, force: true });\n return { removed: true, targetDir };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport which from \"which\";\nimport {\n getGlobalMcpConfigPath,\n getGlobalCommandsDir,\n getGlobalSkillsDir,\n} from \"../src/config/paths.js\";\n\ninterface CheckResult {\n readonly name: string;\n readonly pass: boolean;\n readonly detail: string;\n}\n\nexport async function runVerification(): Promise<{\n results: CheckResult[];\n allPassed: boolean;\n}> {\n const results: CheckResult[] = [];\n\n // 1. Node.js version\n const nodeVersion = process.versions.node;\n const nodeMajor = parseInt(nodeVersion.split(\".\")[0] ?? \"0\", 10);\n results.push({\n name: \"Node.js >= 18\",\n pass: nodeMajor >= 18,\n detail: `v${nodeVersion}`,\n });\n\n // 2. Codex CLI on PATH\n let codexPath: string | null = null;\n try {\n codexPath = await which(\"codex\");\n results.push({ name: \"Codex CLI found\", pass: true, detail: codexPath });\n } catch {\n results.push({ name: \"Codex CLI found\", pass: false, detail: \"Not found. Run: npm i -g @openai/codex\" });\n }\n\n // 3. MCP server registered\n const mcpPath = getGlobalMcpConfigPath();\n let mcpRegistered = false;\n if (fs.existsSync(mcpPath)) {\n try {\n const raw = fs.readFileSync(mcpPath, \"utf-8\");\n const config = JSON.parse(raw);\n mcpRegistered = \"skill-codex\" in (config.mcpServers ?? {});\n } catch {\n // Parse error\n }\n }\n results.push({\n name: \"MCP server registered\",\n pass: mcpRegistered,\n detail: mcpRegistered ? mcpPath : `Not found in ${mcpPath}`,\n });\n\n // 4. Slash commands installed\n const commandsDir = getGlobalCommandsDir();\n const expectedCommands = [\"codex-review.md\", \"codex-do.md\", \"codex-consult.md\"];\n const missingCommands = expectedCommands.filter(\n (cmd) => !fs.existsSync(`${commandsDir}/${cmd}`),\n );\n results.push({\n name: \"Slash commands installed\",\n pass: missingCommands.length === 0,\n detail: missingCommands.length === 0\n ? `All 3 commands in ${commandsDir}`\n : `Missing: ${missingCommands.join(\", \")}`,\n });\n\n // 5. Agent skill installed\n const skillFile = path.join(getGlobalSkillsDir(), \"codex-bridge\", \"SKILL.md\");\n const skillInstalled = fs.existsSync(skillFile);\n results.push({\n name: \"Agent skill installed\",\n pass: skillInstalled,\n detail: skillInstalled ? skillFile : `Not found at ${skillFile}`,\n });\n\n return {\n results,\n allPassed: results.every((r) => r.pass),\n };\n}\n"],"mappings":"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","names":["fs","path","fs","path","fs","path","path","path","path","path","fs","fs","path","path","fs","fs","path","path","fs","command","fs","path","path","fs","fs","path","fs","path","fs","path","path","fs"]}
|