apero-kit-cli 1.0.0

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.
@@ -0,0 +1,223 @@
1
+ import fs from 'fs-extra';
2
+ import { join, resolve } from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { KITS, getKit } from '../kits/index.js';
6
+ import { resolveSource, getTargetDir, TARGETS } from '../utils/paths.js';
7
+ import { copyItems, copyAllOfType, copyRouter, copyHooks, copyBaseFiles, copyAgentsMd } from '../utils/copy.js';
8
+ import { createInitialState } from '../utils/state.js';
9
+ import {
10
+ promptProjectName,
11
+ promptKit,
12
+ promptTarget,
13
+ promptAgents,
14
+ promptSkills,
15
+ promptCommands,
16
+ promptIncludeRouter,
17
+ promptIncludeHooks,
18
+ promptConfirm
19
+ } from '../utils/prompts.js';
20
+
21
+ export async function initCommand(projectName, options) {
22
+ console.log('');
23
+
24
+ // 1. Get project name
25
+ if (!projectName) {
26
+ projectName = await promptProjectName();
27
+ }
28
+
29
+ const projectDir = resolve(process.cwd(), projectName);
30
+
31
+ // Check if directory exists
32
+ if (fs.existsSync(projectDir) && !options.force) {
33
+ const files = fs.readdirSync(projectDir);
34
+ if (files.length > 0) {
35
+ console.log(chalk.red(`Directory "${projectName}" already exists and is not empty.`));
36
+ console.log(chalk.gray('Use --force to overwrite.'));
37
+ return;
38
+ }
39
+ }
40
+
41
+ // 2. Resolve source
42
+ const source = resolveSource(options.source);
43
+ if (source.error) {
44
+ console.log(chalk.red(`Error: ${source.error}`));
45
+ return;
46
+ }
47
+
48
+ console.log(chalk.gray(`Source: ${source.path}`));
49
+
50
+ // 3. Get kit
51
+ let kitName = options.kit;
52
+ if (!kitName) {
53
+ kitName = await promptKit();
54
+ }
55
+
56
+ // 4. Get target
57
+ let target = options.target || 'claude';
58
+ if (!TARGETS[target]) {
59
+ console.log(chalk.yellow(`Unknown target "${target}", using "claude"`));
60
+ target = 'claude';
61
+ }
62
+
63
+ // 5. Prepare what to install
64
+ let toInstall = {
65
+ agents: [],
66
+ commands: [],
67
+ skills: [],
68
+ workflows: [],
69
+ includeRouter: false,
70
+ includeHooks: false
71
+ };
72
+
73
+ if (kitName === 'custom') {
74
+ // Custom mode - prompt for everything
75
+ console.log(chalk.cyan('\nCustom kit configuration:'));
76
+ toInstall.agents = await promptAgents(source.claudeDir);
77
+ toInstall.skills = await promptSkills(source.claudeDir);
78
+ toInstall.commands = await promptCommands(source.claudeDir);
79
+ toInstall.includeRouter = await promptIncludeRouter();
80
+ toInstall.includeHooks = await promptIncludeHooks();
81
+ } else {
82
+ const kit = getKit(kitName);
83
+ if (!kit) {
84
+ console.log(chalk.red(`Unknown kit: ${kitName}`));
85
+ console.log(chalk.gray(`Available kits: ${Object.keys(KITS).join(', ')}`));
86
+ return;
87
+ }
88
+
89
+ toInstall = {
90
+ agents: kit.agents,
91
+ commands: kit.commands,
92
+ skills: kit.skills,
93
+ workflows: kit.workflows || [],
94
+ includeRouter: kit.includeRouter,
95
+ includeHooks: kit.includeHooks
96
+ };
97
+ }
98
+
99
+ // 6. Confirm
100
+ console.log(chalk.cyan('\nWill create:'));
101
+ console.log(chalk.white(` Project: ${projectName}/`));
102
+ console.log(chalk.white(` Target: ${TARGETS[target]}/`));
103
+ console.log(chalk.white(` Kit: ${kitName}`));
104
+
105
+ if (Array.isArray(toInstall.agents)) {
106
+ console.log(chalk.gray(` Agents: ${toInstall.agents.length}`));
107
+ }
108
+ if (Array.isArray(toInstall.skills)) {
109
+ console.log(chalk.gray(` Skills: ${toInstall.skills.length}`));
110
+ }
111
+ if (Array.isArray(toInstall.commands)) {
112
+ console.log(chalk.gray(` Commands: ${toInstall.commands.length}`));
113
+ }
114
+
115
+ console.log('');
116
+
117
+ if (!await promptConfirm('Proceed?')) {
118
+ console.log(chalk.yellow('Cancelled.'));
119
+ return;
120
+ }
121
+
122
+ // 7. Create project
123
+ const spinner = ora('Creating project...').start();
124
+
125
+ try {
126
+ // Create project directory
127
+ await fs.ensureDir(projectDir);
128
+
129
+ // Create target directory
130
+ const targetDir = getTargetDir(projectDir, target);
131
+ await fs.ensureDir(targetDir);
132
+
133
+ // Copy agents
134
+ spinner.text = 'Copying agents...';
135
+ if (toInstall.agents === 'all') {
136
+ await copyAllOfType('agents', source.claudeDir, targetDir);
137
+ } else if (toInstall.agents.length > 0) {
138
+ await copyItems(toInstall.agents, 'agents', source.claudeDir, targetDir);
139
+ }
140
+
141
+ // Copy skills
142
+ spinner.text = 'Copying skills...';
143
+ if (toInstall.skills === 'all') {
144
+ await copyAllOfType('skills', source.claudeDir, targetDir);
145
+ } else if (toInstall.skills.length > 0) {
146
+ await copyItems(toInstall.skills, 'skills', source.claudeDir, targetDir);
147
+ }
148
+
149
+ // Copy commands
150
+ spinner.text = 'Copying commands...';
151
+ if (toInstall.commands === 'all') {
152
+ await copyAllOfType('commands', source.claudeDir, targetDir);
153
+ } else if (toInstall.commands.length > 0) {
154
+ await copyItems(toInstall.commands, 'commands', source.claudeDir, targetDir);
155
+ }
156
+
157
+ // Copy workflows
158
+ spinner.text = 'Copying workflows...';
159
+ if (toInstall.workflows === 'all') {
160
+ await copyAllOfType('workflows', source.claudeDir, targetDir);
161
+ } else if (toInstall.workflows && toInstall.workflows.length > 0) {
162
+ await copyItems(toInstall.workflows, 'workflows', source.claudeDir, targetDir);
163
+ }
164
+
165
+ // Copy router
166
+ if (toInstall.includeRouter) {
167
+ spinner.text = 'Copying router...';
168
+ await copyRouter(source.claudeDir, targetDir);
169
+ }
170
+
171
+ // Copy hooks
172
+ if (toInstall.includeHooks) {
173
+ spinner.text = 'Copying hooks...';
174
+ await copyHooks(source.claudeDir, targetDir);
175
+ }
176
+
177
+ // Copy base files
178
+ spinner.text = 'Copying base files...';
179
+ await copyBaseFiles(source.claudeDir, targetDir);
180
+
181
+ // Copy AGENTS.md
182
+ if (source.agentsMd) {
183
+ await copyAgentsMd(source.agentsMd, projectDir);
184
+ }
185
+
186
+ // Create state file
187
+ spinner.text = 'Saving state...';
188
+ await createInitialState(projectDir, {
189
+ kit: kitName,
190
+ source: source.path,
191
+ target: TARGETS[target],
192
+ installed: {
193
+ agents: toInstall.agents === 'all' ? ['all'] : toInstall.agents,
194
+ skills: toInstall.skills === 'all' ? ['all'] : toInstall.skills,
195
+ commands: toInstall.commands === 'all' ? ['all'] : toInstall.commands,
196
+ workflows: toInstall.workflows === 'all' ? ['all'] : (toInstall.workflows || []),
197
+ router: toInstall.includeRouter,
198
+ hooks: toInstall.includeHooks
199
+ }
200
+ });
201
+
202
+ spinner.succeed(chalk.green('Project created successfully!'));
203
+
204
+ // Print next steps
205
+ console.log('');
206
+ console.log(chalk.cyan('Next steps:'));
207
+ console.log(chalk.white(` cd ${projectName}`));
208
+ console.log(chalk.white(' # Start coding with Claude Code'));
209
+ console.log('');
210
+ console.log(chalk.gray('Useful commands:'));
211
+ console.log(chalk.gray(' ak status - Check file status'));
212
+ console.log(chalk.gray(' ak add <item> - Add more agents/skills'));
213
+ console.log(chalk.gray(' ak update - Sync from source'));
214
+ console.log('');
215
+
216
+ } catch (error) {
217
+ spinner.fail(chalk.red('Failed to create project'));
218
+ console.error(chalk.red(error.message));
219
+ if (process.env.DEBUG) {
220
+ console.error(error.stack);
221
+ }
222
+ }
223
+ }
@@ -0,0 +1,190 @@
1
+ import chalk from 'chalk';
2
+ import { KITS, getKitList } from '../kits/index.js';
3
+ import { resolveSource } from '../utils/paths.js';
4
+ import { listAvailable } from '../utils/copy.js';
5
+
6
+ export async function listCommand(type, options = {}) {
7
+ const validTypes = ['kits', 'agents', 'skills', 'commands', 'workflows'];
8
+
9
+ // If no type specified, show help
10
+ if (!type) {
11
+ console.log(chalk.cyan('\nAvailable list commands:'));
12
+ console.log(chalk.white(' ak list kits - List available kits'));
13
+ console.log(chalk.white(' ak list agents - List available agents'));
14
+ console.log(chalk.white(' ak list skills - List available skills'));
15
+ console.log(chalk.white(' ak list commands - List available commands'));
16
+ console.log(chalk.white(' ak list workflows - List available workflows'));
17
+ console.log('');
18
+ return;
19
+ }
20
+
21
+ // Normalize type
22
+ type = type.toLowerCase();
23
+ if (!validTypes.includes(type)) {
24
+ console.log(chalk.red(`Unknown type: ${type}`));
25
+ console.log(chalk.gray(`Valid types: ${validTypes.join(', ')}`));
26
+ return;
27
+ }
28
+
29
+ // List kits (doesn't need source)
30
+ if (type === 'kits') {
31
+ listKits();
32
+ return;
33
+ }
34
+
35
+ // For other types, need source
36
+ const source = resolveSource(options.source);
37
+ if (source.error) {
38
+ console.log(chalk.red(`Error: ${source.error}`));
39
+ return;
40
+ }
41
+
42
+ console.log(chalk.gray(`Source: ${source.path}\n`));
43
+
44
+ switch (type) {
45
+ case 'agents':
46
+ listAgents(source.claudeDir);
47
+ break;
48
+ case 'skills':
49
+ listSkills(source.claudeDir);
50
+ break;
51
+ case 'commands':
52
+ listCommands(source.claudeDir);
53
+ break;
54
+ case 'workflows':
55
+ listWorkflows(source.claudeDir);
56
+ break;
57
+ }
58
+ }
59
+
60
+ function listKits() {
61
+ const kits = getKitList();
62
+
63
+ console.log(chalk.cyan.bold('\nAvailable Kits:\n'));
64
+
65
+ for (const kit of kits) {
66
+ const colorFn = chalk[kit.color] || chalk.white;
67
+ console.log(` ${kit.emoji} ${colorFn.bold(kit.name.padEnd(12))} - ${kit.description}`);
68
+
69
+ // Show details
70
+ if (Array.isArray(kit.agents)) {
71
+ console.log(chalk.gray(` Agents: ${kit.agents.length} | Skills: ${kit.skills.length} | Commands: ${kit.commands.length}`));
72
+ } else {
73
+ console.log(chalk.gray(' Includes: ALL agents, skills, commands'));
74
+ }
75
+ }
76
+
77
+ console.log(`\n 🔧 ${chalk.bold('custom'.padEnd(12))} - Pick your own agents, skills, and commands`);
78
+ console.log('');
79
+ }
80
+
81
+ function listAgents(sourceDir) {
82
+ const agents = listAvailable('agents', sourceDir);
83
+
84
+ console.log(chalk.cyan.bold(`Available Agents (${agents.length}):\n`));
85
+
86
+ if (agents.length === 0) {
87
+ console.log(chalk.gray(' No agents found'));
88
+ return;
89
+ }
90
+
91
+ // Group into columns
92
+ const cols = 3;
93
+ const rows = Math.ceil(agents.length / cols);
94
+
95
+ for (let i = 0; i < rows; i++) {
96
+ let line = ' ';
97
+ for (let j = 0; j < cols; j++) {
98
+ const idx = i + j * rows;
99
+ if (idx < agents.length) {
100
+ line += chalk.white(agents[idx].name.padEnd(25));
101
+ }
102
+ }
103
+ console.log(line);
104
+ }
105
+ console.log('');
106
+ }
107
+
108
+ function listSkills(sourceDir) {
109
+ const skills = listAvailable('skills', sourceDir).filter(s => s.isDir);
110
+
111
+ console.log(chalk.cyan.bold(`Available Skills (${skills.length}):\n`));
112
+
113
+ if (skills.length === 0) {
114
+ console.log(chalk.gray(' No skills found'));
115
+ return;
116
+ }
117
+
118
+ // Group into columns
119
+ const cols = 2;
120
+ const rows = Math.ceil(skills.length / cols);
121
+
122
+ for (let i = 0; i < rows; i++) {
123
+ let line = ' ';
124
+ for (let j = 0; j < cols; j++) {
125
+ const idx = i + j * rows;
126
+ if (idx < skills.length) {
127
+ line += chalk.white(skills[idx].name.padEnd(35));
128
+ }
129
+ }
130
+ console.log(line);
131
+ }
132
+ console.log('');
133
+ }
134
+
135
+ function listCommands(sourceDir) {
136
+ const commands = listAvailable('commands', sourceDir);
137
+
138
+ console.log(chalk.cyan.bold(`Available Commands (${commands.length}):\n`));
139
+
140
+ if (commands.length === 0) {
141
+ console.log(chalk.gray(' No commands found'));
142
+ return;
143
+ }
144
+
145
+ // Separate files and directories
146
+ const files = commands.filter(c => !c.isDir);
147
+ const dirs = commands.filter(c => c.isDir);
148
+
149
+ // Print main commands (files)
150
+ console.log(chalk.gray(' Main commands:'));
151
+ const cols = 4;
152
+ let rows = Math.ceil(files.length / cols);
153
+
154
+ for (let i = 0; i < rows; i++) {
155
+ let line = ' ';
156
+ for (let j = 0; j < cols; j++) {
157
+ const idx = i + j * rows;
158
+ if (idx < files.length) {
159
+ line += chalk.white(('/' + files[idx].name).padEnd(18));
160
+ }
161
+ }
162
+ console.log(line);
163
+ }
164
+
165
+ // Print command groups (directories)
166
+ if (dirs.length > 0) {
167
+ console.log(chalk.gray('\n Command groups:'));
168
+ for (const dir of dirs) {
169
+ console.log(chalk.yellow(` /${dir.name}/`));
170
+ }
171
+ }
172
+
173
+ console.log('');
174
+ }
175
+
176
+ function listWorkflows(sourceDir) {
177
+ const workflows = listAvailable('workflows', sourceDir);
178
+
179
+ console.log(chalk.cyan.bold(`Available Workflows (${workflows.length}):\n`));
180
+
181
+ if (workflows.length === 0) {
182
+ console.log(chalk.gray(' No workflows found'));
183
+ return;
184
+ }
185
+
186
+ for (const wf of workflows) {
187
+ console.log(chalk.white(` • ${wf.name}`));
188
+ }
189
+ console.log('');
190
+ }
@@ -0,0 +1,113 @@
1
+ import chalk from 'chalk';
2
+ import { isAkProject } from '../utils/paths.js';
3
+ import { loadState, getFileStatuses } from '../utils/state.js';
4
+ import fs from 'fs-extra';
5
+ import { join } from 'path';
6
+
7
+ export async function statusCommand(options = {}) {
8
+ const projectDir = process.cwd();
9
+
10
+ // Check if in ak project
11
+ if (!isAkProject(projectDir)) {
12
+ console.log(chalk.red('Not in an ak project.'));
13
+ console.log(chalk.gray('Run "ak init" first.'));
14
+ return;
15
+ }
16
+
17
+ // Load state
18
+ const state = await loadState(projectDir);
19
+ if (!state) {
20
+ console.log(chalk.yellow('No state file found.'));
21
+ console.log(chalk.gray('This project may have been created without ak.'));
22
+ return;
23
+ }
24
+
25
+ // Print project info
26
+ console.log(chalk.cyan.bold('\nProject Status\n'));
27
+ console.log(chalk.white(` Kit: ${state.kit}`));
28
+ console.log(chalk.white(` Target: ${state.target}`));
29
+ console.log(chalk.white(` Source: ${state.source}`));
30
+ console.log(chalk.gray(` Created: ${new Date(state.createdAt).toLocaleDateString()}`));
31
+ console.log(chalk.gray(` Updated: ${new Date(state.lastUpdate).toLocaleDateString()}`));
32
+ console.log('');
33
+
34
+ // Get file statuses
35
+ const result = await getFileStatuses(projectDir);
36
+
37
+ if (result.error) {
38
+ console.log(chalk.red(`Error: ${result.error}`));
39
+ return;
40
+ }
41
+
42
+ const { statuses } = result;
43
+
44
+ // Summary
45
+ const total = statuses.unchanged.length + statuses.modified.length + statuses.added.length;
46
+ console.log(chalk.cyan('File Status:'));
47
+ console.log(chalk.green(` ✓ ${statuses.unchanged.length} unchanged`));
48
+ if (statuses.modified.length > 0) {
49
+ console.log(chalk.yellow(` ~ ${statuses.modified.length} modified`));
50
+ }
51
+ if (statuses.added.length > 0) {
52
+ console.log(chalk.blue(` + ${statuses.added.length} added locally`));
53
+ }
54
+ if (statuses.deleted.length > 0) {
55
+ console.log(chalk.red(` - ${statuses.deleted.length} deleted`));
56
+ }
57
+ console.log(chalk.gray(` Total: ${total} files tracked`));
58
+ console.log('');
59
+
60
+ // Show modified files
61
+ if (statuses.modified.length > 0 && (options.verbose || statuses.modified.length <= 10)) {
62
+ console.log(chalk.yellow('Modified files:'));
63
+ for (const file of statuses.modified) {
64
+ console.log(chalk.yellow(` ~ ${file}`));
65
+ }
66
+ console.log('');
67
+ } else if (statuses.modified.length > 10) {
68
+ console.log(chalk.yellow(`Modified files: (showing first 10, use --verbose for all)`));
69
+ for (const file of statuses.modified.slice(0, 10)) {
70
+ console.log(chalk.yellow(` ~ ${file}`));
71
+ }
72
+ console.log(chalk.gray(` ... and ${statuses.modified.length - 10} more`));
73
+ console.log('');
74
+ }
75
+
76
+ // Show added files
77
+ if (statuses.added.length > 0 && (options.verbose || statuses.added.length <= 5)) {
78
+ console.log(chalk.blue('Added locally:'));
79
+ for (const file of statuses.added) {
80
+ console.log(chalk.blue(` + ${file}`));
81
+ }
82
+ console.log('');
83
+ }
84
+
85
+ // Installed components
86
+ if (state.installed) {
87
+ console.log(chalk.cyan('Installed Components:'));
88
+ const { agents, skills, commands, workflows, router, hooks } = state.installed;
89
+
90
+ if (agents && agents.length > 0) {
91
+ console.log(chalk.gray(` Agents: ${agents.includes('all') ? 'ALL' : agents.length}`));
92
+ }
93
+ if (skills && skills.length > 0) {
94
+ console.log(chalk.gray(` Skills: ${skills.includes('all') ? 'ALL' : skills.length}`));
95
+ }
96
+ if (commands && commands.length > 0) {
97
+ console.log(chalk.gray(` Commands: ${commands.includes('all') ? 'ALL' : commands.length}`));
98
+ }
99
+ if (workflows && workflows.length > 0) {
100
+ console.log(chalk.gray(` Workflows: ${workflows.includes('all') ? 'ALL' : workflows.length}`));
101
+ }
102
+ if (router) console.log(chalk.gray(' Router: ✓'));
103
+ if (hooks) console.log(chalk.gray(' Hooks: ✓'));
104
+ console.log('');
105
+ }
106
+
107
+ // Check source availability
108
+ if (state.source && !fs.existsSync(state.source)) {
109
+ console.log(chalk.yellow('⚠ Source directory not found. Update may not work.'));
110
+ console.log(chalk.gray(` Expected: ${state.source}`));
111
+ console.log('');
112
+ }
113
+ }
@@ -0,0 +1,183 @@
1
+ import fs from 'fs-extra';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { resolveSource, isAkProject } from '../utils/paths.js';
6
+ import { loadState, updateState, getFileStatuses } from '../utils/state.js';
7
+ import { hashFile, hashDirectory } from '../utils/hash.js';
8
+ import { promptUpdateConfirm } from '../utils/prompts.js';
9
+
10
+ export async function updateCommand(options = {}) {
11
+ const projectDir = process.cwd();
12
+
13
+ // Check if in ak project
14
+ if (!isAkProject(projectDir)) {
15
+ console.log(chalk.red('Not in an ak project.'));
16
+ console.log(chalk.gray('Run "ak init" first.'));
17
+ return;
18
+ }
19
+
20
+ // Load state
21
+ const state = await loadState(projectDir);
22
+ if (!state) {
23
+ console.log(chalk.red('No state file found.'));
24
+ console.log(chalk.gray('This project may have been created without ak. Run "ak doctor" for more info.'));
25
+ return;
26
+ }
27
+
28
+ // Resolve source
29
+ const sourceFlag = options.source || state.source;
30
+ const source = resolveSource(sourceFlag);
31
+ if (source.error) {
32
+ console.log(chalk.red(`Error: ${source.error}`));
33
+ return;
34
+ }
35
+
36
+ console.log(chalk.gray(`Source: ${source.path}`));
37
+ console.log(chalk.gray(`Target: ${state.target}`));
38
+ console.log('');
39
+
40
+ const spinner = ora('Checking for updates...').start();
41
+
42
+ try {
43
+ // Get current file statuses
44
+ const { statuses, targetDir } = await getFileStatuses(projectDir);
45
+
46
+ // Determine what types to update
47
+ let typesToUpdate = ['agents', 'skills', 'commands', 'workflows'];
48
+ if (options.agents) typesToUpdate = ['agents'];
49
+ if (options.skills) typesToUpdate = ['skills'];
50
+ if (options.commands) typesToUpdate = ['commands'];
51
+
52
+ // Get source hashes
53
+ const sourceHashes = {};
54
+ for (const type of typesToUpdate) {
55
+ const typeDir = join(source.claudeDir, type);
56
+ if (fs.existsSync(typeDir)) {
57
+ const hashes = await hashDirectory(typeDir);
58
+ for (const [path, hash] of Object.entries(hashes)) {
59
+ sourceHashes[`${type}/${path}`] = hash;
60
+ }
61
+ }
62
+ }
63
+
64
+ // Compare with current
65
+ const toUpdate = [];
66
+ const skipped = [];
67
+ const newFiles = [];
68
+
69
+ for (const [path, sourceHash] of Object.entries(sourceHashes)) {
70
+ const currentPath = join(targetDir, path);
71
+ const originalHash = state.originalHashes?.[path];
72
+ const currentHash = fs.existsSync(currentPath) ? hashFile(currentPath) : null;
73
+
74
+ if (!currentHash) {
75
+ // New file from source
76
+ newFiles.push(path);
77
+ } else if (currentHash === originalHash) {
78
+ // File unchanged locally, can update
79
+ if (sourceHash !== currentHash) {
80
+ toUpdate.push(path);
81
+ }
82
+ } else {
83
+ // File modified locally, skip
84
+ if (sourceHash !== originalHash) {
85
+ skipped.push(path);
86
+ }
87
+ }
88
+ }
89
+
90
+ spinner.stop();
91
+
92
+ // Show summary
93
+ if (toUpdate.length === 0 && newFiles.length === 0) {
94
+ console.log(chalk.green('✓ Already up to date!'));
95
+ if (skipped.length > 0) {
96
+ console.log(chalk.yellow(` ${skipped.length} file(s) skipped (modified locally)`));
97
+ }
98
+ return;
99
+ }
100
+
101
+ console.log(chalk.cyan('Updates available:'));
102
+ console.log(chalk.green(` ${toUpdate.length} file(s) to update`));
103
+ console.log(chalk.blue(` ${newFiles.length} new file(s)`));
104
+ if (skipped.length > 0) {
105
+ console.log(chalk.yellow(` ${skipped.length} file(s) skipped (modified locally)`));
106
+ }
107
+ console.log('');
108
+
109
+ // Dry run mode
110
+ if (options.dryRun) {
111
+ console.log(chalk.cyan('Dry run - no changes made'));
112
+ console.log(chalk.gray('\nFiles that would be updated:'));
113
+ [...toUpdate, ...newFiles].forEach(f => console.log(chalk.gray(` ${f}`)));
114
+ if (skipped.length > 0) {
115
+ console.log(chalk.gray('\nFiles that would be skipped:'));
116
+ skipped.forEach(f => console.log(chalk.yellow(` ~ ${f}`)));
117
+ }
118
+ return;
119
+ }
120
+
121
+ // Confirm
122
+ if (!options.force) {
123
+ const confirmed = await promptUpdateConfirm({
124
+ toUpdate: [...toUpdate, ...newFiles],
125
+ skipped
126
+ });
127
+ if (!confirmed) {
128
+ console.log(chalk.yellow('Cancelled.'));
129
+ return;
130
+ }
131
+ }
132
+
133
+ // Apply updates
134
+ const updateSpinner = ora('Applying updates...').start();
135
+
136
+ let updated = 0;
137
+ let failed = 0;
138
+
139
+ for (const path of [...toUpdate, ...newFiles]) {
140
+ try {
141
+ const srcPath = join(source.claudeDir, path);
142
+ const destPath = join(targetDir, path);
143
+
144
+ await fs.ensureDir(join(targetDir, path.split('/').slice(0, -1).join('/')));
145
+ await fs.copy(srcPath, destPath, { overwrite: true });
146
+ updated++;
147
+ } catch (err) {
148
+ failed++;
149
+ if (process.env.DEBUG) {
150
+ console.error(`Failed to update ${path}: ${err.message}`);
151
+ }
152
+ }
153
+ }
154
+
155
+ // Update state with new hashes
156
+ const newHashes = await hashDirectory(targetDir);
157
+ await updateState(projectDir, {
158
+ source: source.path,
159
+ originalHashes: newHashes
160
+ });
161
+
162
+ updateSpinner.succeed(chalk.green(`Updated ${updated} file(s)`));
163
+
164
+ if (failed > 0) {
165
+ console.log(chalk.yellow(` ${failed} file(s) failed to update`));
166
+ }
167
+
168
+ if (skipped.length > 0) {
169
+ console.log(chalk.gray(`\nSkipped files (modified locally):`));
170
+ skipped.slice(0, 5).forEach(f => console.log(chalk.yellow(` ~ ${f}`)));
171
+ if (skipped.length > 5) {
172
+ console.log(chalk.gray(` ... and ${skipped.length - 5} more`));
173
+ }
174
+ }
175
+
176
+ } catch (error) {
177
+ spinner.fail(chalk.red('Update failed'));
178
+ console.error(chalk.red(error.message));
179
+ if (process.env.DEBUG) {
180
+ console.error(error.stack);
181
+ }
182
+ }
183
+ }