claude-cli-advanced-starter-pack 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cli-advanced-starter-pack",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Advanced Claude Code CLI toolkit - agents, hooks, skills, MCP servers, phased development, and GitHub integration",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -14,6 +14,7 @@ import { join, dirname, basename } from 'path';
14
14
  import { fileURLToPath } from 'url';
15
15
  import { showHeader, showSuccess, showError, showWarning, showInfo } from '../cli/menu.js';
16
16
  import { getVersion } from '../utils.js';
17
+ import { createBackup } from './setup-wizard.js';
17
18
 
18
19
  const __filename = fileURLToPath(import.meta.url);
19
20
  const __dirname = dirname(__filename);
@@ -1528,7 +1529,8 @@ export async function runInit(options = {}) {
1528
1529
  message: 'How would you like to handle existing commands?',
1529
1530
  choices: [
1530
1531
  { name: 'Skip existing - only install new commands (recommended)', value: 'skip' },
1531
- { name: 'Overwrite all - replace existing with starter pack versions', value: 'overwrite' },
1532
+ { name: 'Overwrite with backup - save existing to .claude/backups/ first', value: 'backup' },
1533
+ { name: 'Overwrite all - replace existing (no backup)', value: 'overwrite' },
1532
1534
  { name: 'Cancel installation', value: 'cancel' },
1533
1535
  ],
1534
1536
  },
@@ -1539,7 +1541,8 @@ export async function runInit(options = {}) {
1539
1541
  return;
1540
1542
  }
1541
1543
 
1542
- overwrite = overwriteChoice === 'overwrite';
1544
+ overwrite = overwriteChoice === 'overwrite' || overwriteChoice === 'backup';
1545
+ const createBackups = overwriteChoice === 'backup';
1543
1546
 
1544
1547
  if (!overwrite) {
1545
1548
  // Filter out existing commands (keep only new ones + required)
@@ -1547,11 +1550,17 @@ export async function runInit(options = {}) {
1547
1550
  finalCommands.length = 0;
1548
1551
  finalCommands.push(...filtered);
1549
1552
  console.log(chalk.green(`\n ✓ Will install ${finalCommands.length} new command(s), preserving ${commandsToOverwrite.length} existing`));
1553
+ } else if (createBackups) {
1554
+ console.log(chalk.cyan(`\n ✓ Will backup and overwrite ${commandsToOverwrite.length} existing command(s)`));
1550
1555
  } else {
1551
1556
  console.log(chalk.yellow(`\n ⚠ Will overwrite ${commandsToOverwrite.length} existing command(s)`));
1552
1557
  }
1553
1558
  }
1554
1559
 
1560
+ // Track if we should create backups (set outside the if block for use later)
1561
+ const createBackups = options.backup || (typeof overwrite !== 'undefined' && commandsToOverwrite.length > 0 && !options.force);
1562
+ let backedUpFiles = [];
1563
+
1555
1564
  // Step 7: Install commands
1556
1565
  console.log(chalk.bold('Step 6: Installing slash commands\n'));
1557
1566
 
@@ -1594,6 +1603,14 @@ export async function runInit(options = {}) {
1594
1603
  }
1595
1604
  }
1596
1605
 
1606
+ // Create backup if overwriting existing file
1607
+ if (existsSync(cmdPath) && createBackups) {
1608
+ const backupPath = createBackup(cmdPath);
1609
+ if (backupPath) {
1610
+ backedUpFiles.push({ original: cmdPath, backup: backupPath });
1611
+ }
1612
+ }
1613
+
1597
1614
  writeFileSync(cmdPath, content, 'utf8');
1598
1615
  installed.push(cmdName);
