claude-code-pack 1.0.0 → 1.2.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 CHANGED
@@ -2,19 +2,36 @@
2
2
 
3
3
  Portable Claude Code setup — installs your plugins, skills, MCP servers, and settings on any machine with a single command.
4
4
 
5
- ## Quick Start
5
+ ## Installation
6
+
7
+ ### From npm
6
8
 
7
9
  ```bash
8
- npx claude-pack install
10
+ npx claude-code-pack install
9
11
  ```
10
12
 
11
13
  Or install globally:
12
14
 
13
15
  ```bash
14
- npm install -g claude-pack
16
+ npm install -g claude-code-pack
17
+ claude-pack install
18
+ ```
19
+
20
+ ### From GitHub
21
+
22
+ ```bash
23
+ git clone git@github.com:lvtan71/claude-pack.git
24
+ cd claude-pack
25
+ npm link
15
26
  claude-pack install
16
27
  ```
17
28
 
29
+ Or run directly without installing:
30
+
31
+ ```bash
32
+ npx github:lvtan71/claude-pack install
33
+ ```
34
+
18
35
  ## What It Does
19
36
 
20
37
  `claude-pack` reads `claude-pack.config.json` and sets up your Claude Code environment:
@@ -28,43 +45,63 @@ claude-pack install
28
45
 
29
46
  Everything is fetched fresh from GitHub, so you always get the latest version.
30
47
 
31
- ## Configuration
48
+ ## What's Included
32
49
 
33
- Edit `claude-pack.config.json` to customize your setup:
50
+ ### Plugins
34
51
 
