claude-code-pack 1.0.1 → 1.2.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 CHANGED
@@ -88,9 +88,21 @@ Everything is fetched fresh from GitHub, so you always get the latest version.
88
88
 
89
89
  | Setting | Value |
90
90
  |---------|-------|
91
- | **Model** | `opus` |
91
+ | **Model (Claude Code)** | `opus` |
92
+ | **Model (Codex)** | `gpt-5.4` (reasoning effort: `xhigh`) |
92
93
  | **Statusline** | Custom bash script with context window, 5h/7d rate limit bars, and color-coded usage indicators |
93
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 works |
100
+ |------|-------------|
101
+ | **MCP servers** | Generated into `config.toml` from the same `mcpServers` in `claude-pack.config.json` |
102
+ | **Skills** | Installed to both `~/.claude/skills/` (Claude Code) and `~/.codex/skills/` (Codex) from the same bundled source |
103
+
104
+ This means when you add a new MCP server or skill to the pack, both Claude Code and Codex pick it up.
105
+
94
106
  ## Commands
95
107
 
96
108
  ```bash
@@ -103,6 +115,7 @@ claude-pack install --force # Overwrite existing installations
103
115
  claude-pack install --skip-plugins # Skip plugin installation
104
116
  claude-pack install --skip-skills # Skip skill installation
105
117
  claude-pack install --skip-settings # Skip settings merge
118
+ claude-pack install --skip-codex # Skip Codex config generation
106
119
  claude-pack install --dry-run # Preview without changes
107
120
  ```
108
121
 
@@ -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
  }
@@ -76,6 +76,33 @@
76
76
  "args": [
77
77
  "mcp-server-time"
78
78
  ]
79
+ },
80
+ "playwright": {
81
+ "command": "npx",
82
+ "args": ["-y", "@playwright/mcp@latest"]
83
+ },
84
+ "aws_knowledge_mcp": {
85
+ "type": "http",
86
+ "url": "https://knowledge-mcp.global.api.aws"
87
+ },
88
+ "awslabs_core_mcp_server": {
89
+ "command": "uvx",
90
+ "args": ["awslabs.core-mcp-server@latest"],
91
+ "env": { "FASTMCP_LOG_LEVEL": "ERROR" }
92
+ },
93
+ "awslabs_aws_api_mcp_server": {
94
+ "command": "uvx",
95
+ "args": ["awslabs.aws-api-mcp-server@latest"],
96
+ "env": { "AWS_REGION": "us-east-1" }
97
+ },
98
+ "awslabs_aws_documentation_mcp_server": {
99
+ "command": "uvx",
100
+ "args": ["awslabs.aws-documentation-mcp-server@latest"],
101
+ "env": { "FASTMCP_LOG_LEVEL": "ERROR", "AWS_DOCUMENTATION_PARTITION": "aws" }
102
+ },
103
+ "exa": {
104
+ "command": "npx",
105
+ "args": ["-y", "exa-mcp-server"]
79
106
  }
80
107
  },
81
108
  "settings": {
@@ -87,5 +114,11 @@
87
114
  },
88
115
  "assets": {
89
116
  "statusline": "assets/statusline-command.sh"
117
+ },
118
+ "codex": {
119
+ "enabled": true,
120
+ "model": "gpt-5.4",
121
+ "model_reasoning_effort": "xhigh",
122
+ "configPath": "$HOME/.codex/config.toml"
90
123
  }
91
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-pack",
3
- "version": "1.0.1",
3
+ "version": "1.2.1",
4
4
  "description": "Portable Claude Code setup — installs plugins, skills, MCP servers, and statusline on any machine",
5
5
  "type": "module",
