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 +14 -1
- package/bin/claude-pack.mjs +2 -0
- package/claude-pack.config.json +33 -0
- package/package.json +1 -1
- package/src/install.mjs +162 -48
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
|
|
package/bin/claude-pack.mjs
CHANGED
|
@@ -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
|
}
|
package/claude-pack.config.json
CHANGED
|
@@ -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
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
|
|
268
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
}
|