35
- ```json
36
- {
37
- "plugins": [
38
- {
39
- "name": "claude-mem",
40
- "marketplace": "thedotmack",
41
- "repo": "thedotmack/claude-mem",
42
- "enabled": true
43
- }
44
- ],
45
- "marketplaces": [
46
- { "name": "thedotmack", "repo": "thedotmack/claude-mem" }
47
- ],
48
- "skills": [
49
- { "name": "my-skill", "source": "bundled" },
50
- { "name": "remote-skill", "source": "github", "repo": "user/repo" }
51
- ],
52
- "mcpServers": {
53
- "my-server": {
54
- "command": "npx",
55
- "args": ["-y", "@some/mcp-server"]
56
- }
57
- },
58
- "settings": {
59
- "model": "opus"
60
- }
61
- }
62
- ```
52
+ | Plugin | Source | Description |
53
+ |--------|--------|-------------|
54
+ | **claude-mem** | [thedotmack/claude-mem](https://github.com/thedotmack/claude-mem) | Persistent cross-session memory, smart code exploration (AST-based), phased implementation plans, and timeline reports |
55
+ | **understand-anything** | [Lum1104/Understand-Anything](https://github.com/Lum1104/Understand-Anything) | Codebase analysis, interactive knowledge graphs, architecture visualization, onboarding guides, and diff analysis |
56
+
57
+ ### Marketplaces
63
58
 
64
- ### Skill Sources
59
+ | Marketplace | Source | Description |
60
+ |-------------|--------|-------------|
61
+ | **thedotmack** | [thedotmack/claude-mem](https://github.com/thedotmack/claude-mem) | Plugin marketplace for claude-mem and related tools |
62
+ | **understand-anything** | [Lum1104/Understand-Anything](https://github.com/Lum1104/Understand-Anything) | Codebase understanding, onboarding, and architecture analysis tools |
65
63
 
66
- - `"source": "bundled"` — copies from the `skills/` directory in this package
67
- - `"source": "github", "repo": "user/repo"` — clones from GitHub
64
+ ### Skills (Bundled)
65
+
66
+ | Skill | Description |
67
+ |-------|-------------|
68
+ | **cloud-devops** | Cloud infrastructure and DevOps workflows — AWS, Azure, GCP, Kubernetes, Terraform, CI/CD, monitoring |
69
+ | **fastapi** | FastAPI best practices, Pydantic models, dependency injection, streaming, and latest patterns |
70
+ | **senior-ml-engineer** | MLOps pipelines, model deployment, drift monitoring, RAG systems, LLM integration, cost optimization |
71
+ | **technical-writer** | Documentation, API references, guides, tutorials, and technical content creation |
72
+
73
+ ### MCP Servers
74
+
75
+ | Server | Type | Source | Description |
76
+ |--------|------|--------|-------------|
77
+ | **linear** | HTTP | [mcp.linear.app](https://mcp.linear.app/mcp) | Linear project management — issues, projects, comments, milestones |
78
+ | **context7** | HTTP | [mcp.context7.com](https://mcp.context7.com/mcp) | Up-to-date library documentation and code examples |
79
+ | **fetch** | stdio | `mcp-server-fetch` (uvx) | Fetch web content for processing |
80
+ | **filesystem** | stdio | `@modelcontextprotocol/server-filesystem` (npx) | File system access (defaults to `$HOME`, configurable at install) |
81
+ | **sequential-thinking** | stdio | `@modelcontextprotocol/server-sequential-thinking` (npx) | Step-by-step reasoning and problem decomposition |
82
+ | **time** | stdio | `mcp-server-time` (uvx) | Current time and timezone operations |
83
+
84
+ > `$HOME` in MCP server args is automatically resolved to the user's home directory at install time.
85
+ > The **filesystem** server prompts you during install to confirm or customize the path. Works on Linux/WSL, macOS, and Windows.
86
+
87
+ ### Settings
88
+
89
+ | Setting | Value |
90
+ |---------|-------|
91
+ | **Model (Claude Code)** | `opus` |
92
+ | **Model (Codex)** | `gpt-5.4` (reasoning effort: `xhigh`) |
93
+ | **Statusline** | Custom bash script with context window, 5h/7d rate limit bars, and color-coded usage indicators |
94
+
95
+ ### Codex Integration
96
+
97
+ The installer also generates a `~/.codex/config.toml` that **reuses the same MCP servers and skills** as Claude Code — no duplicate configuration needed.
98
+
99
+ | What | How it's shared |
100
+ |------|----------------|
101
+ | **MCP servers** | Generated into `config.toml` from the same `mcpServers` in `claude-pack.config.json` |
102
+ | **Skills** | Codex `[[skills.config]]` points to `~/.claude/skills/` (the same directory Claude Code uses) |
103
+
104
+ This means when you add a new MCP server or skill to the pack, both Claude Code and Codex pick it up.
68
105
 
69
106
  ## Commands
70
107
 
@@ -78,32 +115,66 @@ claude-pack install --force # Overwrite existing installations
78
115
  claude-pack install --skip-plugins # Skip plugin installation
79
116
  claude-pack install --skip-skills # Skip skill installation
80
117
  claude-pack install --skip-settings # Skip settings merge
118
+ claude-pack install --skip-codex # Skip Codex config generation
81
119
  claude-pack install --dry-run # Preview without changes
82
120
  ```
83
121
 
84
- ## Adding New Components
122
+ ## Contributing
85
123
 
86
- ### Add a plugin
124
+ ### Setup
125
+
126
+ ```bash
127
+ git clone git@github.com:lvtan71/claude-pack.git
128
+ cd claude-pack
129
+ npm link # makes `claude-pack` command available locally
130
+ ```
87
131
 
88
- 1. Add to `claude-pack.config.json` under `plugins` and `marketplaces`
89
- 2. Run `claude-pack install`
132
+ ### Adding a Plugin
90
133
 
91
- ### Add a skill from GitHub
134
+ 1. Edit `claude-pack.config.json` add entries to both `plugins` and `marketplaces`:
92
135
 
93
136
  ```json
137
+ // In "plugins" array:
138
+ {
139
+ "name": "my-plugin",
140
+ "marketplace": "marketplace-name",
141
+ "repo": "github-user/repo-name",
142
+ "enabled": true
143
+ }
144
+
145
+ // In "marketplaces" array:
94
146
  {
95
- "name": "my-skill",
96
- "source": "github",
97
- "repo": "username/my-skill-repo"
147
+ "name": "marketplace-name",
148
+ "repo": "github-user/repo-name"
98
149
  }
99
150
  ```
100
151
 
101
- ### Add a bundled skill
152
+ 2. Update the **Plugins** and **Marketplaces** tables in this README.
102
153
 
103
- 1. Create `skills/my-skill/SKILL.md`
104
- 2. Add to config: `{ "name": "my-skill", "source": "bundled" }`
154
+ ### Adding a Skill
105
155
 
106
- ### Add an MCP server
156
+ **Option A Bundled skill** (skill files live in this repo):
157
+
158
+ 1. Create `skills/my-skill/SKILL.md` (and optional `references/` directory).
159
+ 2. Add to `claude-pack.config.json`:
160
+
161
+ ```json
162
+ { "name": "my-skill", "source": "bundled" }
163
+ ```
164
+
165
+ **Option B — GitHub-hosted skill** (cloned at install time):
166
+
167
+ 1. Add to `claude-pack.config.json`:
168
+
169
+ ```json
170
+ { "name": "my-skill", "source": "github", "repo": "github-user/my-skill-repo" }
171
+ ```
172
+
173
+ 2. Update the **Skills** table in this README.
174
+
175
+ ### Adding an MCP Server
176
+
177
+ 1. Add to the `mcpServers` object in `claude-pack.config.json`:
107
178
 
108
179
  ```json
109
180
  "mcpServers": {
@@ -114,7 +185,46 @@ claude-pack install --dry-run # Preview without changes
114
185
  }
115
186
  ```
116
187
 
188
+ 2. Update the **MCP Servers** section in this README.
189
+
190
+ ### Publishing a New Version
191
+
192
+ 1. Make your changes and commit.
193
+ 2. Bump the version:
194
+
195
+ ```bash
196
+ npm version patch # 1.0.0 → 1.0.1 (bug fixes, small updates)
197
+ npm version minor # 1.0.0 → 1.1.0 (new plugins/skills added)
198
+ npm version major # 1.0.0 → 2.0.0 (breaking config changes)
199
+ ```
200
+
201
+ 3. Push with tags:
202
+
203
+ ```bash
204
+ git push origin main --tags
205
+ ```
206
+
207
+ 4. Publish to npm:
208
+
209
+ ```bash
210
+ npm publish
211
+ ```
212
+
213
+ > `npm version` automatically creates a git commit and tag (e.g., `v1.1.0`).
214
+ > Make sure all changes are committed before running it.
215
+
216
+ ### Pull Request Guidelines
217
+
218
+ - One plugin/skill/MCP server per PR when possible.
219
+ - Update the README tables to reflect your additions.
220
+ - Test with `claude-pack install --dry-run` before submitting.
221
+ - Test a full install with `claude-pack install --force` on a clean environment if possible.
222
+
117
223
  ## Requirements
118
224
 
119
225
  - Node.js >= 18
120
226
  - git
227
+
228
+ ## License
229
+
230
+ MIT
@@ -11,6 +11,7 @@ const flags = {
11
11
  skipPlugins: args.includes('--skip-plugins'),
12
12
  skipSkills: args.includes('--skip-skills'),
13
13
  skipSettings: args.includes('--skip-settings'),
14
+ skipCodex: args.includes('--skip-codex'),
14
15
  };
15
16
 
16
17
  switch (command) {
@@ -44,5 +45,6 @@ Flags:
44
45
  --skip-plugins Skip plugin installation
45
46
  --skip-skills Skip skill installation
46
47
  --skip-settings Skip settings merge
48
+ --skip-codex Skip Codex config generation
47
49
  `);
48
50
  }
@@ -5,6 +5,12 @@
5
5
  "marketplace": "thedotmack",
6
6
  "repo": "thedotmack/claude-mem",
7
7
  "enabled": true
8
+ },
9
+ {
10
+ "name": "understand-anything",
11
+ "marketplace": "understand-anything",
12
+ "repo": "Lum1104/Understand-Anything",
13
+ "enabled": true
8
14
  }
9
15
  ],
10
16
  "marketplaces": [
@@ -18,12 +24,60 @@
18
24
  }
19
25
  ],
20
26
  "skills": [
21
- { "name": "cloud-devops", "source": "bundled" },
22
- { "name": "fastapi", "source": "bundled" },
23
- { "name": "senior-ml-engineer", "source": "bundled" },
24
- { "name": "technical-writer", "source": "bundled" }
27
+ {
28
+ "name": "cloud-devops",
29
+ "source": "bundled"
30
+ },
31
+ {
32
+ "name": "fastapi",
33
+ "source": "bundled"
34
+ },
35
+ {
36
+ "name": "senior-ml-engineer",
37
+ "source": "bundled"
38
+ },
39
+ {
40
+ "name": "technical-writer",
41
+ "source": "bundled"
42
+ }
25
43
  ],
26
- "mcpServers": {},
44
+ "mcpServers": {
45
+ "linear": {
46
+ "type": "http",
47
+ "url": "https://mcp.linear.app/mcp"
48
+ },
49
+ "context7": {
50
+ "type": "http",
51
+ "url": "https://mcp.context7.com/mcp"
52
+ },
53
+ "fetch": {
54
+ "command": "uvx",
55
+ "args": [
56
+ "mcp-server-fetch"
57
+ ]
58
+ },
59
+ "filesystem": {
60
+ "command": "npx",
61
+ "args": [
62
+ "-y",
63
+ "@modelcontextprotocol/server-filesystem",
64
+ "$HOME"
65
+ ]
66
+ },
67
+ "sequential-thinking": {
68
+ "command": "npx",
69
+ "args": [
70
+ "-y",
71
+ "@modelcontextprotocol/server-sequential-thinking"
72
+ ]
73
+ },
74
+ "time": {
75
+ "command": "uvx",
76
+ "args": [
77
+ "mcp-server-time"
78
+ ]
79
+ }
80
+ },
27
81
  "settings": {
28
82
  "model": "opus",
29
83
  "statusLine": {
@@ -33,5 +87,12 @@
33
87
  },
34
88
  "assets": {
35
89
  "statusline": "assets/statusline-command.sh"
90
+ },
91
+ "codex": {
92
+ "enabled": true,
93
+ "model": "gpt-5.4",
94
+ "model_reasoning_effort": "xhigh",
95
+ "configPath": "$HOME/.codex/config.toml",
96
+ "skillsPath": "$HOME/.claude/skills"
36
97
  }
37
- }
98
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-pack",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Portable Claude Code setup — installs plugins, skills, MCP servers, and statusline on any machine",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  "license": "MIT",
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "git+https://github.com/tanle/claude-pack.git"
26
+ "url": "git+https://github.com/lvtan71/claude-pack.git"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=18"
package/src/install.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync } from 'node:fs';
3
3
  import { join, dirname, resolve } from 'node:path';
