33const fs = require ( 'fs' ) ;
44const path = require ( 'path' ) ;
55const { execSync } = require ( 'child_process' ) ;
6+ const readline = require ( 'readline' ) ;
7+
8+ // Parse command-line arguments
9+ const args = process . argv . slice ( 2 ) ;
10+ const options = {
11+ force : args . includes ( '--force' ) ,
12+ skip : args . includes ( '--skip' ) ,
13+ backup : args . includes ( '--backup' ) ,
14+ help : args . includes ( '--help' ) || args . includes ( '-h' ) ,
15+ interactive : ! args . some ( arg => [ '--force' , '--skip' , '--backup' ] . includes ( arg ) )
16+ } ;
17+
18+ // Show help if requested
19+ if ( options . help ) {
20+ console . log ( '🤖 Claude Code Commands Setup' ) ;
21+ console . log ( '==============================' ) ;
22+ console . log ( '' ) ;
23+ console . log ( 'Usage: npx claude-setup [options]' ) ;
24+ console . log ( '' ) ;
25+ console . log ( 'Options:' ) ;
26+ console . log ( ' --skip Skip existing files (preserve customizations)' ) ;
27+ console . log ( ' --backup Backup existing files before replacing' ) ;
28+ console . log ( ' --force Replace all files (creates backup)' ) ;
29+ console . log ( ' --help Show this help message' ) ;
30+ console . log ( '' ) ;
31+ console . log ( 'Default behavior is interactive - you\'ll be prompted for conflicts.' ) ;
32+ process . exit ( 0 ) ;
33+ }
634
735console . log ( '🤖 Claude Code Commands Setup' ) ;
836console . log ( '==============================' ) ;
937
10- // Safety check: Don't run setup in the claude-setup repo itself
11- const packageJsonPath = path . join ( process . cwd ( ) , 'package.json' ) ;
12- if ( fs . existsSync ( packageJsonPath ) ) {
13- const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) ;
14- if ( packageJson . name === 'claude-setup' ) {
15- console . log ( '' ) ;
16- console . log ( '⚠️ WARNING: Cannot run setup in the claude-setup repository itself!' ) ;
17- console . log ( '' ) ;
18- console . log ( 'This script copies commands FROM this repo TO other projects.' ) ;
19- console . log ( 'Running it here would create duplicate directories.' ) ;
20- console . log ( '' ) ;
21- console . log ( 'To use these commands in another project:' ) ;
22- console . log ( '1. Navigate to your project directory' ) ;
23- console . log ( '2. Run: npx claude-setup' ) ;
24- console . log ( '' ) ;
25- process . exit ( 1 ) ;
38+ // Check if we're in a git repository
39+ function isGitRepo ( ) {
40+ try {
41+ execSync ( 'git status' , { stdio : 'ignore' } ) ;
42+ return true ;
43+ } catch ( e ) {
44+ return false ;
2645 }
2746}
2847
29- // Check if we're in a git repository
30- try {
31- execSync ( 'git status' , { stdio : 'ignore' } ) ;
32- } catch ( e ) {
48+ if ( ! isGitRepo ( ) ) {
3349 console . log ( '❌ Not a git repository. Run "git init" first.' ) ;
3450 process . exit ( 1 ) ;
3551}
3652
37- // Create .claude directory
38- if ( ! fs . existsSync ( '.claude' ) ) {
39- fs . mkdirSync ( '.claude' , { recursive : true } ) ;
40- console . log ( '✅ Created .claude directory' ) ;
53+ // Utility function to get all files recursively
54+ function getAllFiles ( dir , files = [ ] ) {
55+ if ( ! fs . existsSync ( dir ) ) return files ;
56+
57+ const items = fs . readdirSync ( dir ) ;
58+ for ( const item of items ) {
59+ const fullPath = path . join ( dir , item ) ;
60+ if ( fs . statSync ( fullPath ) . isDirectory ( ) ) {
61+ getAllFiles ( fullPath , files ) ;
62+ } else {
63+ files . push ( fullPath ) ;
64+ }
65+ }
66+ return files ;
67+ }
68+
69+ // Detect conflicts between source and target
70+ function detectConflicts ( sourceDir , targetDir ) {
71+ const conflicts = [ ] ;
72+ if ( ! fs . existsSync ( targetDir ) ) return conflicts ;
73+
74+ const sourceFiles = getAllFiles ( sourceDir ) ;
75+ for ( const file of sourceFiles ) {
76+ const relativePath = path . relative ( sourceDir , file ) ;
77+ const targetPath = path . join ( targetDir , relativePath ) ;
78+ if ( fs . existsSync ( targetPath ) ) {
79+ conflicts . push ( relativePath ) ;
80+ }
81+ }
82+ return conflicts ;
4183}
4284
43- // Copy commands
44- const sourceCommands = path . join ( __dirname , '../.claude/commands' ) ;
45- const targetCommands = '.claude/commands' ;
85+ // Prompt user for conflict resolution
86+ async function promptUser ( conflicts ) {
87+ console . log ( '' ) ;
88+ console . log ( `⚠️ Found ${ conflicts . length } existing file(s):` ) ;
89+ // Show first 5 conflicts
90+ conflicts . slice ( 0 , 5 ) . forEach ( file => {
91+ console . log ( ` • ${ file } ` ) ;
92+ } ) ;
93+ if ( conflicts . length > 5 ) {
94+ console . log ( ` ... and ${ conflicts . length - 5 } more` ) ;
95+ }
96+
97+ console . log ( '' ) ;
98+ console . log ( 'How would you like to proceed?' ) ;
99+ console . log ( ' 1. Skip - Keep your existing files (recommended)' ) ;
100+ console . log ( ' 2. Backup - Backup existing and install fresh' ) ;
101+ console . log ( ' 3. Merge - Add only non-conflicting files' ) ;
102+ console . log ( ' 4. Cancel - Exit without changes' ) ;
103+ console . log ( '' ) ;
104+
105+ const rl = readline . createInterface ( {
106+ input : process . stdin ,
107+ output : process . stdout
108+ } ) ;
109+
110+ return new Promise ( ( resolve ) => {
111+ rl . question ( 'Choice [1-4]: ' , ( answer ) => {
112+ rl . close ( ) ;
113+ switch ( answer . trim ( ) ) {
114+ case '1' : resolve ( 'skip' ) ; break ;
115+ case '2' : resolve ( 'backup' ) ; break ;
116+ case '3' : resolve ( 'merge' ) ; break ;
117+ case '4' : resolve ( 'cancel' ) ; break ;
118+ default :
119+ console . log ( 'Invalid choice, using "skip" as default' ) ;
120+ resolve ( 'skip' ) ;
121+ }
122+ } ) ;
123+ } ) ;
124+ }
46125
47- if ( fs . existsSync ( sourceCommands ) ) {
48- execSync ( `cp -r "${ sourceCommands } " "${ targetCommands } "` ) ;
49- console . log ( '✅ Copied commands' ) ;
50- } else {
51- console . log ( '❌ Source commands not found' ) ;
52- process . exit ( 1 ) ;
126+ // Determine resolution strategy
127+ async function determineStrategy ( conflicts , options ) {
128+ if ( conflicts . length === 0 ) return 'proceed' ;
129+
130+ if ( options . skip ) return 'skip' ;
131+ if ( options . backup ) return 'backup' ;
132+ if ( options . force ) return 'backup' ; // Force is like backup but less interactive
133+ if ( options . interactive ) return await promptUser ( conflicts ) ;
134+
135+ return 'skip' ; // Default safe behavior
53136}
54137
55- // Copy agents
56- const sourceAgents = path . join ( __dirname , '../.claude/agents' ) ;
57- const targetAgents = '.claude/agents' ;
138+ // Copy directory recursively
139+ function copyRecursive ( source , target , skipExisting = false ) {
140+ // Create target directory if it doesn't exist
141+ if ( ! fs . existsSync ( target ) ) {
142+ fs . mkdirSync ( target , { recursive : true } ) ;
143+ }
144+
145+ const items = fs . readdirSync ( source ) ;
146+ let copied = 0 ;
147+ let skipped = 0 ;
148+
149+ for ( const item of items ) {
150+ const sourcePath = path . join ( source , item ) ;
151+ const targetPath = path . join ( target , item ) ;
152+
153+ if ( fs . statSync ( sourcePath ) . isDirectory ( ) ) {
154+ const result = copyRecursive ( sourcePath , targetPath , skipExisting ) ;
155+ copied += result . copied ;
156+ skipped += result . skipped ;
157+ } else {
158+ if ( skipExisting && fs . existsSync ( targetPath ) ) {
159+ skipped ++ ;
160+ } else {
161+ fs . copyFileSync ( sourcePath , targetPath ) ;
162+ copied ++ ;
163+ }
164+ }
165+ }
166+
167+ return { copied, skipped } ;
168+ }
58169
59- if ( fs . existsSync ( sourceAgents ) ) {
60- execSync ( `cp -r "${ sourceAgents } " "${ targetAgents } "` ) ;
61- console . log ( '✅ Copied agents' ) ;
170+ // Backup existing directory
171+ function backupDirectory ( dir ) {
172+ if ( ! fs . existsSync ( dir ) ) return null ;
173+
174+ const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, '-' ) . slice ( 0 , - 5 ) ;
175+ const backupPath = `${ dir } .backup-${ timestamp } ` ;
176+ fs . renameSync ( dir , backupPath ) ;
177+ return backupPath ;
62178}
63179
64- // Copy essential files
65- const filesToCopy = [ 'CLAUDE.md' , 'AGENTS.md' , 'ACTIVE_WORK.md' ] ;
180+ // Safe copy with conflict resolution
181+ function safeCopy ( source , target , strategy , name ) {
182+ let result = { copied : 0 , skipped : 0 , backedUp : null } ;
183+
184+ switch ( strategy ) {
185+ case 'proceed' :
186+ // No conflicts, just copy
187+ result = copyRecursive ( source , target ) ;
188+ break ;
189+
190+ case 'skip' :
191+ // Skip all conflicts
192+ console . log ( ` ⏭️ Preserving existing ${ name } ` ) ;
193+ result . skipped = getAllFiles ( source ) . length ;
194+ break ;
195+
196+ case 'backup' :
197+ // Backup existing and copy fresh
198+ if ( fs . existsSync ( target ) ) {
199+ result . backedUp = backupDirectory ( target ) ;
200+ console . log ( ` 📦 Backed up to ${ path . basename ( result . backedUp ) } ` ) ;
201+ }
202+ result = copyRecursive ( source , target ) ;
203+ break ;
204+
205+ case 'merge' :
206+ // Only copy non-conflicting files
207+ result = copyRecursive ( source , target , true ) ;
208+ break ;
209+
210+ case 'cancel' :
211+ console . log ( '❌ Setup cancelled' ) ;
212+ process . exit ( 0 ) ;
213+ break ;
214+ }
215+
216+ return result ;
217+ }
66218
67- filesToCopy . forEach ( file => {
68- const sourcePath = path . join ( __dirname , '..' , file ) ;
69- if ( fs . existsSync ( sourcePath ) && ! fs . existsSync ( file ) ) {
70- fs . copyFileSync ( sourcePath , file ) ;
71- console . log ( `✅ Created ${ file } ` ) ;
219+ // Main setup function
220+ async function main ( ) {
221+ // Define source and target paths
222+ const sourceCommands = path . join ( __dirname , '../.claude/commands' ) ;
223+ const targetCommands = '.claude/commands' ;
224+ const sourceAgents = path . join ( __dirname , '../.claude/agents' ) ;
225+ const targetAgents = '.claude/agents' ;
226+
227+ // Check if source files exist
228+ if ( ! fs . existsSync ( sourceCommands ) ) {
229+ console . log ( '❌ Source commands not found. Make sure you\'re running from the correct package.' ) ;
230+ process . exit ( 1 ) ;
231+ }
232+
233+ // Create .claude directory if it doesn't exist
234+ if ( ! fs . existsSync ( '.claude' ) ) {
235+ fs . mkdirSync ( '.claude' , { recursive : true } ) ;
236+ console . log ( '✅ Created .claude directory' ) ;
237+ }
238+
239+ // Detect conflicts
240+ const commandConflicts = detectConflicts ( sourceCommands , targetCommands ) ;
241+ const agentConflicts = detectConflicts ( sourceAgents , targetAgents ) ;
242+ const allConflicts = [ ...new Set ( [ ...commandConflicts , ...agentConflicts ] ) ] ;
243+
244+ // Determine strategy
245+ const strategy = await determineStrategy ( allConflicts , options ) ;
246+
247+ // Copy commands
248+ console . log ( '\n📦 Installing Claude commands...' ) ;
249+ const commandResult = safeCopy ( sourceCommands , targetCommands , strategy , 'commands' ) ;
250+ if ( commandResult . copied > 0 ) {
251+ console . log ( ` ✅ Installed ${ commandResult . copied } command files` ) ;
252+ }
253+ if ( commandResult . skipped > 0 ) {
254+ console . log ( ` ⏭️ Skipped ${ commandResult . skipped } existing files` ) ;
72255 }
73- } ) ;
256+
257+ // Copy agents
258+ let agentResult = { copied : 0 , skipped : 0 , backedUp : null } ;
259+ if ( fs . existsSync ( sourceAgents ) ) {
260+ console . log ( '\n📦 Installing Claude agents...' ) ;
261+ agentResult = safeCopy ( sourceAgents , targetAgents , strategy , 'agents' ) ;
262+ if ( agentResult . copied > 0 ) {
263+ console . log ( ` ✅ Installed ${ agentResult . copied } agent files` ) ;
264+ }
265+ if ( agentResult . skipped > 0 ) {
266+ console . log ( ` ⏭️ Skipped ${ agentResult . skipped } existing files` ) ;
267+ }
268+ }
269+
270+ // Copy essential root files (only if they don't exist)
271+ console . log ( '\n📦 Setting up configuration files...' ) ;
272+ const filesToCopy = [ 'CLAUDE.md' , 'AGENTS.md' , 'ACTIVE_WORK.md' ] ;
273+ let configCopied = 0 ;
274+
275+ filesToCopy . forEach ( file => {
276+ const sourcePath = path . join ( __dirname , '..' , file ) ;
277+ if ( fs . existsSync ( sourcePath ) && ! fs . existsSync ( file ) ) {
278+ fs . copyFileSync ( sourcePath , file ) ;
279+ console . log ( ` ✅ Created ${ file } ` ) ;
280+ configCopied ++ ;
281+ }
282+ } ) ;
283+
284+ if ( configCopied === 0 ) {
285+ console . log ( ' ℹ️ Configuration files already exist' ) ;
286+ }
287+
288+ // Final message
289+ console . log ( '' ) ;
290+ console . log ( '🎉 Setup complete!' ) ;
291+ console . log ( '' ) ;
292+
293+ if ( strategy === 'skip' || strategy === 'merge' ) {
294+ console . log ( 'ℹ️ Some files were preserved to keep your customizations' ) ;
295+ console . log ( '' ) ;
296+ }
297+
298+ console . log ( 'Next steps:' ) ;
299+ console . log ( '• Review .claude/commands/ for available commands' ) ;
300+ console . log ( '• Use /hygiene to check project health' ) ;
301+ console . log ( '• Use /todo to manage tasks' ) ;
302+ console . log ( '• Customize commands for your specific needs' ) ;
303+
304+ if ( commandResult . backedUp || agentResult . backedUp ) {
305+ console . log ( '' ) ;
306+ console . log ( '📝 Note: Your previous setup was backed up' ) ;
307+ }
308+ }
74309
75- console . log ( '' ) ;
76- console . log ( '🎉 Setup complete!' ) ;
77- console . log ( '' ) ;
78- console . log ( 'Next steps:' ) ;
79- console . log ( '• Use /hygiene to check project health' ) ;
80- console . log ( '• Use /todo to manage tasks' ) ;
81- console . log ( '• Read AGENTS.md to understand agents vs commands' ) ;
82- console . log ( '• Customize commands in .claude/commands/ for your project' ) ;
310+ // Run the setup
311+ main ( ) . catch ( error => {
312+ console . error ( '❌ Setup failed:' , error . message ) ;
313+ process . exit ( 1 ) ;
314+ } ) ;
0 commit comments