@@ -32,7 +32,9 @@ import {
3232 linkLocalRepo ,
3333 unlinkLocalRepo ,
3434 getLocalInfo ,
35- generateReadmeFromClsyncJson
35+ generateReadmeFromClsyncJson ,
36+ findClaudeDirs ,
37+ scanLocalClaudeDirs
3638} from "../src/repo-sync.js" ;
3739
3840// Get terminal width
@@ -1434,6 +1436,175 @@ program
14341436 }
14351437 } ) ;
14361438
1439+ // ============================================================================
1440+ // SCAN: Find all .claude directories and run claude command
1441+ // ============================================================================
1442+ program
1443+ . command ( "scan" )
1444+ . description ( "Find all directories with .claude and run claude command on each" )
1445+ . option ( "-p, --path <paths...>" , "Specific paths to search (can specify multiple)" )
1446+ . option ( "-d, --depth <n>" , "Maximum search depth" , "4" )
1447+ . option ( "-e, --exclude <patterns...>" , "Patterns to exclude (e.g., node_modules)" )
1448+ . option ( "-c, --cmd <args>" , "Claude command/prompt to run (default: /review)" )
1449+ . option ( "-l, --list" , "Only list directories, don't run commands (dry run)" )
1450+ . option ( "-i, --interactive" , "Run claude interactively for each directory" )
1451+ . option ( "-v, --verbose" , "Show detailed output" )
1452+ . action ( async ( options ) => {
1453+ try {
1454+ console . log ( chalk . cyan ( '\n 🔍 Scanning for Claude Code Projects\n' ) ) ;
1455+
1456+ const searchPaths = options . path || undefined ;
1457+ const maxDepth = parseInt ( options . depth ) || 4 ;
1458+ const exclude = options . exclude || undefined ;
1459+ const claudeArgs = options . cmd ? options . cmd . split ( ' ' ) : undefined ;
1460+ const dryRun = options . list || false ;
1461+ const verbose = options . verbose || false ;
1462+
1463+ if ( searchPaths ) {
1464+ console . log ( chalk . dim ( ` Search paths: ${ searchPaths . join ( ', ' ) } \n` ) ) ;
1465+ }
1466+
1467+ // First, find all directories
1468+ const spinner = ora ( 'Searching for .claude directories...' ) . start ( ) ;
1469+
1470+ const dirs = await findClaudeDirs ( {
1471+ searchPaths,
1472+ maxDepth,
1473+ exclude
1474+ } ) ;
1475+
1476+ spinner . succeed ( `Found ${ dirs . length } directories with .claude` ) ;
1477+ console . log ( ) ;
1478+
1479+ if ( dirs . length === 0 ) {
1480+ console . log ( chalk . yellow ( ' No directories with .claude found.\n' ) ) ;
1481+ console . log ( chalk . dim ( ' Tips:' ) ) ;
1482+ console . log ( chalk . dim ( ' - Check if you have any Claude Code projects' ) ) ;
1483+ console . log ( chalk . dim ( ' - Try specifying a path: clsync scan -p ~/Projects' ) ) ;
1484+ console . log ( chalk . dim ( ' - Increase search depth: clsync scan -d 6\n' ) ) ;
1485+ process . exit ( 0 ) ;
1486+ }
1487+
1488+ // Display found directories
1489+ console . log ( chalk . white . bold ( ' 📁 Found Projects:\n' ) ) ;
1490+ for ( let i = 0 ; i < dirs . length ; i ++ ) {
1491+ const dir = dirs [ i ] ;
1492+ const homeDir = os . homedir ( ) ;
1493+ const displayPath = dir . startsWith ( homeDir )
1494+ ? dir . replace ( homeDir , '~' )
1495+ : dir ;
1496+ console . log ( chalk . dim ( ` ${ String ( i + 1 ) . padStart ( 2 ) } . ${ displayPath } ` ) ) ;
1497+ }
1498+ console . log ( ) ;
1499+
1500+ if ( dryRun ) {
1501+ console . log ( chalk . dim ( ' (Dry run - no commands executed)\n' ) ) ;
1502+ process . exit ( 0 ) ;
1503+ }
1504+
1505+ // Ask for confirmation if running commands
1506+ const inquirer = await import ( 'inquirer' ) ;
1507+ const { confirm } = await inquirer . default . prompt ( [
1508+ {
1509+ type : 'confirm' ,
1510+ name : 'confirm' ,
1511+ message : `Run claude ${ claudeArgs ? claudeArgs . join ( ' ' ) : '/review' } on all ${ dirs . length } directories?` ,
1512+ default : false
1513+ }
1514+ ] ) ;
1515+
1516+ if ( ! confirm ) {
1517+ console . log ( chalk . dim ( '\n Cancelled.\n' ) ) ;
1518+ process . exit ( 0 ) ;
1519+ }
1520+
1521+ console . log ( ) ;
1522+
1523+ // If interactive mode, run one at a time
1524+ if ( options . interactive ) {
1525+ for ( let i = 0 ; i < dirs . length ; i ++ ) {
1526+ const dir = dirs [ i ] ;
1527+ const homeDir = os . homedir ( ) ;
1528+ const displayPath = dir . startsWith ( homeDir )
1529+ ? dir . replace ( homeDir , '~' )
1530+ : dir ;
1531+
1532+ console . log ( chalk . cyan ( `\n [${ i + 1 } /${ dirs . length } ] 📁 ${ displayPath } \n` ) ) ;
1533+
1534+ const { runThis } = await inquirer . default . prompt ( [
1535+ {
1536+ type : 'confirm' ,
1537+ name : 'runThis' ,
1538+ message : 'Run claude interactively?' ,
1539+ default : true
1540+ }
1541+ ] ) ;
1542+
1543+ if ( runThis ) {
1544+ const { spawn } = await import ( 'child_process' ) ;
1545+ await new Promise ( ( resolve ) => {
1546+ const proc = spawn ( 'claude' , claudeArgs || [ '/review' ] , {
1547+ cwd : dir ,
1548+ stdio : 'inherit' ,
1549+ shell : true
1550+ } ) ;
1551+ proc . on ( 'close' , resolve ) ;
1552+ } ) ;
1553+ }
1554+ }
1555+
1556+ showSuccess ( 'Scan Complete!' ) ;
1557+ } else {
1558+ // Non-interactive batch mode
1559+ const results = await scanLocalClaudeDirs ( {
1560+ searchPaths,
1561+ maxDepth,
1562+ exclude,
1563+ claudeArgs,
1564+ dryRun : false ,
1565+ sequential : true ,
1566+ onProgress : ( msg ) => {
1567+ if ( verbose ) {
1568+ console . log ( chalk . dim ( ` ${ msg } ` ) ) ;
1569+ }
1570+ }
1571+ } ) ;
1572+
1573+ console . log ( chalk . cyan ( '\n 📊 Results:\n' ) ) ;
1574+
1575+ let successCount = 0 ;
1576+ let failCount = 0 ;
1577+
1578+ for ( const result of results . results ) {
1579+ const homeDir = os . homedir ( ) ;
1580+ const displayPath = result . dir . startsWith ( homeDir )
1581+ ? result . dir . replace ( homeDir , '~' )
1582+ : result . dir ;
1583+
1584+ if ( result . success ) {
1585+ console . log ( chalk . green ( ` ✓ ${ displayPath } ` ) ) ;
1586+ successCount ++ ;
1587+ } else {
1588+ console . log ( chalk . red ( ` ✗ ${ displayPath } ` ) ) ;
1589+ if ( verbose && result . error ) {
1590+ console . log ( chalk . dim ( ` Error: ${ result . error } ` ) ) ;
1591+ }
1592+ failCount ++ ;
1593+ }
1594+ }
1595+
1596+ console . log ( ) ;
1597+ console . log ( chalk . dim ( ` Summary: ${ successCount } succeeded, ${ failCount } failed\n` ) ) ;
1598+
1599+ showSuccess ( 'Scan Complete!' ) ;
1600+ }
1601+
1602+ } catch ( error ) {
1603+ showError ( error . message ) ;
1604+ process . exit ( 1 ) ;
1605+ }
1606+ } ) ;
1607+
14371608program . parse ( ) ;
14381609
14391610} // end of else block for interactive mode
0 commit comments