4
- import { homedir } from 'node:os';
4
+ import { homedir, platform } from 'node:os';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { createInterface } from 'node:readline';
6
7
 
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
  const PACK_ROOT = resolve(__dirname, '..');
@@ -37,6 +38,37 @@ function writeJSON(path, data) {
37
38
  writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
38
39
  }
39
40
 
41
+ function prompt(question) {
42
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
43
+ return new Promise((resolve) => {
44
+ rl.question(question, (answer) => {
45
+ rl.close();
46
+ resolve(answer.trim());
47
+ });
48
+ });
49
+ }
50
+
51
+ function getDefaultHome() {
52
+ return homedir();
53
+ }
54
+
55
+ function resolveHomeInConfig(obj) {
56
+ if (typeof obj === 'string') {
57
+ return obj.replace(/\$HOME/g, homedir());
58
+ }
59
+ if (Array.isArray(obj)) {
60
+ return obj.map(resolveHomeInConfig);
61
+ }
62
+ if (obj && typeof obj === 'object') {
63
+ const resolved = {};
64
+ for (const [k, v] of Object.entries(obj)) {
65
+ resolved[k] = resolveHomeInConfig(v);
66
+ }
67
+ return resolved;
68
+ }
69
+ return obj;
70
+ }
71
+
40
72
  function loadConfig() {
41
73
  const configPath = join(PACK_ROOT, 'claude-pack.config.json');
42
74
  return JSON.parse(readFileSync(configPath, 'utf8'));
@@ -59,7 +91,7 @@ function gitClone(repo, dest) {
59
91
  return 'cloned';
60
92
  }
61
93
 
62
- function getPluginVersion(marketplaceDir, pluginName) {
94
+ function getPluginVersion(marketplaceDir) {
63
95
  // Try to read version from plugin's package.json or manifest
64
96
  const candidates = [
65
97
  join(marketplaceDir, 'plugin', 'package.json'),
@@ -82,7 +114,7 @@ function getGitCommitSha(dir) {
82
114
  }
83
115
  }
84
116
 
85
- function findPluginInstallPath(marketplaceDir, pluginName) {
117
+ function findPluginInstallPath(marketplaceDir) {
86
118
  // Look for a plugin/ subdirectory or the root
87
119
  const pluginSubdir = join(marketplaceDir, 'plugin');
88
120
  if (existsSync(pluginSubdir)) return pluginSubdir;
@@ -288,7 +320,7 @@ function installSkills(config, flags) {
288
320
 
289
321
  // ─── Settings Merge ────────────────────────────────────────────────────
290
322
 
291
- function installSettings(config, flags) {
323
+ async function installSettings(config, flags) {
292
324
  console.log('\n⚙ Settings');
293
325
 
294
326
  const settings = readJSON(SETTINGS_PATH);
@@ -329,23 +361,173 @@ function installSettings(config, flags) {
329
361
  }
330
362
 
331
363
  // Merge MCP servers
364
+ let resolvedFsPath = null;
332
365
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
366
+ console.log('\n🔌 MCP Servers');
333
367
  if (!settings.mcpServers) settings.mcpServers = {};
368
+
369
+ // Resolve filesystem server path interactively
370
+ const home = getDefaultHome();
371
+ const os = platform();
372
+ const osLabel = os === 'win32' ? 'Windows' : os === 'darwin' ? 'macOS' : 'Linux/WSL';
373
+
334
374
  for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
375
+ if (name === 'filesystem' && !flags.dryRun) {
376
+ console.log(`\n Detected OS: ${osLabel}`);
377
+ console.log(` Default filesystem path: ${home}`);
378
+ const answer = await prompt(` Use "${home}" as filesystem root? [Y/n/custom path] `);
379
+
380
+ if (answer === '' || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
381
+ resolvedFsPath = home;
382
+ const resolved = JSON.parse(JSON.stringify(serverConfig));
383
+ resolved.args = resolved.args.map((a) => a === '$HOME' ? home : a);
384
+ settings.mcpServers[name] = resolved;
385
+ log('✓', `MCP server "${name}" configured (${home})`);
386
+ } else if (answer.toLowerCase() === 'n' || answer.toLowerCase() === 'no') {
387
+ log('⊘', `MCP server "${name}" skipped — configure manually in ~/.claude/settings.json:`);
388
+ console.log(` "mcpServers": {`);
389
+ console.log(` "filesystem": {`);
390
+ console.log(` "command": "npx",`);
391
+ console.log(` "args": ["-y", "@modelcontextprotocol/server-filesystem", "/your/path"]`);
392
+ console.log(` }`);
393
+ console.log(` }`);
394
+ } else {
395
+ // User provided a custom path
396
+ const customPath = answer.replace(/^~/, home);
397
+ resolvedFsPath = customPath;
398
+ const resolved = JSON.parse(JSON.stringify(serverConfig));
399
+ resolved.args = resolved.args.map((a) => a === '$HOME' ? customPath : a);
400
+ settings.mcpServers[name] = resolved;
401
+ log('✓', `MCP server "${name}" configured (${customPath})`);
402
+ }
403
+ continue;
404
+ }
405
+
406
+ const resolved = resolveHomeInConfig(serverConfig);
335
407
  if (flags.dryRun) {
336
408
  log('○', `Would configure MCP server "${name}"`);
337
409
  } else {
338
- settings.mcpServers[name] = serverConfig;
410
+ settings.mcpServers[name] = resolved;
339
411
  log('✓', `MCP server "${name}" configured`);
340
412
  }
341
413
  }
342
- } else {
343
- log('●', 'No MCP servers to configure (they come from plugins)');
344
414
  }
345
415
 
346
416
  if (!flags.dryRun) {
347
417
  writeJSON(SETTINGS_PATH, settings);
348
418
  }
419
+
420
+ return resolvedFsPath;
421
+ }
422
+
423
+ // ─── Codex Config Generation ───────────────────────────────────────────
424
+
425
+ function tomlValue(val) {
426
+ if (typeof val === 'string') return `"${val}"`;
427
+ if (typeof val === 'boolean') return val ? 'true' : 'false';
428
+ if (typeof val === 'number') return String(val);
429
+ if (Array.isArray(val)) {
430
+ const items = val.map(tomlValue);
431
+ // Use multiline for long arrays
432
+ if (items.join(', ').length > 60) {
433
+ return '[\n ' + items.join(',\n ') + '\n]';
434
+ }
435
+ return '[' + items.join(', ') + ']';
436
+ }
437
+ return `"${val}"`;
438
+ }
439
+
440
+ function generateCodexToml(config, resolvedFsPath) {
441
+ const codex = config.codex;
442
+ const home = homedir();
443
+ const lines = [];
444
+
445
+ lines.push(`model = "${codex.model}"`);
446
+ if (codex.model_reasoning_effort) {
447
+ lines.push(`model_reasoning_effort = "${codex.model_reasoning_effort}"`);
448
+ }
449
+ lines.push('');
450
+
451
+ // Generate MCP server sections from the shared config
452
+ for (const [name, serverConfig] of Object.entries(config.mcpServers || {})) {
453
+ lines.push(`[mcp_servers.${name}]`);
454
+ lines.push('enabled = true');
455
+
456
+ if (serverConfig.type === 'http' || serverConfig.url) {
457
+ lines.push(`url = "${serverConfig.url}"`);
458
+ } else if (serverConfig.command) {
459
+ let command = serverConfig.command;
460
+ const args = serverConfig.args || [];
461
+
462
+ // For npx commands, resolve to full node/npx path for Codex compatibility
463
+ let resolvedArgs = args.map((a) => {
464
+ if (a === '$HOME') return resolvedFsPath || home;
465
+ return a;
466
+ });
467
+
468
+ lines.push(`command = "${command}"`);
469
+ if (resolvedArgs.length > 0) {
470
+ lines.push(`args = ${tomlValue(resolvedArgs)}`);
471
+ }
472
+ }
473
+
474
+ // Environment variables
475
+ if (serverConfig.env && Object.keys(serverConfig.env).length > 0) {
476
+ lines.push('');
477
+ lines.push(`[mcp_servers.${name}.env]`);
478
+ for (const [k, v] of Object.entries(serverConfig.env)) {
479
+ lines.push(`${k} = "${v}"`);
480
+ }
481
+ }
482
+
483
+ lines.push('');
484
+ }
485
+
486
+ // Skills — point to the shared ~/.claude/skills directory
487
+ const skillsPath = (codex.skillsPath || '$HOME/.claude/skills').replace(/\$HOME/g, home);
488
+ lines.push('[[skills.config]]');
489
+ lines.push(`path = "${skillsPath}"`);
490
+ lines.push('enabled = true');
491
+ lines.push('');
492
+
493
+ return lines.join('\n');
494
+ }
495
+
496
+ async function installCodex(config, flags, resolvedFsPath) {
497
+ if (!config.codex?.enabled) return;
498
+
499
+ console.log('\n🤖 Codex');
500
+
501
+ const home = homedir();
502
+ const configPath = (config.codex.configPath || '$HOME/.codex/config.toml').replace(/\$HOME/g, home);
503
+ const configDir = dirname(configPath);
504
+
505
+ if (flags.dryRun) {
506
+ log('○', `Would generate Codex config at ${configPath}`);
507
+ log('○', `Model: ${config.codex.model}`);
508
+ log('○', `Skills path: ~/.claude/skills (shared with Claude Code)`);
509
+ const mcpCount = Object.keys(config.mcpServers || {}).length;
510
+ log('○', `MCP servers: ${mcpCount} (same as Claude Code)`);
511
+ return;
512
+ }
513
+
514
+ const toml = generateCodexToml(config, resolvedFsPath);
515
+ ensureDir(configDir);
516
+
517
+ const exists = existsSync(configPath);
518
+ if (exists && !flags.force) {
519
+ const answer = await prompt(` Codex config exists at ${configPath}. Overwrite? [y/N] `);
520
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
521
+ log('⊘', 'Codex config skipped (use --force to overwrite)');
522
+ return;
523
+ }
524
+ }
525
+
526
+ writeFileSync(configPath, toml);
527
+ log('✓', `Codex config written to ${configPath}`);
528
+ log('✓', `Model: ${config.codex.model}`);
529
+ log('✓', `Skills: ~/.claude/skills (shared with Claude Code)`);
530
+ log('✓', `MCP servers: ${Object.keys(config.mcpServers || {}).length} configured`);
349
531
  }
350
532
 
351
533
  // ─── Main Install ──────────────────────────────────────────────────────
@@ -379,13 +561,19 @@ export async function install(flags = {}) {
379
561
 
380
562
  if (!flags.skipPlugins) installPlugins(config, flags);
381
563
  if (!flags.skipSkills) installSkills(config, flags);
382
- if (!flags.skipSettings) installSettings(config, flags);
564
+ let resolvedFsPath = null;
565
+ if (!flags.skipSettings) {
566
+ resolvedFsPath = await installSettings(config, flags);
567
+ }
568
+ if (!flags.skipCodex) {
569
+ await installCodex(config, flags, resolvedFsPath);
570
+ }
383
571
 
384
572
  console.log('\n────────────────────────────────────────────');
385
573
  if (flags.dryRun) {
386
574
  console.log(' Dry run complete. Run without --dry-run to apply.');
387
575
  } else {
388
- console.log(' ✓ All done! Restart Claude Code to pick up changes.');
576
+ console.log(' ✓ All done! Restart Claude Code / Codex to pick up changes.');
389
577
  }
390
578
  console.log('');
391
579
  }