1599
1616
  } catch (error) {
@@ -1603,6 +1620,11 @@ export async function runInit(options = {}) {
1603
1620
 
1604
1621
  spinner.stop();
1605
1622
 
1623
+ // Show backup summary if any files were backed up
1624
+ if (backedUpFiles.length > 0) {
1625
+ console.log(chalk.cyan(`\n 📁 Backed up ${backedUpFiles.length} file(s) to .claude/backups/`));
1626
+ }
1627
+
1606
1628
  // Step 7: Generate INDEX.md
1607
1629
  const indexPath = join(commandsDir, 'INDEX.md');
1608
1630
  const indexContent = generateIndexFile(installed, projectName);
@@ -9,8 +9,8 @@ import inquirer from 'inquirer';
9
9
  import chalk from 'chalk';
10
10
  import ora from 'ora';
11
11
  import boxen from 'boxen';
12
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
13
- import { join } from 'path';
12
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync, renameSync, copyFileSync } from 'fs';
13
+ import { join, basename } from 'path';
14
14
  import { runInit } from './init.js';
15
15
  import { detectTechStack } from './detect-tech-stack.js';
16
16
  import { runClaudeAudit, runEnhancement } from './claude-audit.js';
@@ -18,6 +18,272 @@ import { runSetup as runGitHubSetup } from './setup.js';
18
18
  import { runList } from './list.js';
19
19
  import { showProjectSettingsMenu } from '../cli/menu.js';
20
20
 
21
+ /**
22
+ * Create backup of a file before overwriting
23
+ * @param {string} filePath - Path to file to backup
24
+ * @returns {string|null} - Path to backup file, or null if no backup needed
25
+ */
26
+ export function createBackup(filePath) {
27
+ if (!existsSync(filePath)) return null;
28
+
29
+ const backupDir = join(process.cwd(), '.claude', 'backups');
30
+ if (!existsSync(backupDir)) {
31
+ mkdirSync(backupDir, { recursive: true });
32
+ }
33
+
34
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
35
+ const fileName = basename(filePath);
36
+ const backupPath = join(backupDir, `${fileName}.${timestamp}.bak`);
37
+
38
+ copyFileSync(filePath, backupPath);
39
+ return backupPath;
40
+ }
41
+
42
+ /**
43
+ * Remove CCASP from a project
44
+ */
45
+ async function runRemove() {
46
+ const claudeDir = join(process.cwd(), '.claude');
47
+
48
+ if (!existsSync(claudeDir)) {
49
+ console.log(chalk.yellow('\n⚠️ No .claude folder found in this project.\n'));
50
+ return false;
51
+ }
52
+
53
+ // Show what will be removed
54
+ console.log(chalk.bold('\n📁 CCASP files found in this project:\n'));
55
+
56
+ const itemsToRemove = [];
57
+
58
+ // Check for commands
59
+ const commandsDir = join(claudeDir, 'commands');
60
+ if (existsSync(commandsDir)) {
61
+ const commands = readdirSync(commandsDir).filter(f => f.endsWith('.md'));
62
+ if (commands.length > 0) {
63
+ console.log(` ${chalk.cyan('commands/')} - ${commands.length} command(s)`);
64
+ itemsToRemove.push({ type: 'dir', path: commandsDir, label: 'commands/' });
65
+ }
66
+ }
67
+
68
+ // Check for agents
69
+ const agentsDir = join(claudeDir, 'agents');
70
+ if (existsSync(agentsDir)) {
71
+ const agents = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
72
+ if (agents.length > 0) {
73
+ console.log(` ${chalk.cyan('agents/')} - ${agents.length} agent(s)`);
74
+ itemsToRemove.push({ type: 'dir', path: agentsDir, label: 'agents/' });
75
+ }
76
+ }
77
+
78
+ // Check for skills
79
+ const skillsDir = join(claudeDir, 'skills');
80
+ if (existsSync(skillsDir)) {
81
+ const skills = readdirSync(skillsDir).filter(f => !f.startsWith('.'));
82
+ if (skills.length > 0) {
83
+ console.log(` ${chalk.cyan('skills/')} - ${skills.length} skill(s)`);
84
+ itemsToRemove.push({ type: 'dir', path: skillsDir, label: 'skills/' });
85
+ }
86
+ }
87
+
88
+ // Check for hooks
89
+ const hooksDir = join(claudeDir, 'hooks');
90
+ if (existsSync(hooksDir)) {
91
+ const hooks = readdirSync(hooksDir).filter(f => f.endsWith('.js'));
92
+ if (hooks.length > 0) {
93
+ console.log(` ${chalk.cyan('hooks/')} - ${hooks.length} hook(s)`);
94
+ itemsToRemove.push({ type: 'dir', path: hooksDir, label: 'hooks/' });
95
+ }
96
+ }
97
+
98
+ // Check for docs
99
+ const docsDir = join(claudeDir, 'docs');
100
+ if (existsSync(docsDir)) {
101
+ console.log(` ${chalk.cyan('docs/')} - documentation`);
102
+ itemsToRemove.push({ type: 'dir', path: docsDir, label: 'docs/' });
103
+ }
104
+
105
+ // Check for config files
106
+ const configFiles = ['settings.json', 'settings.local.json', 'tech-stack.json'];
107
+ for (const file of configFiles) {
108
+ const filePath = join(claudeDir, file);
109
+ if (existsSync(filePath)) {
110
+ console.log(` ${chalk.cyan(file)}`);
111
+ itemsToRemove.push({ type: 'file', path: filePath, label: file });
112
+ }
113
+ }
114
+
115
+ if (itemsToRemove.length === 0) {
116
+ console.log(chalk.yellow(' No CCASP items found.\n'));
117
+ return false;
118
+ }
119
+
120
+ console.log('');
121
+
122
+ // Removal options
123
+ const { removeAction } = await inquirer.prompt([
124
+ {
125
+ type: 'list',
126
+ name: 'removeAction',
127
+ message: 'What would you like to do?',
128
+ choices: [
129
+ {
130
+ name: `${chalk.red('1.')} Remove ALL ${chalk.dim('- Delete entire .claude folder')}`,
131
+ value: 'all',
132
+ short: 'Remove All',
133
+ },
134
+ {
135
+ name: `${chalk.yellow('2.')} Remove with backup ${chalk.dim('- Backup to .claude-backup/ first')}`,
136
+ value: 'backup',
137
+ short: 'Backup & Remove',
138
+ },
139
+ {
140
+ name: `${chalk.cyan('3.')} Selective removal ${chalk.dim('- Choose what to remove')}`,
141
+ value: 'selective',
142
+ short: 'Selective',
143
+ },
144
+ {
145
+ name: `${chalk.green('0.')} Cancel ${chalk.dim('- Keep everything')}`,
146
+ value: 'cancel',
147
+ short: 'Cancel',
148
+ },
149
+ ],
150
+ },
151
+ ]);
152
+
153
+ if (removeAction === 'cancel') {
154
+ console.log(chalk.dim('\nCancelled. No changes made.\n'));
155
+ return false;
156
+ }
157
+
158
+ if (removeAction === 'backup' || removeAction === 'all') {
159
+ // Confirm dangerous action
160
+ const { confirmRemove } = await inquirer.prompt([
161
+ {
162
+ type: 'confirm',
163
+ name: 'confirmRemove',
164
+ message: chalk.red(`Are you sure you want to ${removeAction === 'all' ? 'DELETE' : 'backup and delete'} all CCASP files?`),
165
+ default: false,
166
+ },
167
+ ]);
168
+
169
+ if (!confirmRemove) {
170
+ console.log(chalk.dim('\nCancelled. No changes made.\n'));
171
+ return false;
172
+ }
173
+ }
174
+
175
+ const spinner = ora('Processing...').start();
176
+
177
+ try {
178
+ if (removeAction === 'backup') {
179
+ // Create backup first
180
+ const backupDir = join(process.cwd(), '.claude-backup', new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19));
181
+ mkdirSync(backupDir, { recursive: true });
182
+
183
+ // Copy entire .claude folder to backup
184
+ copyDirRecursive(claudeDir, backupDir);
185
+ spinner.succeed(`Backed up to ${chalk.cyan(backupDir)}`);
186
+
187
+ // Then remove
188
+ spinner.start('Removing .claude folder...');
189
+ rmSync(claudeDir, { recursive: true, force: true });
190
+ spinner.succeed('.claude folder removed');
191
+
192
+ console.log(
193
+ boxen(
194
+ chalk.green('✅ CCASP removed with backup\n\n') +
195
+ `Backup location:\n${chalk.cyan(backupDir)}\n\n` +
196
+ chalk.dim('To restore: copy backup contents to .claude/'),
197
+ {
198
+ padding: 1,
199
+ borderStyle: 'round',
200
+ borderColor: 'green',
201
+ }
202
+ )
203
+ );
204
+ } else if (removeAction === 'all') {
205
+ // Remove without backup
206
+ rmSync(claudeDir, { recursive: true, force: true });
207
+ spinner.succeed('.claude folder removed');
208
+
209
+ console.log(
210
+ boxen(
211
+ chalk.green('✅ CCASP removed\n\n') +
212
+ chalk.dim('Run ') + chalk.cyan('ccasp wizard') + chalk.dim(' to set up again.'),
213
+ {
214
+ padding: 1,
215
+ borderStyle: 'round',
216
+ borderColor: 'green',
217
+ }
218
+ )
219
+ );
220
+ } else if (removeAction === 'selective') {
221
+ spinner.stop();
222
+
223
+ // Let user select what to remove
224
+ const { itemsToDelete } = await inquirer.prompt([
225
+ {
226
+ type: 'checkbox',
227
+ name: 'itemsToDelete',
228
+ message: 'Select items to remove:',
229
+ choices: itemsToRemove.map(item => ({
230
+ name: item.label,
231
+ value: item,
232
+ checked: false,
233
+ })),
234
+ },
235
+ ]);
236
+
237
+ if (itemsToDelete.length === 0) {
238
+ console.log(chalk.dim('\nNo items selected. No changes made.\n'));
239
+ return false;
240
+ }
241
+
242
+ // Create backups for selected items
243
+ const backupDir = join(process.cwd(), '.claude', 'backups', new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19));
244
+ mkdirSync(backupDir, { recursive: true });
245
+
246
+ for (const item of itemsToDelete) {
247
+ if (item.type === 'dir') {
248
+ copyDirRecursive(item.path, join(backupDir, basename(item.path)));
249
+ rmSync(item.path, { recursive: true, force: true });
250
+ } else {
251
+ copyFileSync(item.path, join(backupDir, basename(item.path)));
252
+ rmSync(item.path, { force: true });
253
+ }
254
+ console.log(` ${chalk.red('✗')} Removed ${item.label}`);
255
+ }
256
+
257
+ console.log(chalk.dim(`\nBackups saved to: ${backupDir}\n`));
258
+ }
259
+
260
+ return true;
261
+ } catch (error) {
262
+ spinner.fail('Removal failed');
263
+ console.error(chalk.red(error.message));
264
+ return false;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Recursively copy a directory
270
+ */
271
+ function copyDirRecursive(src, dest) {
272
+ mkdirSync(dest, { recursive: true });
273
+ const entries = readdirSync(src, { withFileTypes: true });
274
+
275
+ for (const entry of entries) {
276
+ const srcPath = join(src, entry.name);
277
+ const destPath = join(dest, entry.name);
278
+
279
+ if (entry.isDirectory()) {
280
+ copyDirRecursive(srcPath, destPath);
281
+ } else {
282
+ copyFileSync(srcPath, destPath);
283
+ }
284
+ }
285
+ }
286
+
21
287
  /**
22
288
  * Display setup header
23
289
  */
@@ -79,6 +345,11 @@ const SETUP_OPTIONS = [
79
345
  value: 'settings',
80
346
  short: 'Settings',
81
347
  },
348
+ {
349
+ name: `${chalk.yellow('9.')} Remove CCASP ${chalk.dim('- Uninstall from this project')}`,
350
+ value: 'remove',
351
+ short: 'Remove',
352
+ },
82
353
  {
83
354
  name: `${chalk.yellow('0.')} Exit`,
84
355
  value: 'exit',
@@ -435,6 +706,13 @@ export async function runSetupWizard(options = {}) {
435
706
  }
436
707
  break;
437
708
 
709
+ case 'remove':
710
+ const removed = await runRemove();
711
+ if (removed) {
712
+ running = false; // Exit wizard after removal
713
+ }
714
+ break;
715
+
438
716
  case 'exit':
439
717
  running = false;
440
718
  console.log(chalk.dim('\nRun `ccasp wizard` anytime to return.\n'));