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 +1 -1
- package/src/commands/init.js +24 -2
- package/src/commands/setup-wizard.js +280 -2
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -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
|
|
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'));
|