ccsetup 1.1.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 +144 -342
- package/bin/create-project.js +1246 -90
- package/bin/lib/claudeInterface.js +209 -0
- package/lib/aiAgentSelector.js +155 -0
- package/lib/templates/README.md +176 -0
- package/lib/templates/catalog.js +230 -0
- package/lib/templates/filter.js +257 -0
- package/lib/templates/index.js +45 -0
- package/lib/templates/metadata/agents.json +413 -0
- package/lib/templates/metadata-extractor.js +329 -0
- package/lib/templates/search.js +356 -0
- package/package.json +13 -5
- package/template/{agents → .claude/agents}/checker.md +29 -0
- package/template/.claude/settings.json +32 -0
- package/template/.claude/skills/codex-review/SKILL.md +139 -0
- package/template/.claude/skills/prd/SKILL.md +343 -0
- package/template/.claude/skills/ralph/SKILL.md +339 -0
- package/template/.claude/skills/secops/SKILL.md +259 -0
- package/template/.codex/skills/codex-review/SKILL.md +139 -0
- package/template/.codex/skills/prd/SKILL.md +343 -0
- package/template/.codex/skills/ralph/SKILL.md +339 -0
- package/template/AGENTS.md +43 -0
- package/template/CLAUDE.md +141 -21
- package/template/CONTRIBUTING.md +37 -0
- package/template/agents/README.md +15 -171
- package/template/docs/ROADMAP.md +0 -36
- package/template/docs/agent-orchestration.md +24 -141
- package/template/docs/codex-setup.md +32 -0
- package/template/hooks/codex-review/index.js +105 -0
- package/template/hooks/workflow-selector/index.js +398 -0
- package/template/scripts/codex-review/codex-review.sh +266 -0
- package/template/scripts/ralph/CLAUDE.md +174 -0
- package/template/scripts/ralph/CODEX.md +76 -0
- package/template/scripts/ralph/ralph.sh +150 -0
- package/template/tickets/ticket-list.md +17 -68
- package/template/agents/ai-engineer.md +0 -31
- package/template/agents/api-documenter.md +0 -31
- package/template/agents/architect-review.md +0 -42
- package/template/agents/backend-architect.md +0 -29
- package/template/agents/business-analyst.md +0 -34
- package/template/agents/c-pro.md +0 -34
- package/template/agents/cloud-architect.md +0 -31
- package/template/agents/code-reviewer.md +0 -28
- package/template/agents/content-marketer.md +0 -34
- package/template/agents/context-manager.md +0 -63
- package/template/agents/cpp-pro.md +0 -37
- package/template/agents/customer-support.md +0 -34
- package/template/agents/data-engineer.md +0 -31
- package/template/agents/data-scientist.md +0 -28
- package/template/agents/database-admin.md +0 -31
- package/template/agents/database-optimizer.md +0 -31
- package/template/agents/debugger.md +0 -29
- package/template/agents/deployment-engineer.md +0 -31
- package/template/agents/devops-troubleshooter.md +0 -31
- package/template/agents/dx-optimizer.md +0 -62
- package/template/agents/error-detective.md +0 -31
- package/template/agents/frontend-developer.md +0 -30
- package/template/agents/golang-pro.md +0 -31
- package/template/agents/graphql-architect.md +0 -31
- package/template/agents/incident-responder.md +0 -73
- package/template/agents/javascript-pro.md +0 -34
- package/template/agents/legacy-modernizer.md +0 -31
- package/template/agents/ml-engineer.md +0 -31
- package/template/agents/mlops-engineer.md +0 -56
- package/template/agents/mobile-developer.md +0 -31
- package/template/agents/network-engineer.md +0 -31
- package/template/agents/payment-integration.md +0 -31
- package/template/agents/performance-engineer.md +0 -31
- package/template/agents/prompt-engineer.md +0 -58
- package/template/agents/python-pro.md +0 -31
- package/template/agents/quant-analyst.md +0 -31
- package/template/agents/risk-manager.md +0 -40
- package/template/agents/rust-pro.md +0 -34
- package/template/agents/sales-automator.md +0 -34
- package/template/agents/search-specialist.md +0 -58
- package/template/agents/security-auditor.md +0 -31
- package/template/agents/sql-pro.md +0 -34
- package/template/agents/terraform-specialist.md +0 -34
- package/template/agents/test-automator.md +0 -31
- /package/template/{agents → .claude/agents}/backend.md +0 -0
- /package/template/{agents → .claude/agents}/blockchain.md +0 -0
- /package/template/{agents → .claude/agents}/coder.md +0 -0
- /package/template/{agents → .claude/agents}/frontend.md +0 -0
- /package/template/{agents → .claude/agents}/planner.md +0 -0
- /package/template/{agents → .claude/agents}/researcher.md +0 -0
- /package/template/{agents → .claude/agents}/shadcn.md +0 -0
package/bin/create-project.js
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
|
+
const TemplateCatalog = require('../lib/templates/catalog');
|
|
7
|
+
const TemplateFilter = require('../lib/templates/filter');
|
|
8
|
+
const TemplateSearch = require('../lib/templates/search');
|
|
6
9
|
|
|
7
10
|
// Parse CLI arguments
|
|
8
11
|
const args = process.argv.slice(2);
|
|
12
|
+
|
|
9
13
|
const flags = {
|
|
10
14
|
force: false,
|
|
11
15
|
dryRun: false,
|
|
@@ -13,7 +17,9 @@ const flags = {
|
|
|
13
17
|
allAgents: false,
|
|
14
18
|
noAgents: false,
|
|
15
19
|
agents: false,
|
|
16
|
-
browseAgents: false
|
|
20
|
+
browseAgents: false,
|
|
21
|
+
browse: false,
|
|
22
|
+
prompt: null
|
|
17
23
|
};
|
|
18
24
|
|
|
19
25
|
let projectName = '.';
|
|
@@ -35,6 +41,10 @@ for (let i = 0; i < args.length; i++) {
|
|
|
35
41
|
flags.agents = true;
|
|
36
42
|
} else if (arg === '--browse-agents') {
|
|
37
43
|
flags.browseAgents = true;
|
|
44
|
+
} else if (arg === '--browse') {
|
|
45
|
+
flags.browse = true;
|
|
46
|
+
} else if (arg === '--install-hooks') {
|
|
47
|
+
flags.installHooks = true;
|
|
38
48
|
} else if (!arg.startsWith('-')) {
|
|
39
49
|
projectName = arg;
|
|
40
50
|
}
|
|
@@ -45,23 +55,27 @@ if (flags.help) {
|
|
|
45
55
|
console.log(`
|
|
46
56
|
Usage: ccsetup [project-name] [options]
|
|
47
57
|
|
|
58
|
+
Commands:
|
|
59
|
+
ccsetup Interactive mode
|
|
60
|
+
ccsetup <name> Create a new Claude Code project
|
|
61
|
+
|
|
48
62
|
Options:
|
|
49
|
-
--force, -f
|
|
50
|
-
--dry-run, -d
|
|
51
|
-
--agents
|
|
52
|
-
--all-agents
|
|
53
|
-
--no-agents
|
|
54
|
-
--browse-agents
|
|
55
|
-
--
|
|
63
|
+
--force, -f Skip all prompts and overwrite existing files
|
|
64
|
+
--dry-run, -d Show what would be done without making changes
|
|
65
|
+
--agents Interactive agent selection mode
|
|
66
|
+
--all-agents Include all agents without prompting
|
|
67
|
+
--no-agents Skip agent selection entirely
|
|
68
|
+
--browse-agents Copy all agents to /agents folder for browsing
|
|
69
|
+
--browse Enhanced template browsing and selection interface
|
|
70
|
+
--help, -h Show this help message
|
|
71
|
+
|
|
72
|
+
Advanced:
|
|
73
|
+
--install-hooks Install workflow selection hook to .claude/hooks (optional, power users only)
|
|
56
74
|
|
|
57
75
|
Examples:
|
|
58
|
-
ccsetup
|
|
59
|
-
ccsetup my-project
|
|
60
|
-
ccsetup
|
|
61
|
-
ccsetup my-app --dry-run # Preview changes without creating files
|
|
62
|
-
ccsetup --agents # Interactive agent selection only
|
|
63
|
-
ccsetup my-app --all-agents # Include all agents automatically
|
|
64
|
-
ccsetup --browse-agents # Copy all agents for manual selection
|
|
76
|
+
npx ccsetup # Interactive setup in current directory
|
|
77
|
+
npx ccsetup my-project # Create in new directory
|
|
78
|
+
npx ccsetup my-app --all-agents # Include all agents automatically
|
|
65
79
|
`);
|
|
66
80
|
process.exit(0);
|
|
67
81
|
}
|
|
@@ -83,16 +97,26 @@ function validateProjectName(name) {
|
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
// Validate conflicting flags
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
function validateFlags() {
|
|
101
|
+
// Existing validations
|
|
102
|
+
if (flags.allAgents && flags.noAgents) {
|
|
103
|
+
console.error('Error: Cannot use --all-agents and --no-agents together');
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
90
106
|
|
|
91
|
-
if (flags.browseAgents && (flags.allAgents || flags.noAgents || flags.agents)) {
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
if (flags.browseAgents && (flags.allAgents || flags.noAgents || flags.agents || flags.browse)) {
|
|
108
|
+
console.error('Error: --browse-agents cannot be used with other agent flags');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (flags.browse && (flags.allAgents || flags.noAgents || flags.browseAgents)) {
|
|
113
|
+
console.error('Error: --browse cannot be used with other agent flags except --agents');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
94
116
|
}
|
|
95
117
|
|
|
118
|
+
validateFlags();
|
|
119
|
+
|
|
96
120
|
// Validate the project name
|
|
97
121
|
if (projectName !== '.') {
|
|
98
122
|
try {
|
|
@@ -105,6 +129,7 @@ if (projectName !== '.') {
|
|
|
105
129
|
|
|
106
130
|
const targetDir = path.resolve(process.cwd(), projectName);
|
|
107
131
|
const templateDir = path.join(__dirname, '..', 'template');
|
|
132
|
+
const VALID_AI_PROVIDERS = new Set(['claude', 'codex', 'both']);
|
|
108
133
|
|
|
109
134
|
// Create readline interface only if needed
|
|
110
135
|
let rl = null;
|
|
@@ -155,6 +180,72 @@ function validateAgentFile(file) {
|
|
|
155
180
|
return true;
|
|
156
181
|
}
|
|
157
182
|
|
|
183
|
+
function includesClaude(provider) {
|
|
184
|
+
return provider === 'claude' || provider === 'both';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function includesCodex(provider) {
|
|
188
|
+
return provider === 'codex' || provider === 'both';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getEnvBoolean(name) {
|
|
192
|
+
const value = process.env[name];
|
|
193
|
+
if (value == null || value === '') return null;
|
|
194
|
+
|
|
195
|
+
const normalized = value.toLowerCase().trim();
|
|
196
|
+
if (['1', 'true', 'yes', 'y'].includes(normalized)) return true;
|
|
197
|
+
if (['0', 'false', 'no', 'n'].includes(normalized)) return false;
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getConfiguredAiProvider() {
|
|
202
|
+
const envValue = process.env.CCSETUP_AI_PROVIDER;
|
|
203
|
+
if (!envValue) return null;
|
|
204
|
+
|
|
205
|
+
const normalized = envValue.toLowerCase().trim();
|
|
206
|
+
if (!VALID_AI_PROVIDERS.has(normalized)) {
|
|
207
|
+
throw new Error(`Invalid CCSETUP_AI_PROVIDER value: ${envValue}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return normalized;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function selectAiProvider() {
|
|
214
|
+
const envProvider = getConfiguredAiProvider();
|
|
215
|
+
if (envProvider) {
|
|
216
|
+
return envProvider;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (flags.force || flags.dryRun) {
|
|
220
|
+
return 'claude';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const selectModule = await import('@inquirer/select');
|
|
224
|
+
const select = selectModule.default;
|
|
225
|
+
|
|
226
|
+
return select({
|
|
227
|
+
message: 'Which AI setup would you like to generate for this project?',
|
|
228
|
+
choices: [
|
|
229
|
+
{
|
|
230
|
+
name: 'Claude Code',
|
|
231
|
+
value: 'claude',
|
|
232
|
+
description: 'Copy CLAUDE.md and the .claude project setup'
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'Codex CLI',
|
|
236
|
+
value: 'codex',
|
|
237
|
+
description: 'Copy AGENTS.md plus project-local Codex skills'
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'Both',
|
|
241
|
+
value: 'both',
|
|
242
|
+
description: 'Copy both Claude Code and Codex project assets'
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
default: 'claude'
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
158
249
|
// Function to check if Claude Code is installed
|
|
159
250
|
function checkClaudeCode() {
|
|
160
251
|
const claudeDir = path.join(targetDir, '.claude');
|
|
@@ -254,6 +345,23 @@ async function initializeClaudeDirectory(selectedAgentFiles, conflictStrategy, d
|
|
|
254
345
|
}
|
|
255
346
|
}
|
|
256
347
|
|
|
348
|
+
// Copy .claude/settings.json
|
|
349
|
+
const settingsSrc = path.join(templateClaudeDir, 'settings.json');
|
|
350
|
+
const settingsDest = path.join(claudeDir, 'settings.json');
|
|
351
|
+
if (fs.existsSync(settingsSrc)) {
|
|
352
|
+
if (!fs.existsSync(settingsDest)) {
|
|
353
|
+
if (!dryRun) {
|
|
354
|
+
fs.copyFileSync(settingsSrc, settingsDest);
|
|
355
|
+
}
|
|
356
|
+
createdItems.push('.claude/settings.json');
|
|
357
|
+
if (dryRun) {
|
|
358
|
+
console.log(' ✨ Would copy: .claude/settings.json');
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
skippedItems.push('.claude/settings.json');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
257
365
|
// Copy .claude/agents/README.md
|
|
258
366
|
const agentsReadmeSrc = path.join(templateClaudeDir, 'agents', 'README.md');
|
|
259
367
|
const agentsReadmeDest = path.join(claudeAgentsDir, 'README.md');
|
|
@@ -303,8 +411,68 @@ async function initializeClaudeDirectory(selectedAgentFiles, conflictStrategy, d
|
|
|
303
411
|
}
|
|
304
412
|
}
|
|
305
413
|
|
|
414
|
+
// Copy .claude/skills/ directory (recursively)
|
|
415
|
+
const templateSkillsDir = path.join(templateClaudeDir, 'skills');
|
|
416
|
+
if (fs.existsSync(templateSkillsDir)) {
|
|
417
|
+
const claudeSkillsDir = path.join(claudeDir, 'skills');
|
|
418
|
+
const skillDirs = fs.readdirSync(templateSkillsDir).filter(d => {
|
|
419
|
+
return fs.statSync(path.join(templateSkillsDir, d)).isDirectory();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
for (const skillName of skillDirs) {
|
|
423
|
+
const skillSrcDir = path.join(templateSkillsDir, skillName);
|
|
424
|
+
const skillDestDir = path.join(claudeSkillsDir, skillName);
|
|
425
|
+
const skillFile = path.join(skillSrcDir, 'SKILL.md');
|
|
426
|
+
|
|
427
|
+
if (fs.existsSync(skillFile)) {
|
|
428
|
+
if (!fs.existsSync(path.join(skillDestDir, 'SKILL.md'))) {
|
|
429
|
+
if (!dryRun) {
|
|
430
|
+
fs.mkdirSync(skillDestDir, { recursive: true });
|
|
431
|
+
fs.copyFileSync(skillFile, path.join(skillDestDir, 'SKILL.md'));
|
|
432
|
+
}
|
|
433
|
+
createdItems.push(`.claude/skills/${skillName}/SKILL.md`);
|
|
434
|
+
if (dryRun) {
|
|
435
|
+
console.log(` ✨ Would copy: .claude/skills/${skillName}/SKILL.md`);
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
skippedItems.push(`.claude/skills/${skillName}/SKILL.md`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Copy template/hooks/ to .claude/hooks/ (all hook directories)
|
|
445
|
+
const templateHooksDir = path.join(templateDir, 'hooks');
|
|
446
|
+
if (fs.existsSync(templateHooksDir)) {
|
|
447
|
+
const claudeHooksDir = path.join(claudeDir, 'hooks');
|
|
448
|
+
const hookDirs = fs.readdirSync(templateHooksDir).filter(d => {
|
|
449
|
+
return fs.statSync(path.join(templateHooksDir, d)).isDirectory();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
for (const hookName of hookDirs) {
|
|
453
|
+
const hookSrcDir = path.join(templateHooksDir, hookName);
|
|
454
|
+
const hookDestDir = path.join(claudeHooksDir, hookName);
|
|
455
|
+
const hookFile = path.join(hookSrcDir, 'index.js');
|
|
456
|
+
|
|
457
|
+
if (fs.existsSync(hookFile)) {
|
|
458
|
+
if (!fs.existsSync(path.join(hookDestDir, 'index.js'))) {
|
|
459
|
+
if (!dryRun) {
|
|
460
|
+
fs.mkdirSync(hookDestDir, { recursive: true });
|
|
461
|
+
fs.copyFileSync(hookFile, path.join(hookDestDir, 'index.js'));
|
|
462
|
+
}
|
|
463
|
+
createdItems.push(`.claude/hooks/${hookName}/index.js`);
|
|
464
|
+
if (dryRun) {
|
|
465
|
+
console.log(` ✨ Would copy: .claude/hooks/${hookName}/index.js`);
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
skippedItems.push(`.claude/hooks/${hookName}/index.js`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
306
474
|
// Copy selected agents to .claude/agents
|
|
307
|
-
const templateAgentsDir = path.join(templateDir, 'agents');
|
|
475
|
+
const templateAgentsDir = path.join(templateDir, '.claude', 'agents');
|
|
308
476
|
let copiedAgents = 0;
|
|
309
477
|
let skippedAgents = 0;
|
|
310
478
|
|
|
@@ -391,6 +559,126 @@ async function initializeClaudeDirectory(selectedAgentFiles, conflictStrategy, d
|
|
|
391
559
|
}
|
|
392
560
|
}
|
|
393
561
|
|
|
562
|
+
async function initializeCodexDirectory(conflictStrategy, dryRun) {
|
|
563
|
+
const codexDir = path.join(targetDir, '.codex');
|
|
564
|
+
const skillsDir = path.join(codexDir, 'skills');
|
|
565
|
+
const templateCodexDir = path.join(templateDir, '.codex');
|
|
566
|
+
const createdItems = [];
|
|
567
|
+
const skippedItems = [];
|
|
568
|
+
|
|
569
|
+
function copyCodexFile(src, dest, relativePath) {
|
|
570
|
+
if (!fs.existsSync(src)) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (!fs.existsSync(dest)) {
|
|
575
|
+
if (!dryRun) {
|
|
576
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
577
|
+
fs.copyFileSync(src, dest);
|
|
578
|
+
}
|
|
579
|
+
createdItems.push(relativePath);
|
|
580
|
+
if (dryRun) {
|
|
581
|
+
console.log(` ✨ Would copy: ${relativePath}`);
|
|
582
|
+
}
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (conflictStrategy === 'overwrite') {
|
|
587
|
+
if (!dryRun) {
|
|
588
|
+
fs.copyFileSync(src, dest);
|
|
589
|
+
}
|
|
590
|
+
if (dryRun) {
|
|
591
|
+
console.log(` ♻️ Would replace: ${relativePath}`);
|
|
592
|
+
}
|
|
593
|
+
createdItems.push(relativePath);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (conflictStrategy === 'rename') {
|
|
598
|
+
const ext = path.extname(dest);
|
|
599
|
+
const baseName = path.basename(dest, ext);
|
|
600
|
+
const dirName = path.dirname(dest);
|
|
601
|
+
let newDest = path.join(dirName, `${baseName}-ccsetup${ext}`);
|
|
602
|
+
let counter = 1;
|
|
603
|
+
while (fs.existsSync(newDest)) {
|
|
604
|
+
newDest = path.join(dirName, `${baseName}-ccsetup-${counter}${ext}`);
|
|
605
|
+
counter++;
|
|
606
|
+
}
|
|
607
|
+
if (!dryRun) {
|
|
608
|
+
fs.copyFileSync(src, newDest);
|
|
609
|
+
}
|
|
610
|
+
createdItems.push(path.relative(targetDir, newDest));
|
|
611
|
+
if (dryRun) {
|
|
612
|
+
console.log(` 📄 Would create: ${path.relative(targetDir, newDest)}`);
|
|
613
|
+
}
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
skippedItems.push(relativePath);
|
|
618
|
+
if (dryRun) {
|
|
619
|
+
console.log(` ⏭️ Would skip: ${relativePath}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
if (!fs.existsSync(codexDir)) {
|
|
625
|
+
if (!dryRun) {
|
|
626
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
627
|
+
}
|
|
628
|
+
createdItems.push('.codex/');
|
|
629
|
+
if (dryRun) {
|
|
630
|
+
console.log(' 📁 Would create directory: .codex/');
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
skippedItems.push('.codex/');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (!fs.existsSync(skillsDir)) {
|
|
637
|
+
if (!dryRun) {
|
|
638
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
639
|
+
}
|
|
640
|
+
createdItems.push('.codex/skills/');
|
|
641
|
+
if (dryRun) {
|
|
642
|
+
console.log(' 📁 Would create directory: .codex/skills/');
|
|
643
|
+
}
|
|
644
|
+
} else {
|
|
645
|
+
skippedItems.push('.codex/skills/');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const templateSkillsDir = path.join(templateCodexDir, 'skills');
|
|
649
|
+
if (fs.existsSync(templateSkillsDir)) {
|
|
650
|
+
const skillDirs = fs.readdirSync(templateSkillsDir, { withFileTypes: true }).filter(entry => entry.isDirectory());
|
|
651
|
+
for (const entry of skillDirs) {
|
|
652
|
+
const srcDir = path.join(templateSkillsDir, entry.name);
|
|
653
|
+
const skillFileSrc = path.join(srcDir, 'SKILL.md');
|
|
654
|
+
|
|
655
|
+
if (!fs.existsSync(skillFileSrc)) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const destDir = path.join(skillsDir, entry.name);
|
|
660
|
+
const skillFileDest = path.join(destDir, 'SKILL.md');
|
|
661
|
+
|
|
662
|
+
if (!fs.existsSync(destDir)) {
|
|
663
|
+
if (!dryRun) {
|
|
664
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
665
|
+
}
|
|
666
|
+
createdItems.push(`.codex/skills/${entry.name}/`);
|
|
667
|
+
if (dryRun) {
|
|
668
|
+
console.log(` 📁 Would create directory: .codex/skills/${entry.name}/`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
copyCodexFile(skillFileSrc, skillFileDest, `.codex/skills/${entry.name}/SKILL.md`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
return { createdItems, skippedItems };
|
|
677
|
+
} catch (error) {
|
|
678
|
+
throw new Error(`Failed to initialize .codex directory: ${error.message}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
394
682
|
// Function to parse agent frontmatter
|
|
395
683
|
function parseAgentFrontmatter(filePath) {
|
|
396
684
|
try {
|
|
@@ -426,7 +714,7 @@ function parseAgentFrontmatter(filePath) {
|
|
|
426
714
|
|
|
427
715
|
// Function to get available agents
|
|
428
716
|
function getAvailableAgents() {
|
|
429
|
-
const agentsDir = path.join(templateDir, 'agents');
|
|
717
|
+
const agentsDir = path.join(templateDir, '.claude', 'agents');
|
|
430
718
|
const agents = [];
|
|
431
719
|
|
|
432
720
|
try {
|
|
@@ -451,6 +739,274 @@ function getAvailableAgents() {
|
|
|
451
739
|
return agents.sort((a, b) => a.name.localeCompare(b.name));
|
|
452
740
|
}
|
|
453
741
|
|
|
742
|
+
// Install Claude Code hooks
|
|
743
|
+
async function installClaudeHooks() {
|
|
744
|
+
console.log('\n🪝 Installing Claude Code Workflow Selection Hook...\n');
|
|
745
|
+
|
|
746
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
747
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
748
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
749
|
+
|
|
750
|
+
// Check if .claude directory exists
|
|
751
|
+
if (!fs.existsSync(claudeDir)) {
|
|
752
|
+
console.error('❌ Error: .claude directory not found.');
|
|
753
|
+
console.log('\n💡 Please run "claude init" first to initialize Claude Code in this directory.\n');
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
try {
|
|
758
|
+
// Create hooks directory
|
|
759
|
+
if (!fs.existsSync(hooksDir)) {
|
|
760
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
761
|
+
console.log('✅ Created .claude/hooks directory');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Copy workflow-selector hook
|
|
765
|
+
const hookSourceDir = path.join(templateDir, 'hooks', 'workflow-selector');
|
|
766
|
+
const hookDestDir = path.join(hooksDir, 'workflow-selector');
|
|
767
|
+
|
|
768
|
+
if (!fs.existsSync(hookSourceDir)) {
|
|
769
|
+
console.error('❌ Error: Hook source files not found in template.');
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Check if hook already exists
|
|
774
|
+
const hookFile = path.join(hookSourceDir, 'index.js');
|
|
775
|
+
const destFile = path.join(hookDestDir, 'index.js');
|
|
776
|
+
let shouldCopyHook = true;
|
|
777
|
+
|
|
778
|
+
if (fs.existsSync(destFile)) {
|
|
779
|
+
console.log('⚠️ Workflow-selector hook already exists.');
|
|
780
|
+
const selectModule = await import('@inquirer/select');
|
|
781
|
+
const select = selectModule.default;
|
|
782
|
+
|
|
783
|
+
const action = await select({
|
|
784
|
+
message: 'How would you like to proceed?',
|
|
785
|
+
choices: [
|
|
786
|
+
{
|
|
787
|
+
name: '📋 Keep existing - Preserve your customizations',
|
|
788
|
+
value: 'keep',
|
|
789
|
+
description: 'Keep your existing hook file unchanged'
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
name: '🔄 Update - Replace with latest version',
|
|
793
|
+
value: 'replace',
|
|
794
|
+
description: 'Replace with the latest hook (backup will be created)'
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: '👀 Compare - View differences first',
|
|
798
|
+
value: 'compare',
|
|
799
|
+
description: 'Compare existing and new versions before deciding'
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
name: '❌ Skip - Cancel hook installation',
|
|
803
|
+
value: 'skip',
|
|
804
|
+
description: 'Skip installing the hook file'
|
|
805
|
+
}
|
|
806
|
+
]
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
if (action === 'skip') {
|
|
810
|
+
console.log('⏭️ Skipped hook file installation');
|
|
811
|
+
shouldCopyHook = false;
|
|
812
|
+
} else if (action === 'keep') {
|
|
813
|
+
console.log('✅ Keeping existing hook file');
|
|
814
|
+
shouldCopyHook = false;
|
|
815
|
+
} else if (action === 'compare') {
|
|
816
|
+
// Show comparison
|
|
817
|
+
console.log('\n📄 Existing hook file summary:');
|
|
818
|
+
const existingContent = fs.readFileSync(destFile, 'utf8');
|
|
819
|
+
const existingLines = existingContent.split('\n').length;
|
|
820
|
+
console.log(` Lines: ${existingLines}`);
|
|
821
|
+
console.log(` Size: ${fs.statSync(destFile).size} bytes`);
|
|
822
|
+
console.log(` Modified: ${fs.statSync(destFile).mtime.toLocaleString()}`);
|
|
823
|
+
|
|
824
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
825
|
+
const confirm = confirmModule.default;
|
|
826
|
+
const shouldReplace = await confirm({
|
|
827
|
+
message: 'Replace with new version?',
|
|
828
|
+
default: false
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
if (shouldReplace) {
|
|
832
|
+
// Create backup
|
|
833
|
+
const backupFile = destFile + '.backup-' + Date.now();
|
|
834
|
+
fs.copyFileSync(destFile, backupFile);
|
|
835
|
+
console.log(`✅ Created backup: ${path.basename(backupFile)}`);
|
|
836
|
+
shouldCopyHook = true;
|
|
837
|
+
} else {
|
|
838
|
+
shouldCopyHook = false;
|
|
839
|
+
}
|
|
840
|
+
} else if (action === 'replace') {
|
|
841
|
+
// Create backup
|
|
842
|
+
const backupFile = destFile + '.backup-' + Date.now();
|
|
843
|
+
fs.copyFileSync(destFile, backupFile);
|
|
844
|
+
console.log(`✅ Created backup: ${path.basename(backupFile)}`);
|
|
845
|
+
shouldCopyHook = true;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Create workflow-selector directory if needed
|
|
850
|
+
if (!fs.existsSync(hookDestDir)) {
|
|
851
|
+
fs.mkdirSync(hookDestDir, { recursive: true });
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Copy hook file if needed
|
|
855
|
+
if (shouldCopyHook) {
|
|
856
|
+
fs.copyFileSync(hookFile, destFile);
|
|
857
|
+
console.log('✅ Installed workflow-selector hook');
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Update settings.json
|
|
861
|
+
let settings = {};
|
|
862
|
+
if (fs.existsSync(settingsFile)) {
|
|
863
|
+
const content = fs.readFileSync(settingsFile, 'utf8');
|
|
864
|
+
try {
|
|
865
|
+
settings = JSON.parse(content);
|
|
866
|
+
} catch (e) {
|
|
867
|
+
console.warn('⚠️ Warning: Could not parse existing settings.json, creating new one');
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Add hook configuration intelligently
|
|
872
|
+
if (!settings.hooks) {
|
|
873
|
+
settings.hooks = {};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Check if UserPromptSubmit hooks already exist
|
|
877
|
+
const workflowHookCommand = "node $CLAUDE_PROJECT_DIR/.claude/hooks/workflow-selector/index.js";
|
|
878
|
+
let hookExists = false;
|
|
879
|
+
|
|
880
|
+
if (settings.hooks.UserPromptSubmit && Array.isArray(settings.hooks.UserPromptSubmit)) {
|
|
881
|
+
// Check if our hook is already configured
|
|
882
|
+
hookExists = settings.hooks.UserPromptSubmit.some(hookConfig =>
|
|
883
|
+
hookConfig.hooks && hookConfig.hooks.some(hook =>
|
|
884
|
+
hook.type === 'command' && hook.command === workflowHookCommand
|
|
885
|
+
)
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
if (hookExists) {
|
|
889
|
+
console.log('✅ Workflow hook already configured in settings.json');
|
|
890
|
+
} else {
|
|
891
|
+
// Ask user how to proceed
|
|
892
|
+
const selectModule = await import('@inquirer/select');
|
|
893
|
+
const select = selectModule.default;
|
|
894
|
+
|
|
895
|
+
console.log('\n⚠️ Existing UserPromptSubmit hooks detected in settings.json');
|
|
896
|
+
const action = await select({
|
|
897
|
+
message: 'How would you like to add the workflow hook?',
|
|
898
|
+
choices: [
|
|
899
|
+
{
|
|
900
|
+
name: '➕ Add to existing - Preserve current hooks and add workflow hook',
|
|
901
|
+
value: 'add',
|
|
902
|
+
description: 'Keep all existing hooks and add the workflow hook'
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
name: '🔄 Replace all - Replace existing hooks with workflow hook',
|
|
906
|
+
value: 'replace',
|
|
907
|
+
description: 'Replace all existing hooks (backup will be created)'
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
name: '❌ Skip - Don\'t modify hooks configuration',
|
|
911
|
+
value: 'skip',
|
|
912
|
+
description: 'Keep settings.json unchanged'
|
|
913
|
+
}
|
|
914
|
+
]
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
if (action === 'add') {
|
|
918
|
+
// Add our hook to existing array
|
|
919
|
+
settings.hooks.UserPromptSubmit.push({
|
|
920
|
+
"matcher": ".*",
|
|
921
|
+
"hooks": [
|
|
922
|
+
{
|
|
923
|
+
"type": "command",
|
|
924
|
+
"command": workflowHookCommand
|
|
925
|
+
}
|
|
926
|
+
]
|
|
927
|
+
});
|
|
928
|
+
console.log('✅ Added workflow hook to existing hooks');
|
|
929
|
+
} else if (action === 'replace') {
|
|
930
|
+
// Backup existing settings
|
|
931
|
+
const backupFile = settingsFile + '.backup-' + Date.now();
|
|
932
|
+
fs.writeFileSync(backupFile, JSON.stringify(settings, null, 2));
|
|
933
|
+
console.log(`✅ Created settings backup: ${path.basename(backupFile)}`);
|
|
934
|
+
|
|
935
|
+
// Replace with our hook
|
|
936
|
+
settings.hooks.UserPromptSubmit = [
|
|
937
|
+
{
|
|
938
|
+
"matcher": ".*",
|
|
939
|
+
"hooks": [
|
|
940
|
+
{
|
|
941
|
+
"type": "command",
|
|
942
|
+
"command": workflowHookCommand
|
|
943
|
+
}
|
|
944
|
+
]
|
|
945
|
+
}
|
|
946
|
+
];
|
|
947
|
+
console.log('✅ Replaced existing hooks with workflow hook');
|
|
948
|
+
} else {
|
|
949
|
+
console.log('⏭️ Skipped settings.json modification');
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
} else {
|
|
954
|
+
// No existing UserPromptSubmit hooks, safe to add
|
|
955
|
+
settings.hooks.UserPromptSubmit = [
|
|
956
|
+
{
|
|
957
|
+
"matcher": ".*",
|
|
958
|
+
"hooks": [
|
|
959
|
+
{
|
|
960
|
+
"type": "command",
|
|
961
|
+
"command": workflowHookCommand
|
|
962
|
+
}
|
|
963
|
+
]
|
|
964
|
+
}
|
|
965
|
+
];
|
|
966
|
+
console.log('✅ Added workflow hook configuration');
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Write updated settings only if we made changes
|
|
970
|
+
if (!hookExists) {
|
|
971
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
972
|
+
console.log('✅ Updated .claude/settings.json');
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Copy agent-orchestration.md if it doesn't exist
|
|
976
|
+
const orchestrationSource = path.join(templateDir, 'docs', 'agent-orchestration.md');
|
|
977
|
+
const orchestrationDest = path.join(process.cwd(), 'docs', 'agent-orchestration.md');
|
|
978
|
+
|
|
979
|
+
if (!fs.existsSync(orchestrationDest) && fs.existsSync(orchestrationSource)) {
|
|
980
|
+
const docsDir = path.join(process.cwd(), 'docs');
|
|
981
|
+
if (!fs.existsSync(docsDir)) {
|
|
982
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
983
|
+
}
|
|
984
|
+
fs.copyFileSync(orchestrationSource, orchestrationDest);
|
|
985
|
+
console.log('✅ Copied agent-orchestration.md to docs/');
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
console.log('\n🎉 Workflow selection hook installed successfully!\n');
|
|
989
|
+
console.log('The hook will:');
|
|
990
|
+
console.log(' • Analyze your prompts to determine task type');
|
|
991
|
+
console.log(' • Suggest appropriate workflows (Feature Development, Bug Fix, etc.)');
|
|
992
|
+
console.log(' • Generate relevant todo items');
|
|
993
|
+
console.log(' • Guide agent selection based on the task\n');
|
|
994
|
+
console.log('📝 Note: The hook reads workflows from docs/agent-orchestration.md');
|
|
995
|
+
console.log('💡 You can disable the hook by editing .claude/settings.json\n');
|
|
996
|
+
|
|
997
|
+
} catch (error) {
|
|
998
|
+
console.error('❌ Error installing hooks:', error.message);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Setup mode selection function
|
|
1003
|
+
async function selectSetupMode() {
|
|
1004
|
+
if (flags.installHooks) return 'install-hooks';
|
|
1005
|
+
if (flags.agents) return 'agents-only';
|
|
1006
|
+
return 'full';
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
|
|
454
1010
|
// Dynamic import for ESM module
|
|
455
1011
|
async function importCheckbox() {
|
|
456
1012
|
try {
|
|
@@ -512,6 +1068,341 @@ async function selectAgents(availableAgents) {
|
|
|
512
1068
|
return validatedFiles;
|
|
513
1069
|
}
|
|
514
1070
|
|
|
1071
|
+
async function enhancedAgentSelection() {
|
|
1072
|
+
try {
|
|
1073
|
+
console.log('\n🔍 Loading template catalog...');
|
|
1074
|
+
const catalog = TemplateCatalog.createInstance();
|
|
1075
|
+
const templates = await catalog.load();
|
|
1076
|
+
|
|
1077
|
+
if (!templates.agents || templates.agents.length === 0) {
|
|
1078
|
+
console.log('❌ No agents found in catalog. Falling back to basic selection.');
|
|
1079
|
+
return await selectAgents(getAvailableAgents());
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
console.log(`✅ Found ${templates.agents.length} agents in catalog\n`);
|
|
1083
|
+
|
|
1084
|
+
// Import select for mode selection
|
|
1085
|
+
const selectModule = await import('@inquirer/select');
|
|
1086
|
+
const select = selectModule.default;
|
|
1087
|
+
|
|
1088
|
+
const mode = await select({
|
|
1089
|
+
message: 'How would you like to browse and select agents?',
|
|
1090
|
+
choices: [
|
|
1091
|
+
{
|
|
1092
|
+
name: '🔍 Search & Filter - Find agents by keywords and categories',
|
|
1093
|
+
value: 'search',
|
|
1094
|
+
description: 'Use search and filtering to find exactly what you need'
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
name: '📂 Browse by Category - Explore agents organized by type',
|
|
1098
|
+
value: 'category',
|
|
1099
|
+
description: 'Browse agents grouped by their specialization'
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
name: '🏷️ Browse by Tags - Find agents with specific capabilities',
|
|
1103
|
+
value: 'tags',
|
|
1104
|
+
description: 'Explore agents based on their tags and features'
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
name: '📋 Simple List - Traditional selection from all agents',
|
|
1108
|
+
value: 'simple',
|
|
1109
|
+
description: 'Classic checkbox selection from complete agent list'
|
|
1110
|
+
}
|
|
1111
|
+
]
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
let selectedAgents = [];
|
|
1115
|
+
|
|
1116
|
+
switch (mode) {
|
|
1117
|
+
case 'search':
|
|
1118
|
+
selectedAgents = await searchAndFilterSelection(templates.agents);
|
|
1119
|
+
break;
|
|
1120
|
+
case 'category':
|
|
1121
|
+
selectedAgents = await categoryBrowseSelection(templates.agents);
|
|
1122
|
+
break;
|
|
1123
|
+
case 'tags':
|
|
1124
|
+
selectedAgents = await tagBrowseSelection(templates.agents);
|
|
1125
|
+
break;
|
|
1126
|
+
case 'simple':
|
|
1127
|
+
default:
|
|
1128
|
+
selectedAgents = await simpleListSelection(templates.agents);
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
return selectedAgents.map(agent => agent.files[0]).filter(file => file && validateAgentFile(file));
|
|
1133
|
+
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
console.warn(`⚠️ Enhanced selection failed: ${error.message}`);
|
|
1136
|
+
console.log('Falling back to basic agent selection...\n');
|
|
1137
|
+
return await selectAgents(getAvailableAgents());
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
async function searchAndFilterSelection(agents) {
|
|
1142
|
+
const inputModule = await import('@inquirer/input');
|
|
1143
|
+
const input = inputModule.default;
|
|
1144
|
+
const selectModule = await import('@inquirer/select');
|
|
1145
|
+
const select = selectModule.default;
|
|
1146
|
+
const checkboxModule = await import('@inquirer/checkbox');
|
|
1147
|
+
const checkbox = checkboxModule.default;
|
|
1148
|
+
|
|
1149
|
+
console.log('\n🔍 Search & Filter Mode\n');
|
|
1150
|
+
|
|
1151
|
+
let currentResults = agents;
|
|
1152
|
+
const selectedAgents = [];
|
|
1153
|
+
|
|
1154
|
+
while (true) {
|
|
1155
|
+
console.log(`\n📊 Current results: ${currentResults.length} agents`);
|
|
1156
|
+
|
|
1157
|
+
const action = await select({
|
|
1158
|
+
message: 'What would you like to do?',
|
|
1159
|
+
choices: [
|
|
1160
|
+
{ name: '🔍 Search by keyword', value: 'search' },
|
|
1161
|
+
{ name: '📂 Filter by category', value: 'category' },
|
|
1162
|
+
{ name: '🏷️ Filter by tags', value: 'tags' },
|
|
1163
|
+
{ name: '📋 Select from current results', value: 'select' },
|
|
1164
|
+
{ name: '🔄 Reset filters', value: 'reset' },
|
|
1165
|
+
{ name: '✅ Finish selection', value: 'done' }
|
|
1166
|
+
]
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
if (action === 'done') break;
|
|
1170
|
+
|
|
1171
|
+
if (action === 'reset') {
|
|
1172
|
+
currentResults = agents;
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
if (action === 'search') {
|
|
1177
|
+
const query = await input({
|
|
1178
|
+
message: 'Enter search terms:',
|
|
1179
|
+
validate: (input) => input.trim().length > 0 ? true : 'Please enter a search term'
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
const search = new TemplateSearch(currentResults);
|
|
1183
|
+
currentResults = search.search(query.trim()).getResults();
|
|
1184
|
+
console.log(`Found ${currentResults.length} agents matching "${query}"`);
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
if (action === 'category') {
|
|
1189
|
+
const filter = new TemplateFilter(currentResults);
|
|
1190
|
+
const categories = filter.getSubcategories();
|
|
1191
|
+
|
|
1192
|
+
if (categories.length === 0) {
|
|
1193
|
+
console.log('No categories available in current results.');
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const category = await select({
|
|
1198
|
+
message: 'Select category:',
|
|
1199
|
+
choices: [
|
|
1200
|
+
{ name: 'All categories', value: 'all' },
|
|
1201
|
+
...categories.map(cat => ({ name: cat, value: cat }))
|
|
1202
|
+
]
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
currentResults = filter.byCategory(category).getResults();
|
|
1206
|
+
console.log(`Filtered to ${currentResults.length} agents in category "${category}"`);
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
if (action === 'tags') {
|
|
1211
|
+
const filter = new TemplateFilter(currentResults);
|
|
1212
|
+
const tagCloud = filter.getTagsByFrequency();
|
|
1213
|
+
|
|
1214
|
+
if (tagCloud.length === 0) {
|
|
1215
|
+
console.log('No tags available in current results.');
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const tags = await checkbox({
|
|
1220
|
+
message: 'Select tags to filter by:',
|
|
1221
|
+
choices: tagCloud.slice(0, 15).map(({ tag, count }) => ({
|
|
1222
|
+
name: `${tag} (${count})`,
|
|
1223
|
+
value: tag
|
|
1224
|
+
})),
|
|
1225
|
+
pageSize: 10
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
if (tags.length > 0) {
|
|
1229
|
+
currentResults = filter.byTags(tags).getResults();
|
|
1230
|
+
console.log(`Filtered to ${currentResults.length} agents with tags: ${tags.join(', ')}`);
|
|
1231
|
+
}
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (action === 'select') {
|
|
1236
|
+
if (currentResults.length === 0) {
|
|
1237
|
+
console.log('No agents in current results to select from.');
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const choices = currentResults.map(agent => ({
|
|
1242
|
+
name: `${agent.name}\n ${agent.description}`,
|
|
1243
|
+
value: agent,
|
|
1244
|
+
checked: selectedAgents.some(selected => selected.id === agent.id)
|
|
1245
|
+
}));
|
|
1246
|
+
|
|
1247
|
+
const newSelections = await checkbox({
|
|
1248
|
+
message: `Select agents (${currentResults.length} available):`,
|
|
1249
|
+
choices,
|
|
1250
|
+
pageSize: 8
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
// Update selected agents
|
|
1254
|
+
selectedAgents.length = 0;
|
|
1255
|
+
selectedAgents.push(...newSelections);
|
|
1256
|
+
|
|
1257
|
+
console.log(`Selected ${selectedAgents.length} agents`);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
return selectedAgents;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
async function categoryBrowseSelection(agents) {
|
|
1265
|
+
const selectModule = await import('@inquirer/select');
|
|
1266
|
+
const select = selectModule.default;
|
|
1267
|
+
const checkboxModule = await import('@inquirer/checkbox');
|
|
1268
|
+
const checkbox = checkboxModule.default;
|
|
1269
|
+
|
|
1270
|
+
console.log('\n📂 Category Browse Mode\n');
|
|
1271
|
+
|
|
1272
|
+
const filter = new TemplateFilter(agents);
|
|
1273
|
+
const categoryCatalog = {};
|
|
1274
|
+
|
|
1275
|
+
// Group agents by subcategory
|
|
1276
|
+
agents.forEach(agent => {
|
|
1277
|
+
if (!categoryCatalog[agent.subcategory]) {
|
|
1278
|
+
categoryCatalog[agent.subcategory] = [];
|
|
1279
|
+
}
|
|
1280
|
+
categoryCatalog[agent.subcategory].push(agent);
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
const categories = Object.keys(categoryCatalog).sort();
|
|
1284
|
+
|
|
1285
|
+
if (categories.length === 0) {
|
|
1286
|
+
console.log('No categories found. Using simple selection.');
|
|
1287
|
+
return await simpleListSelection(agents);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
console.log(`Found ${categories.length} categories:`);
|
|
1291
|
+
categories.forEach(cat => {
|
|
1292
|
+
console.log(` 📂 ${cat} (${categoryCatalog[cat].length} agents)`);
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
const selectedAgents = [];
|
|
1296
|
+
|
|
1297
|
+
while (true) {
|
|
1298
|
+
const action = await select({
|
|
1299
|
+
message: `Select a category to browse (${selectedAgents.length} agents selected):`,
|
|
1300
|
+
choices: [
|
|
1301
|
+
...categories.map(cat => ({
|
|
1302
|
+
name: `📂 ${cat} (${categoryCatalog[cat].length} agents)`,
|
|
1303
|
+
value: cat
|
|
1304
|
+
})),
|
|
1305
|
+
{ name: '✅ Finish selection', value: 'done' }
|
|
1306
|
+
]
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
if (action === 'done') break;
|
|
1310
|
+
|
|
1311
|
+
const categoryAgents = categoryCatalog[action];
|
|
1312
|
+
const choices = categoryAgents.map(agent => ({
|
|
1313
|
+
name: `${agent.name}\n ${agent.description}`,
|
|
1314
|
+
value: agent,
|
|
1315
|
+
checked: selectedAgents.some(selected => selected.id === agent.id)
|
|
1316
|
+
}));
|
|
1317
|
+
|
|
1318
|
+
const selections = await checkbox({
|
|
1319
|
+
message: `Select agents from ${action}:`,
|
|
1320
|
+
choices,
|
|
1321
|
+
pageSize: 8
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
// Update selected agents for this category
|
|
1325
|
+
selectedAgents = selectedAgents.filter(agent => agent.subcategory !== action);
|
|
1326
|
+
selectedAgents.push(...selections);
|
|
1327
|
+
|
|
1328
|
+
console.log(`Updated selection: ${selectedAgents.length} total agents selected`);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
return selectedAgents;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
async function tagBrowseSelection(agents) {
|
|
1335
|
+
const selectModule = await import('@inquirer/select');
|
|
1336
|
+
const select = selectModule.default;
|
|
1337
|
+
const checkboxModule = await import('@inquirer/checkbox');
|
|
1338
|
+
const checkbox = checkboxModule.default;
|
|
1339
|
+
|
|
1340
|
+
console.log('\n🏷️ Tag Browse Mode\n');
|
|
1341
|
+
|
|
1342
|
+
const filter = new TemplateFilter(agents);
|
|
1343
|
+
const tagCloud = filter.getTagsByFrequency();
|
|
1344
|
+
|
|
1345
|
+
if (tagCloud.length === 0) {
|
|
1346
|
+
console.log('No tags found. Using simple selection.');
|
|
1347
|
+
return await simpleListSelection(agents);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
console.log(`Found ${tagCloud.length} tags. Most popular:`);
|
|
1351
|
+
tagCloud.slice(0, 10).forEach(({ tag, count }) => {
|
|
1352
|
+
console.log(` 🏷️ ${tag} (${count} agents)`);
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
const selectedTags = await checkbox({
|
|
1356
|
+
message: 'Select tags to filter agents:',
|
|
1357
|
+
choices: tagCloud.map(({ tag, count }) => ({
|
|
1358
|
+
name: `${tag} (${count} agents)`,
|
|
1359
|
+
value: tag
|
|
1360
|
+
})),
|
|
1361
|
+
pageSize: 12
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
if (selectedTags.length === 0) {
|
|
1365
|
+
console.log('No tags selected. Using all agents.');
|
|
1366
|
+
return await simpleListSelection(agents);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const filteredAgents = filter.byTags(selectedTags).getResults();
|
|
1370
|
+
console.log(`\nFiltered to ${filteredAgents.length} agents with selected tags`);
|
|
1371
|
+
|
|
1372
|
+
if (filteredAgents.length === 0) {
|
|
1373
|
+
console.log('No agents match the selected tags.');
|
|
1374
|
+
return [];
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
return await simpleListSelection(filteredAgents);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
async function simpleListSelection(agents) {
|
|
1381
|
+
const checkboxModule = await import('@inquirer/checkbox');
|
|
1382
|
+
const checkbox = checkboxModule.default;
|
|
1383
|
+
|
|
1384
|
+
console.log('\n📋 Simple List Selection\n');
|
|
1385
|
+
|
|
1386
|
+
if (agents.length === 0) {
|
|
1387
|
+
console.log('No agents available for selection.');
|
|
1388
|
+
return [];
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
const choices = agents.map(agent => ({
|
|
1392
|
+
name: `${agent.name}\n ${agent.description}`,
|
|
1393
|
+
value: agent,
|
|
1394
|
+
checked: false
|
|
1395
|
+
}));
|
|
1396
|
+
|
|
1397
|
+
const selectedAgents = await checkbox({
|
|
1398
|
+
message: `Select agents (${agents.length} available):`,
|
|
1399
|
+
choices,
|
|
1400
|
+
pageSize: 10
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
return selectedAgents;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
515
1406
|
async function main() {
|
|
516
1407
|
try {
|
|
517
1408
|
// Ensure template directory exists
|
|
@@ -519,18 +1410,53 @@ async function main() {
|
|
|
519
1410
|
throw new Error(`Template directory not found: ${templateDir}`);
|
|
520
1411
|
}
|
|
521
1412
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1413
|
+
|
|
1414
|
+
// Add mode selection early (before agent selection)
|
|
1415
|
+
const setupMode = await selectSetupMode();
|
|
1416
|
+
|
|
1417
|
+
if (setupMode === 'install-hooks') {
|
|
1418
|
+
// Install Claude Code hooks
|
|
1419
|
+
await installClaudeHooks();
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (!flags.force && !flags.dryRun && rl) {
|
|
1424
|
+
rl.close();
|
|
1425
|
+
rl = null;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const aiProvider = await selectAiProvider();
|
|
1429
|
+
|
|
1430
|
+
// Handle --agents and --browse flags for agent selection only
|
|
1431
|
+
if (flags.agents || flags.browse) {
|
|
1432
|
+
if (!includesClaude(aiProvider)) {
|
|
1433
|
+
console.log('Claude agent selection is only available for Claude Code setup.');
|
|
1434
|
+
console.log('Re-run full setup to generate Codex assets.\n');
|
|
529
1435
|
process.exit(0);
|
|
530
1436
|
}
|
|
1437
|
+
|
|
1438
|
+
console.log('🤖 Interactive Agent Selection\n');
|
|
1439
|
+
|
|
1440
|
+
if (aiProvider === 'both') {
|
|
1441
|
+
console.log('This step configures Claude Code agents only. Codex assets are created during full setup.\n');
|
|
1442
|
+
}
|
|
531
1443
|
|
|
532
|
-
|
|
533
|
-
|
|
1444
|
+
let selectedAgentFiles = [];
|
|
1445
|
+
|
|
1446
|
+
if (flags.browse) {
|
|
1447
|
+
// Use enhanced selection interface
|
|
1448
|
+
selectedAgentFiles = await enhancedAgentSelection();
|
|
1449
|
+
} else {
|
|
1450
|
+
// Use traditional selection
|
|
1451
|
+
const availableAgents = getAvailableAgents();
|
|
1452
|
+
|
|
1453
|
+
if (availableAgents.length === 0) {
|
|
1454
|
+
console.log('No agents available for selection.');
|
|
1455
|
+
process.exit(0);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
selectedAgentFiles = await selectAgents(availableAgents);
|
|
1459
|
+
}
|
|
534
1460
|
|
|
535
1461
|
if (selectedAgentFiles.length === 0) {
|
|
536
1462
|
console.log('\n❌ No agents selected.');
|
|
@@ -548,13 +1474,20 @@ async function main() {
|
|
|
548
1474
|
console.log(`\n${colors.green}${colors.bold}✅ You selected ${selectedAgentFiles.length} agent${selectedAgentFiles.length === 1 ? '' : 's'}:${colors.reset}\n`);
|
|
549
1475
|
|
|
550
1476
|
// Show selected agents with descriptions
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1477
|
+
if (flags.browse) {
|
|
1478
|
+
// For enhanced selection, we already have agent data
|
|
1479
|
+
console.log(`${colors.dim}Selected files: ${selectedAgentFiles.join(', ')}${colors.reset}\n`);
|
|
1480
|
+
} else {
|
|
1481
|
+
// For traditional selection, show agent details
|
|
1482
|
+
const availableAgents = getAvailableAgents();
|
|
1483
|
+
selectedAgentFiles.forEach(file => {
|
|
1484
|
+
const agent = availableAgents.find(a => a.file === file);
|
|
1485
|
+
if (agent) {
|
|
1486
|
+
console.log(` ${colors.cyan}${colors.bold}${agent.name}${colors.reset}`);
|
|
1487
|
+
console.log(` ${colors.dim}${agent.description}${colors.reset}\n`);
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
558
1491
|
|
|
559
1492
|
console.log(`${colors.yellow}${colors.bold}📝 Next Steps:${colors.reset}`);
|
|
560
1493
|
console.log(`${colors.dim}1. Make sure Claude Code is initialized: ${colors.reset}${colors.cyan}claude init${colors.reset}`);
|
|
@@ -565,6 +1498,13 @@ async function main() {
|
|
|
565
1498
|
process.exit(0);
|
|
566
1499
|
}
|
|
567
1500
|
|
|
1501
|
+
if (!flags.force && !flags.dryRun && !rl) {
|
|
1502
|
+
rl = readline.createInterface({
|
|
1503
|
+
input: process.stdin,
|
|
1504
|
+
output: process.stdout
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
|
|
568
1508
|
// Additional path validation
|
|
569
1509
|
const normalizedTarget = path.normalize(targetDir);
|
|
570
1510
|
const normalizedCwd = path.normalize(process.cwd());
|
|
@@ -579,8 +1519,12 @@ async function main() {
|
|
|
579
1519
|
}
|
|
580
1520
|
|
|
581
1521
|
// Check for Claude Code installation
|
|
582
|
-
const claudeStatus = checkClaudeCode()
|
|
583
|
-
|
|
1522
|
+
const claudeStatus = includesClaude(aiProvider) ? checkClaudeCode() : {
|
|
1523
|
+
isInstalled: false,
|
|
1524
|
+
hasClaudeDir: false,
|
|
1525
|
+
hasClaudeCodeFile: false
|
|
1526
|
+
};
|
|
1527
|
+
if (includesClaude(aiProvider) && projectName === '.') {
|
|
584
1528
|
if (flags.dryRun) {
|
|
585
1529
|
console.log('⚠️ Note: Claude Code detection skipped in dry-run mode for current directory\n');
|
|
586
1530
|
} else if (!claudeStatus.isInstalled && !claudeStatus.hasClaudeDir) {
|
|
@@ -590,8 +1534,15 @@ async function main() {
|
|
|
590
1534
|
} else {
|
|
591
1535
|
console.log('⚠️ Claude Code not detected in this project.');
|
|
592
1536
|
console.log('ccsetup can create the .claude directory structure for you.\n');
|
|
593
|
-
|
|
594
|
-
|
|
1537
|
+
|
|
1538
|
+
// Use inquirer instead of readline prompt since rl might be closed
|
|
1539
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
1540
|
+
const confirm = confirmModule.default;
|
|
1541
|
+
const shouldCreate = await confirm({
|
|
1542
|
+
message: 'Would you like to create .claude directory?',
|
|
1543
|
+
default: true
|
|
1544
|
+
});
|
|
1545
|
+
if (!shouldCreate) {
|
|
595
1546
|
console.log('\nTo manually initialize Claude Code:');
|
|
596
1547
|
console.log('1. Install Claude Code CLI: https://docs.anthropic.com/claude-code/quickstart');
|
|
597
1548
|
console.log('2. Run \'claude init\' in your project directory');
|
|
@@ -607,7 +1558,12 @@ async function main() {
|
|
|
607
1558
|
|
|
608
1559
|
// Escape targetDir for safe display
|
|
609
1560
|
const safeTargetDir = targetDir.replace(/[^\w\s\-./\\:]/g, '');
|
|
610
|
-
|
|
1561
|
+
const projectLabel = aiProvider === 'both'
|
|
1562
|
+
? 'Claude Code + Codex'
|
|
1563
|
+
: aiProvider === 'codex'
|
|
1564
|
+
? 'Codex'
|
|
1565
|
+
: 'Claude Code';
|
|
1566
|
+
console.log(`${flags.dryRun ? 'Would create' : 'Creating'} ${projectLabel} project in ${safeTargetDir}...`);
|
|
611
1567
|
|
|
612
1568
|
if (projectName !== '.') {
|
|
613
1569
|
if (!fs.existsSync(targetDir)) {
|
|
@@ -628,8 +1584,8 @@ async function main() {
|
|
|
628
1584
|
}
|
|
629
1585
|
|
|
630
1586
|
// Check Claude Code in new directory after creation
|
|
631
|
-
const newDirClaudeStatus = checkClaudeCode();
|
|
632
|
-
if (!flags.dryRun && !newDirClaudeStatus.isInstalled) {
|
|
1587
|
+
const newDirClaudeStatus = includesClaude(aiProvider) ? checkClaudeCode() : { isInstalled: true };
|
|
1588
|
+
if (includesClaude(aiProvider) && !flags.dryRun && !newDirClaudeStatus.isInstalled) {
|
|
633
1589
|
console.log('\n⚠️ Note: Claude Code is not initialized in the new project directory.');
|
|
634
1590
|
console.log('After setup, remember to:');
|
|
635
1591
|
console.log(`1. cd ${JSON.stringify(projectName)}`);
|
|
@@ -644,7 +1600,9 @@ async function main() {
|
|
|
644
1600
|
// Group conflicts by category
|
|
645
1601
|
const conflictsByCategory = {
|
|
646
1602
|
'CLAUDE.md': [],
|
|
1603
|
+
'AGENTS.md': [],
|
|
647
1604
|
'agents': [],
|
|
1605
|
+
'codex': [],
|
|
648
1606
|
'docs': [],
|
|
649
1607
|
'plans': [],
|
|
650
1608
|
'tickets': []
|
|
@@ -653,7 +1611,9 @@ async function main() {
|
|
|
653
1611
|
// Store conflict strategies per category
|
|
654
1612
|
const conflictStrategies = {
|
|
655
1613
|
'CLAUDE.md': 'skip',
|
|
1614
|
+
'AGENTS.md': 'skip',
|
|
656
1615
|
'agents': 'skip',
|
|
1616
|
+
'codex': 'skip',
|
|
657
1617
|
'docs': 'skip',
|
|
658
1618
|
'plans': 'skip',
|
|
659
1619
|
'tickets': 'skip'
|
|
@@ -665,7 +1625,10 @@ async function main() {
|
|
|
665
1625
|
|
|
666
1626
|
|
|
667
1627
|
// Determine which agents to include
|
|
668
|
-
if (
|
|
1628
|
+
if (!includesClaude(aiProvider)) {
|
|
1629
|
+
selectedAgentFiles = [];
|
|
1630
|
+
console.log('\n⏭️ Skipping Claude agents for Codex-only setup');
|
|
1631
|
+
} else if (flags.noAgents) {
|
|
669
1632
|
selectedAgentFiles = [];
|
|
670
1633
|
console.log('\n⏭️ Skipping agent selection (--no-agents flag)');
|
|
671
1634
|
} else if (flags.browseAgents) {
|
|
@@ -691,19 +1654,27 @@ async function main() {
|
|
|
691
1654
|
const select = selectModule.default;
|
|
692
1655
|
|
|
693
1656
|
const agentMode = await select({
|
|
694
|
-
message: '
|
|
1657
|
+
message: 'Would you like to include AI agents in your project?',
|
|
695
1658
|
choices: [
|
|
696
1659
|
{
|
|
697
|
-
name: '
|
|
698
|
-
value: '
|
|
1660
|
+
name: '✨ Select Agents - Choose specific agents for your needs',
|
|
1661
|
+
value: 'select',
|
|
1662
|
+
description: 'Interactive selection of agents based on your project'
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
name: '🔍 Enhanced Selection - Advanced browsing with search and filters',
|
|
1666
|
+
value: 'enhanced',
|
|
1667
|
+
description: 'Use the enhanced interface with categories, tags, and search'
|
|
699
1668
|
},
|
|
700
1669
|
{
|
|
701
|
-
name: '
|
|
702
|
-
value: '
|
|
1670
|
+
name: '📚 Copy All Agents - Get all 8 agents to explore',
|
|
1671
|
+
value: 'browse',
|
|
1672
|
+
description: 'Copies all agents to /agents folder for manual review'
|
|
703
1673
|
},
|
|
704
1674
|
{
|
|
705
|
-
name: 'Skip -
|
|
706
|
-
value: 'skip'
|
|
1675
|
+
name: '⏭️ Skip Agents - Just set up the basic structure',
|
|
1676
|
+
value: 'skip',
|
|
1677
|
+
description: 'You can always add agents later'
|
|
707
1678
|
}
|
|
708
1679
|
]
|
|
709
1680
|
});
|
|
@@ -721,6 +1692,10 @@ async function main() {
|
|
|
721
1692
|
// Interactive selection
|
|
722
1693
|
selectedAgentFiles = await selectAgents(availableAgents);
|
|
723
1694
|
console.log(`\n✅ Selected ${selectedAgentFiles.length} agent${selectedAgentFiles.length === 1 ? '' : 's'}`);
|
|
1695
|
+
} else if (agentMode === 'enhanced') {
|
|
1696
|
+
// Enhanced selection with catalog system
|
|
1697
|
+
selectedAgentFiles = await enhancedAgentSelection();
|
|
1698
|
+
console.log(`\n✅ Selected ${selectedAgentFiles.length} agent${selectedAgentFiles.length === 1 ? '' : 's'}`);
|
|
724
1699
|
}
|
|
725
1700
|
|
|
726
1701
|
// Recreate readline interface after agent selection
|
|
@@ -734,7 +1709,7 @@ async function main() {
|
|
|
734
1709
|
// In dry-run mode, show what would happen
|
|
735
1710
|
console.log(`\nWould prompt for agent selection mode`);
|
|
736
1711
|
console.log(`Would then prompt for agent selection from ${availableAgents.length} available agents`);
|
|
737
|
-
selectedAgentFiles = availableAgents.map(a => a.file).filter(validateAgentFile); // Include all for
|
|
1712
|
+
selectedAgentFiles = availableAgents.map(a => a.file).filter(validateAgentFile); // Include all for dry-run preview
|
|
738
1713
|
}
|
|
739
1714
|
|
|
740
1715
|
function scanTemplate(src, dest, relativePath = '', skipAgents = false) {
|
|
@@ -760,6 +1735,15 @@ async function main() {
|
|
|
760
1735
|
if (path.basename(src) === '.claude') {
|
|
761
1736
|
return; // Don't process .claude directory in regular template scan
|
|
762
1737
|
}
|
|
1738
|
+
|
|
1739
|
+
if (path.basename(src) === '.codex') {
|
|
1740
|
+
return; // Don't process .codex directory in regular template scan
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// Skip hooks directory - hooks should only be installed to .claude/hooks via --install-hooks
|
|
1744
|
+
if (path.basename(src) === 'hooks') {
|
|
1745
|
+
return; // Don't copy hooks to project root
|
|
1746
|
+
}
|
|
763
1747
|
|
|
764
1748
|
// Skip agents directory if we're handling it separately
|
|
765
1749
|
if (skipAgents && path.basename(src) === 'agents') {
|
|
@@ -862,6 +1846,14 @@ async function main() {
|
|
|
862
1846
|
);
|
|
863
1847
|
});
|
|
864
1848
|
} else {
|
|
1849
|
+
if (
|
|
1850
|
+
(!includesClaude(aiProvider) && relativePath === 'CLAUDE.md') ||
|
|
1851
|
+
(!includesCodex(aiProvider) && relativePath === 'AGENTS.md') ||
|
|
1852
|
+
(!includesCodex(aiProvider) && relativePath === path.join('docs', 'codex-setup.md'))
|
|
1853
|
+
) {
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
865
1857
|
allItems.push({
|
|
866
1858
|
src,
|
|
867
1859
|
dest,
|
|
@@ -876,8 +1868,12 @@ async function main() {
|
|
|
876
1868
|
// Categorize the conflict
|
|
877
1869
|
if (relativePath === 'CLAUDE.md') {
|
|
878
1870
|
conflictsByCategory['CLAUDE.md'].push(relativePath);
|
|
1871
|
+
} else if (relativePath === 'AGENTS.md') {
|
|
1872
|
+
conflictsByCategory['AGENTS.md'].push(relativePath);
|
|
879
1873
|
} else if (relativePath.startsWith('agents/')) {
|
|
880
1874
|
conflictsByCategory['agents'].push(relativePath);
|
|
1875
|
+
} else if (relativePath.startsWith('.codex/')) {
|
|
1876
|
+
conflictsByCategory['codex'].push(relativePath);
|
|
881
1877
|
} else if (relativePath.startsWith('docs/')) {
|
|
882
1878
|
conflictsByCategory['docs'].push(relativePath);
|
|
883
1879
|
} else if (relativePath.startsWith('plans/')) {
|
|
@@ -894,6 +1890,40 @@ async function main() {
|
|
|
894
1890
|
|
|
895
1891
|
scanTemplate(templateDir, targetDir, '', true);
|
|
896
1892
|
|
|
1893
|
+
function scanCodexConflicts() {
|
|
1894
|
+
if (!includesCodex(aiProvider)) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
const templateCodexDir = path.join(templateDir, '.codex');
|
|
1899
|
+
if (!fs.existsSync(templateCodexDir)) {
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
function walk(srcDir, relBase = '.codex') {
|
|
1904
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
1905
|
+
for (const entry of entries) {
|
|
1906
|
+
const src = path.join(srcDir, entry.name);
|
|
1907
|
+
const relativePath = path.join(relBase, entry.name);
|
|
1908
|
+
const dest = path.join(targetDir, relativePath);
|
|
1909
|
+
|
|
1910
|
+
if (entry.isDirectory()) {
|
|
1911
|
+
walk(src, relativePath);
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
if (fs.existsSync(dest)) {
|
|
1916
|
+
fileConflicts.push(relativePath);
|
|
1917
|
+
conflictsByCategory['codex'].push(relativePath);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
walk(templateCodexDir);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
scanCodexConflicts();
|
|
1926
|
+
|
|
897
1927
|
// Handle force flag
|
|
898
1928
|
if (flags.force) {
|
|
899
1929
|
// Set all strategies to overwrite
|
|
@@ -917,7 +1947,9 @@ async function main() {
|
|
|
917
1947
|
// Ask for resolution strategy for each category with conflicts
|
|
918
1948
|
const categories = [
|
|
919
1949
|
{ key: 'CLAUDE.md', name: 'CLAUDE.md', emoji: '📄' },
|
|
1950
|
+
{ key: 'AGENTS.md', name: 'AGENTS.md', emoji: '🧭' },
|
|
920
1951
|
{ key: 'agents', name: 'Agents', emoji: '🤖' },
|
|
1952
|
+
{ key: 'codex', name: 'Codex', emoji: '🧠' },
|
|
921
1953
|
{ key: 'docs', name: 'Documentation', emoji: '📚' },
|
|
922
1954
|
{ key: 'plans', name: 'Plans', emoji: '📋' },
|
|
923
1955
|
{ key: 'tickets', name: 'Tickets', emoji: '🎫' }
|
|
@@ -933,20 +1965,32 @@ async function main() {
|
|
|
933
1965
|
console.log(' 2) rename (r) - Save template files with -ccsetup suffix');
|
|
934
1966
|
console.log(' 3) overwrite (o) - Replace with template versions');
|
|
935
1967
|
|
|
936
|
-
|
|
937
|
-
const
|
|
1968
|
+
// Use inquirer instead of readline prompt
|
|
1969
|
+
const selectModule = await import('@inquirer/select');
|
|
1970
|
+
const select = selectModule.default;
|
|
1971
|
+
const strategy = await select({
|
|
1972
|
+
message: `Your choice for ${category.name}:`,
|
|
1973
|
+
choices: [
|
|
1974
|
+
{ name: '(s)kip - Keep your existing files', value: 'skip' },
|
|
1975
|
+
{ name: '(r)ename - Create backups and use template versions', value: 'rename' },
|
|
1976
|
+
{ name: '(o)verwrite - Replace with template versions', value: 'overwrite' }
|
|
1977
|
+
],
|
|
1978
|
+
default: 'skip'
|
|
1979
|
+
});
|
|
938
1980
|
|
|
939
|
-
|
|
940
|
-
console.log(`\n❌ Invalid option: "${userInput}". Please use: skip/s/1, rename/r/2, or overwrite/o/3`);
|
|
941
|
-
if (rl) rl.close();
|
|
942
|
-
process.exit(1);
|
|
943
|
-
}
|
|
1981
|
+
// Strategy will always be valid when using select, no need for validation
|
|
944
1982
|
|
|
945
|
-
if (strategy === 'overwrite' && category.key === 'CLAUDE.md') {
|
|
946
|
-
|
|
947
|
-
|
|
1983
|
+
if (strategy === 'overwrite' && (category.key === 'CLAUDE.md' || category.key === 'AGENTS.md')) {
|
|
1984
|
+
// Use inquirer instead of readline prompt
|
|
1985
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
1986
|
+
const confirm = confirmModule.default;
|
|
1987
|
+
const shouldOverwrite = await confirm({
|
|
1988
|
+
message: `⚠️ Are you sure you want to overwrite ${category.key}? This will lose your project instructions!`,
|
|
1989
|
+
default: false
|
|
1990
|
+
});
|
|
1991
|
+
if (!shouldOverwrite) {
|
|
948
1992
|
conflictStrategies[category.key] = 'skip';
|
|
949
|
-
console.log(
|
|
1993
|
+
console.log(`Keeping existing ${category.key}`);
|
|
950
1994
|
continue;
|
|
951
1995
|
}
|
|
952
1996
|
}
|
|
@@ -981,8 +2025,12 @@ async function main() {
|
|
|
981
2025
|
let strategy = 'skip'; // default
|
|
982
2026
|
if (item.relativePath === 'CLAUDE.md') {
|
|
983
2027
|
strategy = conflictStrategies['CLAUDE.md'];
|
|
2028
|
+
} else if (item.relativePath === 'AGENTS.md') {
|
|
2029
|
+
strategy = conflictStrategies['AGENTS.md'];
|
|
984
2030
|
} else if (item.relativePath.startsWith('agents/')) {
|
|
985
2031
|
strategy = conflictStrategies['agents'];
|
|
2032
|
+
} else if (item.relativePath.startsWith('.codex/')) {
|
|
2033
|
+
strategy = conflictStrategies['codex'];
|
|
986
2034
|
} else if (item.relativePath.startsWith('docs/')) {
|
|
987
2035
|
strategy = conflictStrategies['docs'];
|
|
988
2036
|
} else if (item.relativePath.startsWith('plans/')) {
|
|
@@ -1015,9 +2063,11 @@ async function main() {
|
|
|
1015
2063
|
} else if (strategy === 'overwrite') {
|
|
1016
2064
|
if (!flags.dryRun) {
|
|
1017
2065
|
fs.copyFileSync(item.src, item.dest);
|
|
2066
|
+
console.log(` ♻️ Replaced: ${item.relativePath}`);
|
|
2067
|
+
} else {
|
|
2068
|
+
console.log(` ♻️ Would replace: ${item.relativePath}`);
|
|
1018
2069
|
}
|
|
1019
2070
|
overwrittenCount++;
|
|
1020
|
-
console.log(` ♻️ ${flags.dryRun ? 'Would replace' : 'Replaced'}: ${item.relativePath}`);
|
|
1021
2071
|
}
|
|
1022
2072
|
} else {
|
|
1023
2073
|
if (!flags.dryRun) {
|
|
@@ -1026,6 +2076,7 @@ async function main() {
|
|
|
1026
2076
|
if (!fs.existsSync(destDir)) {
|
|
1027
2077
|
fs.mkdirSync(destDir, { recursive: true });
|
|
1028
2078
|
}
|
|
2079
|
+
|
|
1029
2080
|
fs.copyFileSync(item.src, item.dest);
|
|
1030
2081
|
} else {
|
|
1031
2082
|
console.log(` ✨ Would copy: ${item.relativePath}`);
|
|
@@ -1045,32 +2096,42 @@ async function main() {
|
|
|
1045
2096
|
}
|
|
1046
2097
|
}
|
|
1047
2098
|
|
|
1048
|
-
// Initialize
|
|
2099
|
+
// Initialize provider-specific directories
|
|
1049
2100
|
let claudeInitResult = null;
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
|
|
2101
|
+
let codexInitResult = null;
|
|
2102
|
+
if (includesClaude(aiProvider)) {
|
|
2103
|
+
console.log(`\n🔧 ${claudeStatus.hasClaudeDir ? 'Updating' : 'Initializing'} .claude directory structure...`);
|
|
2104
|
+
claudeInitResult = await initializeClaudeDirectory(selectedAgentFiles, conflictStrategies['agents'], flags.dryRun);
|
|
2105
|
+
|
|
2106
|
+
if (claudeInitResult.createdItems.length > 0) {
|
|
2107
|
+
console.log(` ✅ Created ${claudeInitResult.createdItems.length} items in .claude directory`);
|
|
2108
|
+
}
|
|
2109
|
+
if (claudeInitResult.copiedAgents > 0) {
|
|
2110
|
+
console.log(` 🤖 Copied ${claudeInitResult.copiedAgents} agents to .claude/agents`);
|
|
2111
|
+
}
|
|
2112
|
+
if (claudeInitResult.skippedAgents > 0) {
|
|
2113
|
+
console.log(` ⏭️ Skipped ${claudeInitResult.skippedAgents} existing agents in .claude/agents`);
|
|
2114
|
+
}
|
|
1059
2115
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
2116
|
+
|
|
2117
|
+
if (includesCodex(aiProvider)) {
|
|
2118
|
+
console.log(`\n🔧 ${fs.existsSync(path.join(targetDir, '.codex')) ? 'Updating' : 'Initializing'} .codex directory structure...`);
|
|
2119
|
+
codexInitResult = await initializeCodexDirectory(conflictStrategies['codex'], flags.dryRun);
|
|
2120
|
+
if (codexInitResult.createdItems.length > 0) {
|
|
2121
|
+
console.log(` ✅ Created ${codexInitResult.createdItems.length} items in .codex directory`);
|
|
2122
|
+
}
|
|
1062
2123
|
}
|
|
1063
2124
|
|
|
1064
|
-
console.log(`\n✅
|
|
2125
|
+
console.log(`\n✅ ${projectLabel} project ${flags.dryRun ? 'would be ' : ''}created successfully!`);
|
|
1065
2126
|
|
|
1066
2127
|
// Show summary of what happened
|
|
1067
|
-
if (fileConflicts.length > 0 || copiedCount > 0 || selectedAgentFiles.length > 0 || claudeInitResult) {
|
|
2128
|
+
if (fileConflicts.length > 0 || copiedCount > 0 || selectedAgentFiles.length > 0 || claudeInitResult || codexInitResult) {
|
|
1068
2129
|
console.log('\n📊 Summary:');
|
|
1069
2130
|
if (copiedCount > 0) console.log(` ✨ ${copiedCount} new files ${flags.dryRun ? 'would be' : ''} copied`);
|
|
1070
2131
|
if (skippedCount > 0) console.log(` ⏭️ ${skippedCount} existing files ${flags.dryRun ? 'would be' : ''} kept unchanged`);
|
|
1071
2132
|
if (renamedCount > 0) console.log(` 📄 ${renamedCount} template files ${flags.dryRun ? 'would be' : ''} saved with -ccsetup suffix`);
|
|
1072
2133
|
if (overwrittenCount > 0) console.log(` ♻️ ${overwrittenCount} files ${flags.dryRun ? 'would be' : ''} replaced with template versions`);
|
|
1073
|
-
if (!flags.noAgents && !flags.dryRun) {
|
|
2134
|
+
if (includesClaude(aiProvider) && !flags.noAgents && !flags.dryRun) {
|
|
1074
2135
|
if (flags.browseAgents) {
|
|
1075
2136
|
const agentCount = availableAgents.length;
|
|
1076
2137
|
console.log(` 📚 ${agentCount} agent${agentCount === 1 ? '' : 's'} ${flags.dryRun ? 'would be' : ''} copied to /agents for browsing`);
|
|
@@ -1082,6 +2143,9 @@ async function main() {
|
|
|
1082
2143
|
if (claudeInitResult && claudeInitResult.createdItems.length > 0) {
|
|
1083
2144
|
console.log(` 📁 ${claudeInitResult.createdItems.length} items created in .claude directory`);
|
|
1084
2145
|
}
|
|
2146
|
+
if (codexInitResult && codexInitResult.createdItems.length > 0) {
|
|
2147
|
+
console.log(` 🧠 ${codexInitResult.createdItems.length} items created in .codex directory`);
|
|
2148
|
+
}
|
|
1085
2149
|
}
|
|
1086
2150
|
|
|
1087
2151
|
if (!flags.dryRun) {
|
|
@@ -1090,15 +2154,21 @@ async function main() {
|
|
|
1090
2154
|
// Escape project name to prevent command injection
|
|
1091
2155
|
const escapedProjectName = JSON.stringify(projectName);
|
|
1092
2156
|
console.log(` cd ${escapedProjectName}`);
|
|
1093
|
-
const finalClaudeStatus = checkClaudeCode();
|
|
1094
|
-
if (!finalClaudeStatus.isInstalled) {
|
|
2157
|
+
const finalClaudeStatus = includesClaude(aiProvider) ? checkClaudeCode() : { isInstalled: true };
|
|
2158
|
+
if (includesClaude(aiProvider) && !finalClaudeStatus.isInstalled) {
|
|
1095
2159
|
console.log(' claude init # Initialize Claude Code in the project');
|
|
1096
2160
|
}
|
|
1097
2161
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
2162
|
+
if (includesClaude(aiProvider)) {
|
|
2163
|
+
console.log(' 1. Edit CLAUDE.md to add your project-specific instructions');
|
|
2164
|
+
}
|
|
2165
|
+
if (includesCodex(aiProvider)) {
|
|
2166
|
+
console.log(` ${includesClaude(aiProvider) ? '2' : '1'}. Edit AGENTS.md to add your Codex project instructions`);
|
|
2167
|
+
console.log(` ${includesClaude(aiProvider) ? '3' : '2'}. Review docs/codex-setup.md for Codex setup guidance`);
|
|
2168
|
+
}
|
|
2169
|
+
console.log(` ${includesClaude(aiProvider) && includesCodex(aiProvider) ? '4' : includesCodex(aiProvider) ? '3' : '2'}. Update docs/ROADMAP.md with your project goals`);
|
|
2170
|
+
console.log(` ${includesClaude(aiProvider) && includesCodex(aiProvider) ? '5' : includesCodex(aiProvider) ? '4' : '3'}. Update docs/agent-orchestration.md to define agent workflows`);
|
|
2171
|
+
console.log(` ${includesClaude(aiProvider) && includesCodex(aiProvider) ? '6' : includesCodex(aiProvider) ? '5' : '4'}. Start creating tickets in the tickets/ directory`);
|
|
1102
2172
|
|
|
1103
2173
|
if (flags.browseAgents) {
|
|
1104
2174
|
console.log('\n📚 Agent Browse Mode:');
|
|
@@ -1114,6 +2184,92 @@ async function main() {
|
|
|
1114
2184
|
console.log(' You can compare them with your existing files or copy sections you need');
|
|
1115
2185
|
}
|
|
1116
2186
|
|
|
2187
|
+
if (includesCodex(aiProvider)) {
|
|
2188
|
+
console.log('\n🧠 Codex skills copied to .codex/skills/');
|
|
2189
|
+
console.log(' Use them from this project alongside AGENTS.md and docs/codex-setup.md');
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
// Ask user if they want the workflow selector hook
|
|
2193
|
+
if (includesClaude(aiProvider) && setupMode === 'full' && !flags.dryRun) {
|
|
2194
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
2195
|
+
if (fs.existsSync(claudeDir)) {
|
|
2196
|
+
try {
|
|
2197
|
+
let wantHook = false;
|
|
2198
|
+
|
|
2199
|
+
if (!flags.force) {
|
|
2200
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
2201
|
+
const confirm = confirmModule.default;
|
|
2202
|
+
|
|
2203
|
+
wantHook = await confirm({
|
|
2204
|
+
message: 'Enable workflow selector hook? (suggests agent workflows per prompt, controlled via CCSETUP_WORKFLOW env var)',
|
|
2205
|
+
default: false
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
if (wantHook) {
|
|
2210
|
+
// Create hooks directory
|
|
2211
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
2212
|
+
if (!fs.existsSync(hooksDir)) {
|
|
2213
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// Copy workflow-selector hook
|
|
2217
|
+
const hookSourceDir = path.join(templateDir, 'hooks', 'workflow-selector');
|
|
2218
|
+
const hookDestDir = path.join(hooksDir, 'workflow-selector');
|
|
2219
|
+
|
|
2220
|
+
if (fs.existsSync(hookSourceDir)) {
|
|
2221
|
+
if (!fs.existsSync(hookDestDir)) {
|
|
2222
|
+
fs.mkdirSync(hookDestDir, { recursive: true });
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
const hookFile = path.join(hookSourceDir, 'index.js');
|
|
2226
|
+
const destFile = path.join(hookDestDir, 'index.js');
|
|
2227
|
+
fs.copyFileSync(hookFile, destFile);
|
|
2228
|
+
|
|
2229
|
+
// Update settings.json
|
|
2230
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
2231
|
+
let settings = {};
|
|
2232
|
+
|
|
2233
|
+
if (fs.existsSync(settingsFile)) {
|
|
2234
|
+
try {
|
|
2235
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
2236
|
+
} catch (e) {
|
|
2237
|
+
// Start with empty settings
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
if (!settings.hooks) {
|
|
2242
|
+
settings.hooks = {};
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
settings.hooks.UserPromptSubmit = [
|
|
2246
|
+
{
|
|
2247
|
+
"matcher": ".*",
|
|
2248
|
+
"hooks": [
|
|
2249
|
+
{
|
|
2250
|
+
"type": "command",
|
|
2251
|
+
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/workflow-selector/index.js"
|
|
2252
|
+
}
|
|
2253
|
+
]
|
|
2254
|
+
}
|
|
2255
|
+
];
|
|
2256
|
+
|
|
2257
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
2258
|
+
console.log(' ✅ Workflow selection hook installed');
|
|
2259
|
+
console.log(' 📝 To activate, set the environment variable:');
|
|
2260
|
+
console.log(' export CCSETUP_WORKFLOW=1');
|
|
2261
|
+
console.log(' 💡 The hook suggests workflows and asks before applying them');
|
|
2262
|
+
}
|
|
2263
|
+
} else {
|
|
2264
|
+
console.log(' ⏭️ Skipped workflow selector hook');
|
|
2265
|
+
console.log(' 💡 You can install it later with: npx ccsetup --install-hooks');
|
|
2266
|
+
}
|
|
2267
|
+
} catch (error) {
|
|
2268
|
+
console.warn(' ⚠️ Could not install workflow hook:', error.message);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
|
|
1117
2273
|
console.log('\nHappy coding with Claude! 🎉');
|
|
1118
2274
|
}
|
|
1119
2275
|
|
|
@@ -1132,4 +2288,4 @@ main().catch(err => {
|
|
|
1132
2288
|
rl.close();
|
|
1133
2289
|
}
|
|
1134
2290
|
process.exit(1);
|
|
1135
|
-
});
|
|
2291
|
+
});
|