6
6
  "bin": {
package/src/install.mjs CHANGED
@@ -13,6 +13,8 @@ const PLUGINS_DIR = join(CLAUDE_DIR, 'plugins');
13
13
  const MARKETPLACES_DIR = join(PLUGINS_DIR, 'marketplaces');
14
14
  const CACHE_DIR = join(PLUGINS_DIR, 'cache');
15
15
  const SKILLS_DIR = join(CLAUDE_DIR, 'skills');
16
+ const CODEX_DIR = join(homedir(), '.codex');
17
+ const CODEX_SKILLS_DIR = join(CODEX_DIR, 'skills');
16
18
  const SETTINGS_PATH = join(CLAUDE_DIR, 'settings.json');
17
19
  const KNOWN_MARKETPLACES_PATH = join(PLUGINS_DIR, 'known_marketplaces.json');
18
20
  const INSTALLED_PLUGINS_PATH = join(PLUGINS_DIR, 'installed_plugins.json');
@@ -264,56 +266,55 @@ function installPlugins(config, flags) {
264
266
 
265
267
  // ─── Skill Installation ────────────────────────────────────────────────
266
268
 
267
- function installSkills(config, flags) {
268
- console.log('\n🛠 Skills');
269
-
270
- for (const skill of config.skills || []) {
271
- const dest = join(SKILLS_DIR, skill.name);
272
- const exists = existsSync(dest);
273
-
274
- if (skill.source === 'bundled') {
275
- // Copy from package's bundled skills/
276
- const src = join(PACK_ROOT, 'skills', skill.name);
277
- if (!existsSync(src)) {
278
- log('✗', `Skill "${skill.name}": bundled source not found at ${src}`);
279
- continue;
280
- }
269
+ function installSkillTo(skill, targetDir, label, flags) {
270
+ const dest = join(targetDir, skill.name);
271
+ const exists = existsSync(dest);
281
272
 
282
- if (exists && !flags.force) {
283
- if (flags.dryRun) {
284
- log('●', `Skill "${skill.name}": already installed`);
285
- } else {
286
- log('●', `Skill "${skill.name}": already installed (use --force to overwrite)`);
287
- }
288
- } else {
289
- if (flags.dryRun) {
290
- log('○', `Would install bundled skill "${skill.name}"`);
291
- } else {
292
- ensureDir(SKILLS_DIR);
293
- cpSync(src, dest, { recursive: true });
294
- log('✓', `Skill "${skill.name}" installed (bundled)`);
295
- }
296
- }
297
- } else if (skill.source === 'github' && skill.repo) {
298
- // Clone from GitHub
299
- if (exists && !flags.force) {
300
- if (flags.dryRun) {
301
- log('●', `Skill "${skill.name}": already installed`);
302
- } else {
303
- log('↻', `Updating skill "${skill.name}" from ${skill.repo}...`);
304
- gitClone(skill.repo, dest);
305
- }
273
+ if (skill.source === 'bundled') {
274
+ const src = join(PACK_ROOT, 'skills', skill.name);
275
+ if (!existsSync(src)) {
276
+ log('✗', `${label} "${skill.name}": bundled source not found`);
277
+ return;
278
+ }
279
+ if (exists && !flags.force) {
280
+ log('●', `${label} "${skill.name}": already installed`);
281
+ } else if (flags.dryRun) {
282
+ log('○', `Would install bundled skill "${skill.name}" to ${label}`);
283
+ } else {
284
+ ensureDir(targetDir);
285
+ cpSync(src, dest, { recursive: true });
286
+ log('✓', `${label} "${skill.name}" installed`);
287
+ }
288
+ } else if (skill.source === 'github' && skill.repo) {
289
+ if (exists && !flags.force) {
290
+ if (flags.dryRun) {
291
+ log('●', `${label} "${skill.name}": already installed`);
306
292
  } else {
307
- if (flags.dryRun) {
308
- log('○', `Would clone skill "${skill.name}" from ${skill.repo}`);
309
- } else {
310
- log('↓', `Cloning skill "${skill.name}" from ${skill.repo}...`);
311
- const result = gitClone(skill.repo, dest);
312
- log('✓', `Skill "${skill.name}" ${result}`);
313
- }
293
+ log('↻', `Updating ${label} "${skill.name}" from ${skill.repo}...`);
294
+ gitClone(skill.repo, dest);
314
295
  }
296
+ } else if (flags.dryRun) {
297
+ log('○', `Would clone skill "${skill.name}" to ${label}`);
315
298
  } else {
316
- log('', `Skill "${skill.name}": unknown source type "${skill.source}"`);
299
+ log('', `Cloning ${label} "${skill.name}" from ${skill.repo}...`);
300
+ const result = gitClone(skill.repo, dest);
301
+ log('✓', `${label} "${skill.name}" ${result}`);
302
+ }
303
+ } else {
304
+ log('✗', `${label} "${skill.name}": unknown source "${skill.source}"`);
305
+ }
306
+ }
307
+
308
+ function installSkills(config, flags) {
309
+ console.log('\n🛠 Skills (Claude Code → ~/.claude/skills/)');
310
+ for (const skill of config.skills || []) {
311
+ installSkillTo(skill, SKILLS_DIR, 'Claude', flags);
312
+ }
313
+
314
+ if (!flags.skipCodex && config.codex?.enabled) {
315
+ console.log('\n🛠 Skills (Codex → ~/.codex/skills/)');
316
+ for (const skill of config.skills || []) {
317
+ installSkillTo(skill, CODEX_SKILLS_DIR, 'Codex', flags);
317
318
  }
318
319
  }
319
320
  }
@@ -361,6 +362,7 @@ async function installSettings(config, flags) {
361
362
  }
362
363
 
363
364
  // Merge MCP servers
365
+ let resolvedFsPath = null;
364
366
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
365
367
  console.log('\n🔌 MCP Servers');
366
368
  if (!settings.mcpServers) settings.mcpServers = {};
@@ -377,6 +379,7 @@ async function installSettings(config, flags) {
377
379
  const answer = await prompt(` Use "${home}" as filesystem root? [Y/n/custom path] `);
378
380
 
379
381
  if (answer === '' || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
382
+ resolvedFsPath = home;
380
383
  const resolved = JSON.parse(JSON.stringify(serverConfig));
381
384
  resolved.args = resolved.args.map((a) => a === '$HOME' ? home : a);
382
385
  settings.mcpServers[name] = resolved;
@@ -392,6 +395,7 @@ async function installSettings(config, flags) {
392
395
  } else {
393
396
  // User provided a custom path
394
397
  const customPath = answer.replace(/^~/, home);
398
+ resolvedFsPath = customPath;
395
399
  const resolved = JSON.parse(JSON.stringify(serverConfig));
396
400
  resolved.args = resolved.args.map((a) => a === '$HOME' ? customPath : a);
397
401
  settings.mcpServers[name] = resolved;
@@ -413,6 +417,110 @@ async function installSettings(config, flags) {
413
417
  if (!flags.dryRun) {
414
418
  writeJSON(SETTINGS_PATH, settings);
415
419
  }
420
+
421
+ return resolvedFsPath;
422
+ }
423
+
424
+ // ─── Codex Config Generation ───────────────────────────────────────────
425
+
426
+ function tomlValue(val) {
427
+ if (typeof val === 'string') return `"${val}"`;
428
+ if (typeof val === 'boolean') return val ? 'true' : 'false';
429
+ if (typeof val === 'number') return String(val);
430
+ if (Array.isArray(val)) {
431
+ const items = val.map(tomlValue);
432
+ // Use multiline for long arrays
433
+ if (items.join(', ').length > 60) {
434
+ return '[\n ' + items.join(',\n ') + '\n]';
435
+ }
436
+ return '[' + items.join(', ') + ']';
437
+ }
438
+ return `"${val}"`;
439
+ }
440
+
441
+ function generateCodexToml(config, resolvedFsPath) {
442
+ const codex = config.codex;
443
+ const home = homedir();
444
+ const lines = [];
445
+
446
+ lines.push(`model = "${codex.model}"`);
447
+ if (codex.model_reasoning_effort) {
448
+ lines.push(`model_reasoning_effort = "${codex.model_reasoning_effort}"`);
449
+ }
450
+ lines.push('');
451
+
452
+ // Generate MCP server sections from the shared config
453
+ for (const [name, serverConfig] of Object.entries(config.mcpServers || {})) {
454
+ lines.push(`[mcp_servers.${name}]`);
455
+ lines.push('enabled = true');
456
+
457
+ if (serverConfig.type === 'http' || serverConfig.url) {
458
+ lines.push(`url = "${serverConfig.url}"`);
459
+ } else if (serverConfig.command) {
460
+ let command = serverConfig.command;
461
+ const args = serverConfig.args || [];
462
+
463
+ // For npx commands, resolve to full node/npx path for Codex compatibility
464
+ let resolvedArgs = args.map((a) => {
465
+ if (a === '$HOME') return resolvedFsPath || home;
466
+ return a;
467
+ });
468
+
469
+ lines.push(`command = "${command}"`);
470
+ if (resolvedArgs.length > 0) {
471
+ lines.push(`args = ${tomlValue(resolvedArgs)}`);
472
+ }
473
+ }
474
+
475
+ // Environment variables
476
+ if (serverConfig.env && Object.keys(serverConfig.env).length > 0) {
477
+ lines.push('');
478
+ lines.push(`[mcp_servers.${name}.env]`);
479
+ for (const [k, v] of Object.entries(serverConfig.env)) {
480
+ lines.push(`${k} = "${v}"`);
481
+ }
482
+ }
483
+
484
+ lines.push('');
485
+ }
486
+
487
+ return lines.join('\n');
488
+ }
489
+
490
+ async function installCodex(config, flags, resolvedFsPath) {
491
+ if (!config.codex?.enabled) return;
492
+
493
+ console.log('\n🤖 Codex');
494
+
495
+ const home = homedir();
496
+ const configPath = (config.codex.configPath || '$HOME/.codex/config.toml').replace(/\$HOME/g, home);
497
+ const configDir = dirname(configPath);
498
+
499
+ if (flags.dryRun) {
500
+ log('○', `Would generate Codex config at ${configPath}`);
501
+ log('○', `Model: ${config.codex.model}`);
502
+ const mcpCount = Object.keys(config.mcpServers || {}).length;
503
+ log('○', `MCP servers: ${mcpCount} (same as Claude Code)`);
504
+ return;
505
+ }
506
+
507
+ const toml = generateCodexToml(config, resolvedFsPath);
508
+ ensureDir(configDir);
509
+
510
+ const exists = existsSync(configPath);
511
+ if (exists && !flags.force) {
512
+ const answer = await prompt(` Codex config exists at ${configPath}. Overwrite? [y/N] `);
513
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
514
+ log('⊘', 'Codex config skipped (use --force to overwrite)');
515
+ return;
516
+ }
517
+ }
518
+
519
+ writeFileSync(configPath, toml);
520
+ log('✓', `Codex config written to ${configPath}`);
521
+ log('✓', `Model: ${config.codex.model}`);
522
+ log('✓', `Skills: ~/.codex/skills/ (installed separately)`);
523
+ log('✓', `MCP servers: ${Object.keys(config.mcpServers || {}).length} configured`);
416
524
  }
417
525
 
418
526
  // ─── Main Install ──────────────────────────────────────────────────────
@@ -446,13 +554,19 @@ export async function install(flags = {}) {
446
554
 
447
555
  if (!flags.skipPlugins) installPlugins(config, flags);
448
556
  if (!flags.skipSkills) installSkills(config, flags);
449
- if (!flags.skipSettings) await installSettings(config, flags);
557
+ let resolvedFsPath = null;
558
+ if (!flags.skipSettings) {
559
+ resolvedFsPath = await installSettings(config, flags);
560
+ }
561
+ if (!flags.skipCodex) {
562
+ await installCodex(config, flags, resolvedFsPath);
563
+ }
450
564
 
451
565
  console.log('\n────────────────────────────────────────────');
452
566
  if (flags.dryRun) {
453
567
  console.log(' Dry run complete. Run without --dry-run to apply.');
454
568
  } else {
455
- console.log(' ✓ All done! Restart Claude Code to pick up changes.');
569
+ console.log(' ✓ All done! Restart Claude Code / Codex to pick up changes.');
456
570
  }
457
571
  console.log('');
458
572
  }