ccsetup 1.2.0 → 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.
@@ -3,10 +3,6 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const readline = require('readline');
6
- const RepositoryScanner = require('../lib/scanner');
7
- const ContextGenerator = require('../lib/contextGenerator');
8
- const ContextMerger = require('../lib/contextMerger');
9
- const ProgressReporter = require('../lib/progressReporter');
10
6
  const TemplateCatalog = require('../lib/templates/catalog');
11
7
  const TemplateFilter = require('../lib/templates/filter');
12
8
  const TemplateSearch = require('../lib/templates/search');
@@ -14,12 +10,6 @@ const TemplateSearch = require('../lib/templates/search');
14
10
  // Parse CLI arguments
15
11
  const args = process.argv.slice(2);
16
12
 
17
- // Handle scan subcommand
18
- if (args[0] === 'scan') {
19
- require('./scan.js');
20
- return;
21
- }
22
-
23
13
  const flags = {
24
14
  force: false,
25
15
  dryRun: false,
@@ -29,8 +19,6 @@ const flags = {
29
19
  agents: false,
30
20
  browseAgents: false,
31
21
  browse: false,
32
- scanContext: false,
33
- scanOnly: false,
34
22
  prompt: null
35
23
  };
36
24
 
@@ -55,10 +43,6 @@ for (let i = 0; i < args.length; i++) {
55
43
  flags.browseAgents = true;
56
44
  } else if (arg === '--browse') {
57
45
  flags.browse = true;
58
- } else if (arg === '--scan-context') {
59
- flags.scanContext = true;
60
- } else if (arg === '--scan-only') {
61
- flags.scanOnly = true;
62
46
  } else if (arg === '--install-hooks') {
63
47
  flags.installHooks = true;
64
48
  } else if (!arg.startsWith('-')) {
@@ -70,15 +54,12 @@ for (let i = 0; i < args.length; i++) {
70
54
  if (flags.help) {
71
55
  console.log(`
72
56
  Usage: ccsetup [project-name] [options]
73
- ccsetup scan [path] [options]
74
57
 
75
58
  Commands:
76
- ccsetup Interactive mode - choose full setup or scan-only
59
+ ccsetup Interactive mode
77
60
  ccsetup <name> Create a new Claude Code project
78
- ccsetup scan Advanced repository scanning (see 'ccsetup scan --help')
79
61
 
80
62
  Options:
81
- --scan-only Skip project setup, only scan and create/update CLAUDE.md ⭐
82
63
  --force, -f Skip all prompts and overwrite existing files
83
64
  --dry-run, -d Show what would be done without making changes
84
65
  --agents Interactive agent selection mode
@@ -86,32 +67,15 @@ Options:
86
67
  --no-agents Skip agent selection entirely
87
68
  --browse-agents Copy all agents to /agents folder for browsing
88
69
  --browse Enhanced template browsing and selection interface
89
- --scan-context Scan repository and add context to CLAUDE.md
90
70
  --help, -h Show this help message
91
71
 
92
72
  Advanced:
93
73
  --install-hooks Install workflow selection hook to .claude/hooks (optional, power users only)
94
74
 
95
- Quick Start:
96
- npx ccsetup # Interactive mode - choose what to do
97
- npx ccsetup --scan-only # Just scan and create CLAUDE.md ⭐
98
- npx ccsetup my-project # Full project setup
99
-
100
- Scan-Only Mode ⭐:
101
- Perfect for existing projects! Analyzes your codebase and creates/updates
102
- CLAUDE.md with project context without modifying your project structure.
103
-
104
75
  Examples:
105
- ccsetup --scan-only # Scan current directory only
106
- ccsetup . --scan-only # Same as above
107
- ccsetup --scan-only --force # Skip confirmation prompts
108
- ccsetup --scan-only --dry-run # Preview what would happen
109
-
110
- Full Setup Examples:
111
- ccsetup # Interactive setup in current directory
112
- ccsetup my-project # Create in new directory
113
- ccsetup . --scan-context # Full setup with context scanning
114
- ccsetup my-app --all-agents # Include all agents automatically
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
115
79
  `);
116
80
  process.exit(0);
117
81
  }
@@ -132,202 +96,8 @@ function validateProjectName(name) {
132
96
  return true;
133
97
  }
134
98
 
135
- async function scanRepositoryForContext(projectPath) {
136
- try {
137
- const progressReporter = new ProgressReporter();
138
- console.log('🔍 Scanning repository for project context...');
139
-
140
- const scanner = new RepositoryScanner(projectPath);
141
- const scanResults = await scanner.scan(progressReporter);
142
-
143
- const contextGenerator = new ContextGenerator(scanResults);
144
- const context = contextGenerator.generate();
145
- const structuredSections = contextGenerator.generateStructuredSections();
146
-
147
- console.log('\n📊 Detected project details:');
148
- if (context.overview) {
149
- console.log(` ${context.overview}`);
150
- }
151
-
152
- if (context.techStack && context.techStack.frameworks && context.techStack.frameworks.length > 0) {
153
- console.log(` Framework: ${context.techStack.frameworks.join(', ')}`);
154
- }
155
-
156
- if (context.commands && Object.keys(context.commands).length > 0) {
157
- const totalCommands = Object.values(context.commands).reduce((sum, cmds) => sum + cmds.length, 0);
158
- console.log(` Commands: ${totalCommands} available scripts`);
159
- }
160
-
161
- if (context.patterns && Object.keys(context.patterns).length > 0) {
162
- const totalPatterns = Object.values(context.patterns).reduce((sum, patterns) => sum + patterns.length, 0);
163
- console.log(` Patterns: ${totalPatterns} detected architectural patterns`);
164
- }
165
-
166
- return {
167
- scanResults,
168
- contextGenerator,
169
- structuredSections,
170
- formattedContext: contextGenerator.formatForClaude()
171
- };
172
- } catch (error) {
173
- console.warn(`⚠️ Repository scanning failed: ${error.message}`);
174
- console.log(' Continuing with standard setup...\n');
175
- return null;
176
- }
177
- }
178
-
179
- async function previewAndConfirmContext(formattedContext) {
180
- console.log('\n📝 Generated context preview:');
181
- console.log('━'.repeat(60));
182
- console.log(formattedContext);
183
- console.log('━'.repeat(60));
184
-
185
- // Use inquirer instead of readline prompt
186
- const selectModule = await import('@inquirer/select');
187
- const select = selectModule.default;
188
- const response = await select({
189
- message: 'Would you like to add this context to CLAUDE.md?',
190
- choices: [
191
- { name: 'Yes', value: 'y' },
192
- { name: 'No', value: 'n' },
193
- { name: 'Edit', value: 'edit' }
194
- ],
195
- default: 'y'
196
- });
197
- return response;
198
- }
199
-
200
- async function shouldScanRepository() {
201
- console.log('\n🔍 Existing project files detected.');
202
- // Use inquirer instead of readline prompt
203
- const confirmModule = await import('@inquirer/confirm');
204
- const confirm = confirmModule.default;
205
- const response = await confirm({
206
- message: 'Would you like to scan the repository to add project context to CLAUDE.md?',
207
- default: true
208
- });
209
- return response;
210
- }
211
-
212
- async function mergeContextIntelligently(existingContent, repositoryContext, strategy = 'smart') {
213
- try {
214
- // Handle replace strategy
215
- if (strategy === 'replace') {
216
- console.log(' Replacing with new scan results...');
217
- // Get template and apply context
218
- const templatePath = path.join(__dirname, '..', 'template', 'CLAUDE.md');
219
- let template = '';
220
- if (fs.existsSync(templatePath)) {
221
- template = fs.readFileSync(templatePath, 'utf8');
222
- } else {
223
- template = `# Claude Code Project Instructions
224
-
225
- ## Project Overview
226
- [Project description will be added here]
227
-
228
- ## Key Objectives
229
- [Project objectives will be added here]
230
-
231
- ## Additional Notes
232
- [Any other important information for Claude to know about this project]
233
- `;
234
- }
235
- return await applyContextToTemplate(template, repositoryContext, 'append');
236
- }
237
-
238
- // Use structured sections for intelligent merge, fall back to formatted context
239
- let contextToMerge = repositoryContext.formattedContext;
240
-
241
- if (repositoryContext.structuredSections &&
242
- typeof repositoryContext.structuredSections === 'object' &&
243
- Object.keys(repositoryContext.structuredSections).length > 0) {
244
- contextToMerge = repositoryContext.structuredSections;
245
- console.log(' 📊 Using intelligent merge with structured sections');
246
- } else {
247
- console.log(' 📝 Using fallback merge with formatted content');
248
- }
249
- const merger = new ContextMerger(existingContent, contextToMerge);
250
- const changes = merger.getChangesSummary();
251
-
252
- if (!changes.hasChanges) {
253
- console.log(' ✅ Context is already up to date');
254
- return existingContent;
255
- }
256
-
257
- console.log('\n 📊 Merge Analysis:');
258
- if (changes.added > 0) console.log(` + ${changes.added} new sections`);
259
- if (changes.modified > 0) console.log(` ~ ${changes.modified} updated sections`);
260
- if (changes.unchanged > 0) console.log(` ✓ ${changes.unchanged} preserved sections`);
261
-
262
- const mergedContent = await merger.merge(strategy);
263
-
264
- if (typeof mergedContent === 'string') {
265
- const existingHeader = merger.extractHeaderContent();
266
- return existingHeader + '\n\n' + mergedContent;
267
- }
268
-
269
- return mergedContent;
270
- } catch (error) {
271
- // Re-throw specific errors that should stop the process
272
- if (error.message === 'Interactive merge cancelled' ||
273
- error.message === 'Missing required dependency') {
274
- throw error;
275
- }
276
-
277
- console.warn(` ⚠️ Merge failed: ${error.message}`);
278
- console.log(' 📝 Falling back to simple append...');
279
-
280
- // Fall back to formatted context for simple append
281
- const newContext = repositoryContext.formattedContext;
282
- const additionalNotesMarker = '## Additional Notes';
283
- const additionalNotesIdx = existingContent.indexOf(additionalNotesMarker);
284
-
285
- if (additionalNotesIdx === -1) {
286
- return existingContent.trimEnd() + '\n\n' + additionalNotesMarker + '\n\n' + newContext.trim() + '\n';
287
- } else {
288
- const insertIdx = existingContent.indexOf('\n', additionalNotesIdx) + 1;
289
- return existingContent.substring(0, insertIdx) + '\n' + newContext.trim() + '\n' + existingContent.substring(insertIdx);
290
- }
291
- }
292
- }
293
-
294
- async function applyContextToTemplate(templateContent, repositoryContext, conflictStrategy) {
295
- const contextContent = repositoryContext.formattedContext;
296
- const additionalNotesMarker = '## Additional Notes';
297
- const placeholderContent = '[Any other important information for Claude to know about this project]';
298
-
299
- if (templateContent.includes(additionalNotesMarker)) {
300
- if (templateContent.includes(placeholderContent)) {
301
- return templateContent.replace(placeholderContent, contextContent.trim());
302
- } else {
303
- const sections = templateContent.split(additionalNotesMarker);
304
- if (sections.length >= 2) {
305
- return sections[0] + additionalNotesMarker + contextContent + '\n';
306
- }
307
- }
308
- }
309
-
310
- return templateContent + contextContent;
311
- }
312
-
313
99
  // Validate conflicting flags
314
100
  function validateFlags() {
315
- // Scan-only conflicts
316
- if (flags.scanOnly) {
317
- const conflictingFlags = [];
318
- if (flags.allAgents) conflictingFlags.push('--all-agents');
319
- if (flags.noAgents) conflictingFlags.push('--no-agents');
320
- if (flags.browseAgents) conflictingFlags.push('--browse-agents');
321
- if (flags.browse) conflictingFlags.push('--browse');
322
- if (flags.agents) conflictingFlags.push('--agents');
323
-
324
- if (conflictingFlags.length > 0) {
325
- console.error(`Error: --scan-only cannot be used with ${conflictingFlags.join(', ')}`);
326
- console.log('Tip: --scan-only skips all agent-related operations');
327
- process.exit(1);
328
- }
329
- }
330
-
331
101
  // Existing validations
332
102
  if (flags.allAgents && flags.noAgents) {
333
103
  console.error('Error: Cannot use --all-agents and --no-agents together');
@@ -359,6 +129,7 @@ if (projectName !== '.') {
359
129
 
360
130
  const targetDir = path.resolve(process.cwd(), projectName);
361
131
  const templateDir = path.join(__dirname, '..', 'template');
132
+ const VALID_AI_PROVIDERS = new Set(['claude', 'codex', 'both']);
362
133
 
363
134
  // Create readline interface only if needed
364
135
  let rl = null;
@@ -409,6 +180,72 @@ function validateAgentFile(file) {
409
180
  return true;
410
181
  }
411
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
+
412
249
  // Function to check if Claude Code is installed
413
250
  function checkClaudeCode() {
414
251
  const claudeDir = path.join(targetDir, '.claude');
@@ -604,6 +441,36 @@ async function initializeClaudeDirectory(selectedAgentFiles, conflictStrategy, d
604
441
  }
605
442
  }
606
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
+
607
474
  // Copy selected agents to .claude/agents
608
475
  const templateAgentsDir = path.join(templateDir, '.claude', 'agents');
609
476
  let copiedAgents = 0;
@@ -692,6 +559,126 @@ async function initializeClaudeDirectory(selectedAgentFiles, conflictStrategy, d
692
559
  }
693
560
  }
694
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
+
695
682
  // Function to parse agent frontmatter
696
683
  function parseAgentFrontmatter(filePath) {
697
684
  try {
@@ -1014,441 +1001,11 @@ async function installClaudeHooks() {
1014
1001
 
1015
1002
  // Setup mode selection function
1016
1003
  async function selectSetupMode() {
1017
- // Direct flag handling
1018
1004
  if (flags.installHooks) return 'install-hooks';
1019
- if (flags.scanOnly) return 'scan-only';
1020
1005
  if (flags.agents) return 'agents-only';
1021
- if (flags.force || flags.browseAgents || flags.allAgents || flags.noAgents) return 'full';
1022
-
1023
- // Skip selection if already in a specific mode
1024
- if (projectName !== '.' || flags.scanContext) return 'full';
1025
-
1026
- // Interactive selection for bare 'npx ccsetup'
1027
- console.log('Welcome to ccsetup! 🎉\n');
1028
- console.log('This tool helps you set up and maintain your Claude Code project.');
1029
- console.log('Let\'s analyze your repository and create the perfect CLAUDE.md file.\n');
1030
-
1031
- // Check if CLAUDE.md exists to provide context
1032
- const claudeMdExists = fs.existsSync(path.join(process.cwd(), 'CLAUDE.md'));
1033
- if (claudeMdExists) {
1034
- console.log('📋 Existing CLAUDE.md detected in this directory.\n');
1035
- } else {
1036
- console.log('📄 No CLAUDE.md found. Let\'s create one!\n');
1037
- }
1038
-
1039
- // Dynamic import for ESM module
1040
- const selectModule = await import('@inquirer/select');
1041
- const select = selectModule.default;
1042
-
1043
- const mode = await select({
1044
- message: claudeMdExists ?
1045
- 'CLAUDE.md exists. How would you like to proceed?' :
1046
- 'What would you like to do?',
1047
- choices: claudeMdExists ? [
1048
- {
1049
- name: 'Smart Merge - Keep my content, add new findings',
1050
- value: 'scan-smart',
1051
- description: 'Preserves your customizations while adding newly detected information'
1052
- },
1053
- {
1054
- name: 'Replace - Fresh scan, replace existing',
1055
- value: 'scan-replace',
1056
- description: 'Creates a brand new CLAUDE.md from your current codebase'
1057
- },
1058
- {
1059
- name: 'Full Setup - Add agents and project structure',
1060
- value: 'full',
1061
- description: 'Creates the complete Claude Code boilerplate structure with agents, docs, tickets, and plans'
1062
- }
1063
- ] : [
1064
- {
1065
- name: '🚀 Quick Start - Just create CLAUDE.md',
1066
- value: 'scan-smart',
1067
- description: 'Scans your code and creates CLAUDE.md with project context'
1068
- },
1069
- {
1070
- name: '🏗️ Full Setup - Complete Claude Code structure',
1071
- value: 'full',
1072
- description: 'Creates CLAUDE.md plus agents, docs, tickets, and plans'
1073
- }
1074
- ]
1075
- });
1076
-
1077
- return mode;
1078
- }
1079
-
1080
- // Validate scan-only environment
1081
- async function validateScanOnlyEnvironment() {
1082
- // Check directory exists and is accessible
1083
- try {
1084
- await fs.promises.access(targetDir, fs.constants.R_OK | fs.constants.W_OK);
1085
- } catch (error) {
1086
- let message = 'Cannot access target directory';
1087
- let suggestion = 'Check directory permissions';
1088
-
1089
- if (error.code === 'ENOENT') {
1090
- message = 'Target directory does not exist';
1091
- suggestion = 'Create the directory first or check the path';
1092
- } else if (error.code === 'EACCES') {
1093
- message = 'Permission denied accessing target directory';
1094
- suggestion = 'Run with appropriate permissions or use sudo';
1095
- } else if (error.code === 'ENOTDIR') {
1096
- message = 'Target path is not a directory';
1097
- suggestion = 'Specify a valid directory path';
1098
- }
1099
-
1100
- return {
1101
- valid: false,
1102
- message,
1103
- suggestion
1104
- };
1105
- }
1106
-
1107
- // Check if CLAUDE.md exists and is writable
1108
- const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
1109
- if (fs.existsSync(claudeMdPath)) {
1110
- try {
1111
- await fs.promises.access(claudeMdPath, fs.constants.R_OK | fs.constants.W_OK);
1112
- } catch (error) {
1113
- return {
1114
- valid: false,
1115
- message: 'Cannot access existing CLAUDE.md file',
1116
- suggestion: 'Check file permissions or run with appropriate access rights'
1117
- };
1118
- }
1119
- }
1120
-
1121
- // Check if directory is empty (warning only)
1122
- const files = await fs.promises.readdir(targetDir);
1123
- const hasFiles = files.some(f => !f.startsWith('.') && f !== 'node_modules');
1124
-
1125
- if (!hasFiles && projectName === '.') {
1126
- console.log('⚠️ Warning: Current directory appears to be empty.');
1127
- console.log(' Scan results may be limited.\n');
1128
- }
1129
-
1130
- return { valid: true };
1006
+ return 'full';
1131
1007
  }
1132
1008
 
1133
- // Get CLAUDE.md template
1134
- async function getClaudeMdTemplate() {
1135
- const templatePath = path.join(__dirname, '..', 'template', 'CLAUDE.md');
1136
- try {
1137
- return await fs.promises.readFile(templatePath, 'utf8');
1138
- } catch (error) {
1139
- // Fallback to minimal template
1140
- return `# Claude Code Project Instructions
1141
-
1142
- ## Project Overview
1143
- [Project description will be added here]
1144
-
1145
- ## Key Objectives
1146
- [Project objectives will be added here]
1147
-
1148
- ## Additional Notes
1149
- [Any other important information for Claude to know about this project]
1150
- `;
1151
- }
1152
- }
1153
-
1154
- // Scan-only mode implementation
1155
- async function scanOnlyMode(defaultMergeStrategy = 'smart') {
1156
- console.log('\n🔍 Repository Scan Mode\n');
1157
-
1158
- // Provide context based on merge strategy
1159
- switch (defaultMergeStrategy) {
1160
- case 'smart':
1161
- console.log('Smart Merge Mode');
1162
- console.log('Intelligently merges new findings with your existing CLAUDE.md');
1163
- console.log('Preserves your customizations while adding newly detected information\n');
1164
- break;
1165
- case 'replace':
1166
- console.log('Replace Mode');
1167
- console.log('Creates a brand new CLAUDE.md from your current codebase');
1168
- console.log('Perfect for: Starting fresh or major project restructuring\n');
1169
- break;
1170
- }
1171
-
1172
- console.log('This will analyze your repository and update CLAUDE.md with:');
1173
- console.log(' • Project structure and organization');
1174
- console.log(' • Technology stack and dependencies');
1175
- console.log(' • Available commands and scripts');
1176
- console.log(' • Architectural patterns detected');
1177
- console.log(' • Important context for Claude\n');
1178
-
1179
- // Validate environment
1180
- const validation = await validateScanOnlyEnvironment();
1181
- if (!validation.valid) {
1182
- console.error(`❌ ${validation.message}`);
1183
- if (validation.suggestion) {
1184
- console.log(`💡 ${validation.suggestion}`);
1185
- }
1186
- return;
1187
- }
1188
-
1189
- // Confirmation prompt (unless --force)
1190
- if (!flags.force && !flags.dryRun) {
1191
- // Create readline interface if needed
1192
- if (!rl) {
1193
- rl = readline.createInterface({
1194
- input: process.stdin,
1195
- output: process.stdout
1196
- });
1197
- }
1198
-
1199
- // Use inquirer instead of readline prompt
1200
- const confirmModule = await import('@inquirer/confirm');
1201
- const confirm = confirmModule.default;
1202
- const shouldContinue = await confirm({
1203
- message: 'Continue?',
1204
- default: true
1205
- });
1206
- if (!shouldContinue) {
1207
- console.log('Setup cancelled.');
1208
- if (rl) rl.close();
1209
- return;
1210
- }
1211
- }
1212
-
1213
- // Check if directory has meaningful files
1214
- const files = await fs.promises.readdir(targetDir);
1215
- const hasFiles = files.some(f => !f.startsWith('.') && f !== 'node_modules');
1216
-
1217
- // Perform repository scan
1218
- let repositoryContext = null;
1219
- try {
1220
- repositoryContext = await scanRepositoryForContext(targetDir);
1221
- } catch (error) {
1222
- console.error('❌ Repository scanning failed:', error.message);
1223
- if (rl) rl.close();
1224
- return;
1225
- }
1226
-
1227
- if (!repositoryContext) {
1228
- // Handle empty directory case
1229
- if (!hasFiles && projectName === '.') {
1230
- console.log('📝 Since the directory is empty, would you like to:');
1231
- console.log('1) Create a minimal CLAUDE.md with basic template');
1232
- console.log('2) Cancel and run full setup instead');
1233
-
1234
- // Use inquirer instead of readline prompt
1235
- const selectModule = await import('@inquirer/select');
1236
- const select = selectModule.default;
1237
- const choice = await select({
1238
- message: 'Choose an option:',
1239
- choices: [
1240
- { name: '1) Continue with minimal context', value: '1' },
1241
- { name: '2) Cancel and run full setup instead', value: '2' }
1242
- ]
1243
- });
1244
-
1245
- if (choice === '2') {
1246
- console.log('\n💡 Run `npx ccsetup` without --scan-only for full project setup.');
1247
- if (rl) rl.close();
1248
- return;
1249
- }
1250
-
1251
- // Continue with minimal context
1252
- repositoryContext = {
1253
- formattedContext: `
1254
- ## Additional Notes
1255
-
1256
- This project directory was empty when scanned.
1257
- Please update this file with relevant project information as you develop.
1258
-
1259
- ### Getting Started
1260
- - Add project description above
1261
- - Define key objectives
1262
- - Document important conventions
1263
- - Update as the project evolves
1264
- `
1265
- };
1266
- } else {
1267
- console.log('❌ Unable to generate context from repository.');
1268
- console.log('💡 Tip: Make sure you\'re in a valid project directory.');
1269
- if (rl) rl.close();
1270
- return;
1271
- }
1272
- }
1273
-
1274
- // Preview and confirm context
1275
- if (!flags.force && !flags.dryRun) {
1276
- const confirmResult = await previewAndConfirmContext(repositoryContext.formattedContext);
1277
- if (confirmResult === 'n' || confirmResult === 'no') {
1278
- console.log('✅ Setup cancelled.');
1279
- if (rl) rl.close();
1280
- return;
1281
- }
1282
- }
1283
-
1284
- // Handle CLAUDE.md creation/update
1285
- const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
1286
- const exists = fs.existsSync(claudeMdPath);
1287
- let backupPath = null;
1288
- let mergeStrategy = 'smart'; // Default merge strategy
1289
-
1290
- try {
1291
- if (exists) {
1292
- // Update existing CLAUDE.md
1293
- if (!flags.force && !flags.dryRun) {
1294
- console.log('\n📋 Existing CLAUDE.md detected!');
1295
-
1296
- // Use the default merge strategy if provided
1297
- if (defaultMergeStrategy === 'replace') {
1298
- console.log('This will replace your existing CLAUDE.md with a fresh scan.');
1299
- console.log(' Your current content will be backed up first.');
1300
- const confirmModule = await import('@inquirer/confirm');
1301
- const confirm = confirmModule.default;
1302
- const shouldProceed = await confirm({
1303
- message: 'Proceed with replacement?',
1304
- default: false
1305
- });
1306
- if (!shouldProceed) {
1307
- console.log('Cancelled.');
1308
- if (rl) rl.close();
1309
- return;
1310
- }
1311
- mergeStrategy = 'replace';
1312
- } else {
1313
- console.log('The scan will:');
1314
- console.log(' • Preserve all your existing content');
1315
- console.log(' • Add new findings from the scan');
1316
- console.log(' • Create an automatic backup');
1317
-
1318
- const confirmModule = await import('@inquirer/confirm');
1319
- const confirm = confirmModule.default;
1320
- const shouldContinue = await confirm({
1321
- message: 'Continue with smart merge?',
1322
- default: true
1323
- });
1324
- if (!shouldContinue) {
1325
- console.log('Update cancelled.');
1326
- if (rl) rl.close();
1327
- return;
1328
- }
1329
- mergeStrategy = 'smart';
1330
- }
1331
- } else {
1332
- mergeStrategy = defaultMergeStrategy;
1333
- }
1334
-
1335
- console.log('\n📄 Updating existing CLAUDE.md...');
1336
-
1337
- if (!flags.dryRun) {
1338
- // Check file size before processing
1339
- const stats = fs.statSync(claudeMdPath);
1340
- if (stats.size > 1024 * 1024) { // 1MB
1341
- console.warn('⚠️ Warning: CLAUDE.md is large (>1MB). Processing may take longer.');
1342
- }
1343
-
1344
- let existingContent;
1345
- try {
1346
- existingContent = fs.readFileSync(claudeMdPath, 'utf8');
1347
- } catch (error) {
1348
- throw new Error(`Failed to read existing CLAUDE.md: ${error.message}`);
1349
- }
1350
-
1351
- // Validate content
1352
- if (!existingContent) {
1353
- console.warn('⚠️ Warning: Existing CLAUDE.md appears to be empty.');
1354
- }
1355
-
1356
- // Create backup first
1357
- backupPath = `${claudeMdPath}.backup.${Date.now()}`;
1358
- try {
1359
- fs.writeFileSync(backupPath, existingContent);
1360
- console.log(`📦 Backup created: ${path.basename(backupPath)}`);
1361
- } catch (error) {
1362
- throw new Error(`Failed to create backup: ${error.message}`);
1363
- }
1364
-
1365
- // Use intelligent merge with backup
1366
- let updatedContent;
1367
- try {
1368
- updatedContent = await mergeContextIntelligently(existingContent, repositoryContext, mergeStrategy);
1369
- } catch (mergeError) {
1370
- if (mergeError.message === 'Interactive merge cancelled') {
1371
- console.log('💾 Your original CLAUDE.md is unchanged.');
1372
- console.log(`📦 Scan results backup: ${path.basename(backupPath)}`);
1373
- if (rl) rl.close();
1374
- return;
1375
- } else if (mergeError.message === 'Missing required dependency') {
1376
- console.error('Please install missing dependencies and try again.');
1377
- if (rl) rl.close();
1378
- return;
1379
- }
1380
- throw mergeError;
1381
- }
1382
-
1383
- try {
1384
- fs.writeFileSync(claudeMdPath, updatedContent, 'utf8');
1385
- } catch (error) {
1386
- // Restore from backup if write fails
1387
- try {
1388
- fs.writeFileSync(claudeMdPath, existingContent, 'utf8');
1389
- console.error('❌ Failed to update CLAUDE.md. Original content restored.');
1390
- } catch (restoreError) {
1391
- console.error('❌ Critical: Failed to update AND restore CLAUDE.md!');
1392
- console.error(`💾 Your content is safe in: ${path.basename(backupPath)}`);
1393
- }
1394
- throw new Error(`Failed to write updated content: ${error.message}`);
1395
- }
1396
-
1397
- console.log('✅ CLAUDE.md updated successfully!\n');
1398
-
1399
- // Provide feedback based on merge strategy used
1400
- if (mergeStrategy === 'smart') {
1401
- console.log(' Smart merge completed');
1402
- console.log(' - Your customizations preserved');
1403
- console.log(' - New findings integrated');
1404
- } else if (mergeStrategy === 'replace') {
1405
- console.log(' Replace completed');
1406
- console.log(' - New CLAUDE.md generated from current codebase');
1407
- }
1408
-
1409
- console.log(` 📦 Backup saved: ${path.basename(backupPath)}`);
1410
- console.log(' 💡 Review the updated content and delete backup when satisfied');
1411
- } else {
1412
- console.log(' Would update: CLAUDE.md (dry-run mode)');
1413
- }
1414
- } else {
1415
- // Create new CLAUDE.md
1416
- console.log('\n📄 Creating CLAUDE.md...');
1417
-
1418
- if (!flags.dryRun) {
1419
- const template = await getClaudeMdTemplate();
1420
- const enhanced = await applyContextToTemplate(template, repositoryContext, 'append');
1421
- fs.writeFileSync(claudeMdPath, enhanced, 'utf8');
1422
- console.log('✅ CLAUDE.md created with project context!');
1423
- } else {
1424
- console.log(' Would create: CLAUDE.md (dry-run mode)');
1425
- }
1426
- }
1427
-
1428
- // Show next steps
1429
- console.log('\nNext steps:');
1430
- console.log('1. Review the updated Additional Notes section in CLAUDE.md');
1431
- console.log('2. Move any important context to appropriate sections');
1432
- if (exists && backupPath && !flags.dryRun) {
1433
- console.log(`3. Delete the backup file once satisfied: ${path.basename(backupPath)}`);
1434
- console.log('4. Run `ccsetup scan` anytime to refresh context');
1435
- } else if (exists) {
1436
- console.log('3. Run `ccsetup scan` anytime to refresh context');
1437
- } else {
1438
- console.log('3. Add project-specific instructions and guidelines');
1439
- console.log('4. Run `npx ccsetup` for full setup if you need agents and project structure');
1440
- console.log('5. Run `ccsetup scan` anytime to refresh context');
1441
- }
1442
-
1443
- } catch (error) {
1444
- console.error('❌ Error handling CLAUDE.md:', error.message);
1445
- if (exists && !flags.dryRun) {
1446
- console.log('💡 Your original CLAUDE.md is safe. Check for backup files if needed.');
1447
- }
1448
- } finally {
1449
- if (rl) rl.close();
1450
- }
1451
- }
1452
1009
 
1453
1010
  // Dynamic import for ESM module
1454
1011
  async function importCheckbox() {
@@ -1862,29 +1419,27 @@ async function main() {
1862
1419
  await installClaudeHooks();
1863
1420
  return;
1864
1421
  }
1865
-
1866
- if (setupMode === 'scan-only' || setupMode === 'scan-smart' || setupMode === 'scan-replace') {
1867
- // Close readline if open
1868
- if (rl) {
1869
- rl.close();
1870
- rl = null;
1871
- }
1872
1422
 
1873
- // Set appropriate flags based on mode
1874
- flags.scanOnly = true;
1875
- if (setupMode === 'scan-replace') {
1876
- flags.mergeStrategy = 'replace';
1877
- } else {
1878
- flags.mergeStrategy = 'smart';
1879
- }
1880
-
1881
- await scanOnlyMode(flags.mergeStrategy);
1882
- return;
1423
+ if (!flags.force && !flags.dryRun && rl) {
1424
+ rl.close();
1425
+ rl = null;
1883
1426
  }
1884
1427
 
1428
+ const aiProvider = await selectAiProvider();
1429
+
1885
1430
  // Handle --agents and --browse flags for agent selection only
1886
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');
1435
+ process.exit(0);
1436
+ }
1437
+
1887
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
+ }
1888
1443
 
1889
1444
  let selectedAgentFiles = [];
1890
1445
 
@@ -1943,6 +1498,13 @@ async function main() {
1943
1498
  process.exit(0);
1944
1499
  }
1945
1500
 
1501
+ if (!flags.force && !flags.dryRun && !rl) {
1502
+ rl = readline.createInterface({
1503
+ input: process.stdin,
1504
+ output: process.stdout
1505
+ });
1506
+ }
1507
+
1946
1508
  // Additional path validation
1947
1509
  const normalizedTarget = path.normalize(targetDir);
1948
1510
  const normalizedCwd = path.normalize(process.cwd());
@@ -1957,8 +1519,12 @@ async function main() {
1957
1519
  }
1958
1520
 
1959
1521
  // Check for Claude Code installation
1960
- const claudeStatus = checkClaudeCode();
1961
- if (projectName === '.') {
1522
+ const claudeStatus = includesClaude(aiProvider) ? checkClaudeCode() : {
1523
+ isInstalled: false,
1524
+ hasClaudeDir: false,
1525
+ hasClaudeCodeFile: false
1526
+ };
1527
+ if (includesClaude(aiProvider) && projectName === '.') {
1962
1528
  if (flags.dryRun) {
1963
1529
  console.log('⚠️ Note: Claude Code detection skipped in dry-run mode for current directory\n');
1964
1530
  } else if (!claudeStatus.isInstalled && !claudeStatus.hasClaudeDir) {
@@ -1992,7 +1558,12 @@ async function main() {
1992
1558
 
1993
1559
  // Escape targetDir for safe display
1994
1560
  const safeTargetDir = targetDir.replace(/[^\w\s\-./\\:]/g, '');
1995
- console.log(`${flags.dryRun ? 'Would create' : 'Creating'} Claude Code project in ${safeTargetDir}...`);
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}...`);
1996
1567
 
1997
1568
  if (projectName !== '.') {
1998
1569
  if (!fs.existsSync(targetDir)) {
@@ -2013,8 +1584,8 @@ async function main() {
2013
1584
  }
2014
1585
 
2015
1586
  // Check Claude Code in new directory after creation
2016
- const newDirClaudeStatus = checkClaudeCode();
2017
- if (!flags.dryRun && !newDirClaudeStatus.isInstalled) {
1587
+ const newDirClaudeStatus = includesClaude(aiProvider) ? checkClaudeCode() : { isInstalled: true };
1588
+ if (includesClaude(aiProvider) && !flags.dryRun && !newDirClaudeStatus.isInstalled) {
2018
1589
  console.log('\n⚠️ Note: Claude Code is not initialized in the new project directory.');
2019
1590
  console.log('After setup, remember to:');
2020
1591
  console.log(`1. cd ${JSON.stringify(projectName)}`);
@@ -2029,7 +1600,9 @@ async function main() {
2029
1600
  // Group conflicts by category
2030
1601
  const conflictsByCategory = {
2031
1602
  'CLAUDE.md': [],
1603
+ 'AGENTS.md': [],
2032
1604
  'agents': [],
1605
+ 'codex': [],
2033
1606
  'docs': [],
2034
1607
  'plans': [],
2035
1608
  'tickets': []
@@ -2038,7 +1611,9 @@ async function main() {
2038
1611
  // Store conflict strategies per category
2039
1612
  const conflictStrategies = {
2040
1613
  'CLAUDE.md': 'skip',
1614
+ 'AGENTS.md': 'skip',
2041
1615
  'agents': 'skip',
1616
+ 'codex': 'skip',
2042
1617
  'docs': 'skip',
2043
1618
  'plans': 'skip',
2044
1619
  'tickets': 'skip'
@@ -2050,7 +1625,10 @@ async function main() {
2050
1625
 
2051
1626
 
2052
1627
  // Determine which agents to include
2053
- if (flags.noAgents) {
1628
+ if (!includesClaude(aiProvider)) {
1629
+ selectedAgentFiles = [];
1630
+ console.log('\n⏭️ Skipping Claude agents for Codex-only setup');
1631
+ } else if (flags.noAgents) {
2054
1632
  selectedAgentFiles = [];
2055
1633
  console.log('\n⏭️ Skipping agent selection (--no-agents flag)');
2056
1634
  } else if (flags.browseAgents) {
@@ -2089,7 +1667,7 @@ async function main() {
2089
1667
  description: 'Use the enhanced interface with categories, tags, and search'
2090
1668
  },
2091
1669
  {
2092
- name: '📚 Copy All Agents - Get all 50+ agents to explore',
1670
+ name: '📚 Copy All Agents - Get all 8 agents to explore',
2093
1671
  value: 'browse',
2094
1672
  description: 'Copies all agents to /agents folder for manual review'
2095
1673
  },
@@ -2131,7 +1709,7 @@ async function main() {
2131
1709
  // In dry-run mode, show what would happen
2132
1710
  console.log(`\nWould prompt for agent selection mode`);
2133
1711
  console.log(`Would then prompt for agent selection from ${availableAgents.length} available agents`);
2134
- selectedAgentFiles = availableAgents.map(a => a.file).filter(validateAgentFile); // Include all for scanning purposes
1712
+ selectedAgentFiles = availableAgents.map(a => a.file).filter(validateAgentFile); // Include all for dry-run preview
2135
1713
  }
2136
1714
 
2137
1715
  function scanTemplate(src, dest, relativePath = '', skipAgents = false) {
@@ -2157,6 +1735,10 @@ async function main() {
2157
1735
  if (path.basename(src) === '.claude') {
2158
1736
  return; // Don't process .claude directory in regular template scan
2159
1737
  }
1738
+
1739
+ if (path.basename(src) === '.codex') {
1740
+ return; // Don't process .codex directory in regular template scan
1741
+ }
2160
1742
 
2161
1743
  // Skip hooks directory - hooks should only be installed to .claude/hooks via --install-hooks
2162
1744
  if (path.basename(src) === 'hooks') {
@@ -2264,6 +1846,14 @@ async function main() {
2264
1846
  );
2265
1847
  });
2266
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
+
2267
1857
  allItems.push({
2268
1858
  src,
2269
1859
  dest,
@@ -2278,8 +1868,12 @@ async function main() {
2278
1868
  // Categorize the conflict
2279
1869
  if (relativePath === 'CLAUDE.md') {
2280
1870
  conflictsByCategory['CLAUDE.md'].push(relativePath);
1871
+ } else if (relativePath === 'AGENTS.md') {
1872
+ conflictsByCategory['AGENTS.md'].push(relativePath);
2281
1873
  } else if (relativePath.startsWith('agents/')) {
2282
1874
  conflictsByCategory['agents'].push(relativePath);
1875
+ } else if (relativePath.startsWith('.codex/')) {
1876
+ conflictsByCategory['codex'].push(relativePath);
2283
1877
  } else if (relativePath.startsWith('docs/')) {
2284
1878
  conflictsByCategory['docs'].push(relativePath);
2285
1879
  } else if (relativePath.startsWith('plans/')) {
@@ -2296,29 +1890,40 @@ async function main() {
2296
1890
 
2297
1891
  scanTemplate(templateDir, targetDir, '', true);
2298
1892
 
2299
- // Handle repository scanning for context
2300
- let repositoryContext = null;
2301
- if (flags.scanContext || (!flags.dryRun && projectName === '.' && !flags.force)) {
2302
- const hasExistingFiles = allItems.some(item => item.exists);
2303
-
2304
- if (hasExistingFiles || flags.scanContext) {
2305
- if (flags.scanContext || (!flags.force && await shouldScanRepository())) {
2306
- repositoryContext = await scanRepositoryForContext(targetDir);
2307
-
2308
- if (repositoryContext && !flags.dryRun) {
2309
- const confirmResult = await previewAndConfirmContext(repositoryContext.formattedContext);
2310
-
2311
- if (confirmResult === 'n' || confirmResult === 'no') {
2312
- repositoryContext = null;
2313
- console.log('✅ Skipping context addition to CLAUDE.md');
2314
- } else if (confirmResult === 'edit') {
2315
- console.log('📝 Context editing not yet implemented. Using generated context as-is.');
2316
- }
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);
2317
1918
  }
2318
1919
  }
2319
1920
  }
1921
+
1922
+ walk(templateCodexDir);
2320
1923
  }
2321
1924
 
1925
+ scanCodexConflicts();
1926
+
2322
1927
  // Handle force flag
2323
1928
  if (flags.force) {
2324
1929
  // Set all strategies to overwrite
@@ -2342,7 +1947,9 @@ async function main() {
2342
1947
  // Ask for resolution strategy for each category with conflicts
2343
1948
  const categories = [
2344
1949
  { key: 'CLAUDE.md', name: 'CLAUDE.md', emoji: '📄' },
1950
+ { key: 'AGENTS.md', name: 'AGENTS.md', emoji: '🧭' },
2345
1951
  { key: 'agents', name: 'Agents', emoji: '🤖' },
1952
+ { key: 'codex', name: 'Codex', emoji: '🧠' },
2346
1953
  { key: 'docs', name: 'Documentation', emoji: '📚' },
2347
1954
  { key: 'plans', name: 'Plans', emoji: '📋' },
2348
1955
  { key: 'tickets', name: 'Tickets', emoji: '🎫' }
@@ -2373,17 +1980,17 @@ async function main() {
2373
1980
 
2374
1981
  // Strategy will always be valid when using select, no need for validation
2375
1982
 
2376
- if (strategy === 'overwrite' && category.key === 'CLAUDE.md') {
1983
+ if (strategy === 'overwrite' && (category.key === 'CLAUDE.md' || category.key === 'AGENTS.md')) {
2377
1984
  // Use inquirer instead of readline prompt
2378
1985
  const confirmModule = await import('@inquirer/confirm');
2379
1986
  const confirm = confirmModule.default;
2380
1987
  const shouldOverwrite = await confirm({
2381
- message: '⚠️ Are you sure you want to overwrite CLAUDE.md? This will lose your project instructions!',
1988
+ message: `⚠️ Are you sure you want to overwrite ${category.key}? This will lose your project instructions!`,
2382
1989
  default: false
2383
1990
  });
2384
1991
  if (!shouldOverwrite) {
2385
1992
  conflictStrategies[category.key] = 'skip';
2386
- console.log('Keeping existing CLAUDE.md');
1993
+ console.log(`Keeping existing ${category.key}`);
2387
1994
  continue;
2388
1995
  }
2389
1996
  }
@@ -2418,8 +2025,12 @@ async function main() {
2418
2025
  let strategy = 'skip'; // default
2419
2026
  if (item.relativePath === 'CLAUDE.md') {
2420
2027
  strategy = conflictStrategies['CLAUDE.md'];
2028
+ } else if (item.relativePath === 'AGENTS.md') {
2029
+ strategy = conflictStrategies['AGENTS.md'];
2421
2030
  } else if (item.relativePath.startsWith('agents/')) {
2422
2031
  strategy = conflictStrategies['agents'];
2032
+ } else if (item.relativePath.startsWith('.codex/')) {
2033
+ strategy = conflictStrategies['codex'];
2423
2034
  } else if (item.relativePath.startsWith('docs/')) {
2424
2035
  strategy = conflictStrategies['docs'];
2425
2036
  } else if (item.relativePath.startsWith('plans/')) {
@@ -2451,26 +2062,10 @@ async function main() {
2451
2062
  console.log(` 📄 ${flags.dryRun ? 'Would create' : 'Created'}: ${path.relative(targetDir, newDest)}`);
2452
2063
  } else if (strategy === 'overwrite') {
2453
2064
  if (!flags.dryRun) {
2454
- // Handle CLAUDE.md with context injection on overwrite
2455
- if (item.relativePath === 'CLAUDE.md' && repositoryContext) {
2456
- const templateContent = fs.readFileSync(item.src, 'utf8');
2457
- const enhancedContent = await applyContextToTemplate(
2458
- templateContent,
2459
- repositoryContext,
2460
- 'append'
2461
- );
2462
- fs.writeFileSync(item.dest, enhancedContent, 'utf8');
2463
- console.log(` ♻️ Replaced CLAUDE.md with project context`);
2464
- } else {
2465
- fs.copyFileSync(item.src, item.dest);
2466
- console.log(` ♻️ Replaced: ${item.relativePath}`);
2467
- }
2065
+ fs.copyFileSync(item.src, item.dest);
2066
+ console.log(` ♻️ Replaced: ${item.relativePath}`);
2468
2067
  } else {
2469
- if (item.relativePath === 'CLAUDE.md' && repositoryContext) {
2470
- console.log(` ♻️ Would replace: ${item.relativePath} (with project context)`);
2471
- } else {
2472
- console.log(` ♻️ Would replace: ${item.relativePath}`);
2473
- }
2068
+ console.log(` ♻️ Would replace: ${item.relativePath}`);
2474
2069
  }
2475
2070
  overwrittenCount++;
2476
2071
  }
@@ -2482,25 +2077,9 @@ async function main() {
2482
2077
  fs.mkdirSync(destDir, { recursive: true });
2483
2078
  }
2484
2079
 
2485
- // Handle CLAUDE.md with context injection
2486
- if (item.relativePath === 'CLAUDE.md' && repositoryContext) {
2487
- const templateContent = fs.readFileSync(item.src, 'utf8');
2488
- const enhancedContent = await applyContextToTemplate(
2489
- templateContent,
2490
- repositoryContext,
2491
- 'append'
2492
- );
2493
- fs.writeFileSync(item.dest, enhancedContent, 'utf8');
2494
- console.log(` ✨ Created CLAUDE.md with project context`);
2495
- } else {
2496
- fs.copyFileSync(item.src, item.dest);
2497
- }
2080
+ fs.copyFileSync(item.src, item.dest);
2498
2081
  } else {
2499
- if (item.relativePath === 'CLAUDE.md' && repositoryContext) {
2500
- console.log(` ✨ Would copy: ${item.relativePath} (with project context)`);
2501
- } else {
2502
- console.log(` ✨ Would copy: ${item.relativePath}`);
2503
- }
2082
+ console.log(` ✨ Would copy: ${item.relativePath}`);
2504
2083
  }
2505
2084
  copiedCount++;
2506
2085
  }
@@ -2517,32 +2096,42 @@ async function main() {
2517
2096
  }
2518
2097
  }
2519
2098
 
2520
- // Initialize .claude directory and copy agents
2099
+ // Initialize provider-specific directories
2521
2100
  let claudeInitResult = null;
2522
- // Always initialize .claude directory structure (it will handle existing directories)
2523
- console.log(`\n🔧 ${claudeStatus.hasClaudeDir ? 'Updating' : 'Initializing'} .claude directory structure...`);
2524
- claudeInitResult = await initializeClaudeDirectory(selectedAgentFiles, conflictStrategies['agents'], flags.dryRun);
2525
-
2526
- if (claudeInitResult.createdItems.length > 0) {
2527
- console.log(` ✅ Created ${claudeInitResult.createdItems.length} items in .claude directory`);
2528
- }
2529
- if (claudeInitResult.copiedAgents > 0) {
2530
- console.log(` 🤖 Copied ${claudeInitResult.copiedAgents} agents to .claude/agents`);
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
+ }
2531
2115
  }
2532
- if (claudeInitResult.skippedAgents > 0) {
2533
- console.log(` ⏭️ Skipped ${claudeInitResult.skippedAgents} existing agents in .claude/agents`);
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
+ }
2534
2123
  }
2535
2124
 
2536
- console.log(`\n✅ Claude Code project ${flags.dryRun ? 'would be' : ''} created successfully!`);
2125
+ console.log(`\n✅ ${projectLabel} project ${flags.dryRun ? 'would be ' : ''}created successfully!`);
2537
2126
 
2538
2127
  // Show summary of what happened
2539
- if (fileConflicts.length > 0 || copiedCount > 0 || selectedAgentFiles.length > 0 || claudeInitResult) {
2128
+ if (fileConflicts.length > 0 || copiedCount > 0 || selectedAgentFiles.length > 0 || claudeInitResult || codexInitResult) {
2540
2129
  console.log('\n📊 Summary:');
2541
2130
  if (copiedCount > 0) console.log(` ✨ ${copiedCount} new files ${flags.dryRun ? 'would be' : ''} copied`);
2542
2131
  if (skippedCount > 0) console.log(` ⏭️ ${skippedCount} existing files ${flags.dryRun ? 'would be' : ''} kept unchanged`);
2543
2132
  if (renamedCount > 0) console.log(` 📄 ${renamedCount} template files ${flags.dryRun ? 'would be' : ''} saved with -ccsetup suffix`);
2544
2133
  if (overwrittenCount > 0) console.log(` ♻️ ${overwrittenCount} files ${flags.dryRun ? 'would be' : ''} replaced with template versions`);
2545
- if (!flags.noAgents && !flags.dryRun) {
2134
+ if (includesClaude(aiProvider) && !flags.noAgents && !flags.dryRun) {
2546
2135
  if (flags.browseAgents) {
2547
2136
  const agentCount = availableAgents.length;
2548
2137
  console.log(` 📚 ${agentCount} agent${agentCount === 1 ? '' : 's'} ${flags.dryRun ? 'would be' : ''} copied to /agents for browsing`);
@@ -2554,8 +2143,8 @@ async function main() {
2554
2143
  if (claudeInitResult && claudeInitResult.createdItems.length > 0) {
2555
2144
  console.log(` 📁 ${claudeInitResult.createdItems.length} items created in .claude directory`);
2556
2145
  }
2557
- if (repositoryContext && !flags.dryRun) {
2558
- console.log(` 🔍 Project context automatically added to CLAUDE.md`);
2146
+ if (codexInitResult && codexInitResult.createdItems.length > 0) {
2147
+ console.log(` 🧠 ${codexInitResult.createdItems.length} items created in .codex directory`);
2559
2148
  }
2560
2149
  }
2561
2150
 
@@ -2565,15 +2154,21 @@ async function main() {
2565
2154
  // Escape project name to prevent command injection
2566
2155
  const escapedProjectName = JSON.stringify(projectName);
2567
2156
  console.log(` cd ${escapedProjectName}`);
2568
- const finalClaudeStatus = checkClaudeCode();
2569
- if (!finalClaudeStatus.isInstalled) {
2157
+ const finalClaudeStatus = includesClaude(aiProvider) ? checkClaudeCode() : { isInstalled: true };
2158
+ if (includesClaude(aiProvider) && !finalClaudeStatus.isInstalled) {
2570
2159
  console.log(' claude init # Initialize Claude Code in the project');
2571
2160
  }
2572
2161
  }
2573
- console.log(' 1. Edit CLAUDE.md to add your project-specific instructions');
2574
- console.log(' 2. Update docs/ROADMAP.md with your project goals');
2575
- console.log(' 3. Update docs/agent-orchestration.md to define agent workflows');
2576
- console.log(' 4. Start creating tickets in the tickets/ directory');
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`);
2577
2172
 
2578
2173
  if (flags.browseAgents) {
2579
2174
  console.log('\n📚 Agent Browse Mode:');
@@ -2589,8 +2184,13 @@ async function main() {
2589
2184
  console.log(' You can compare them with your existing files or copy sections you need');
2590
2185
  }
2591
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
+
2592
2192
  // Ask user if they want the workflow selector hook
2593
- if (setupMode === 'full' && !flags.dryRun) {
2193
+ if (includesClaude(aiProvider) && setupMode === 'full' && !flags.dryRun) {
2594
2194
  const claudeDir = path.join(targetDir, '.claude');
2595
2195
  if (fs.existsSync(claudeDir)) {
2596
2196
  try {
@@ -2688,4 +2288,4 @@ main().catch(err => {
2688
2288
  rl.close();
2689
2289
  }
2690
2290
  process.exit(1);
2691
- });
2291
+ });