claude-cli-advanced-starter-pack 1.0.2 → 1.0.4
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 +32 -15
- 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);
|
|
@@ -1226,17 +1227,12 @@ export default async function ${hookName.replace(/-/g, '_')}(context) {
|
|
|
1226
1227
|
*/
|
|
1227
1228
|
function generateSettingsJson(projectName) {
|
|
1228
1229
|
return JSON.stringify({
|
|
1229
|
-
"$schema": "https://
|
|
1230
|
-
"project": {
|
|
1231
|
-
"name": projectName,
|
|
1232
|
-
"description": "Project configured with Claude CLI Advanced Starter Pack"
|
|
1233
|
-
},
|
|
1230
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
1234
1231
|
"permissions": {
|
|
1235
|
-
"
|
|
1236
|
-
"
|
|
1232
|
+
"allow": [],
|
|
1233
|
+
"deny": []
|
|
1237
1234
|
},
|
|
1238
|
-
"
|
|
1239
|
-
"hooks": []
|
|
1235
|
+
"hooks": {}
|
|
1240
1236
|
}, null, 2);
|
|
1241
1237
|
}
|
|
1242
1238
|
|
|
@@ -1245,12 +1241,12 @@ function generateSettingsJson(projectName) {
|
|
|
1245
1241
|
*/
|
|
1246
1242
|
function generateSettingsLocalJson() {
|
|
1247
1243
|
return JSON.stringify({
|
|
1248
|
-
"$schema": "https://
|
|
1244
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
1249
1245
|
"permissions": {
|
|
1250
|
-
"
|
|
1251
|
-
"
|
|
1246
|
+
"allow": [],
|
|
1247
|
+
"deny": []
|
|
1252
1248
|
},
|
|
1253
|
-
"hooks":
|
|
1249
|
+
"hooks": {}
|
|
1254
1250
|
}, null, 2);
|
|
1255
1251
|
}
|
|
1256
1252
|
|
|
@@ -1528,7 +1524,8 @@ export async function runInit(options = {}) {
|
|
|
1528
1524
|
message: 'How would you like to handle existing commands?',
|
|
1529
1525
|
choices: [
|
|
1530
1526
|
{ name: 'Skip existing - only install new commands (recommended)', value: 'skip' },
|
|
1531
|
-
{ name: 'Overwrite
|
|
1527
|
+
{ name: 'Overwrite with backup - save existing to .claude/backups/ first', value: 'backup' },
|
|
1528
|
+
{ name: 'Overwrite all - replace existing (no backup)', value: 'overwrite' },
|
|
1532
1529
|
{ name: 'Cancel installation', value: 'cancel' },
|
|
1533
1530
|
],
|
|
1534
1531
|
},
|
|
@@ -1539,7 +1536,8 @@ export async function runInit(options = {}) {
|
|
|
1539
1536
|
return;
|
|
1540
1537
|
}
|
|
1541
1538
|
|
|
1542
|
-
overwrite = overwriteChoice === 'overwrite';
|
|
1539
|
+
overwrite = overwriteChoice === 'overwrite' || overwriteChoice === 'backup';
|
|
1540
|
+
const createBackups = overwriteChoice === 'backup';
|
|
1543
1541
|
|
|
1544
1542
|
if (!overwrite) {
|
|
1545
1543
|
// Filter out existing commands (keep only new ones + required)
|
|
@@ -1547,11 +1545,17 @@ export async function runInit(options = {}) {
|
|
|
1547
1545
|
finalCommands.length = 0;
|
|
1548
1546
|
finalCommands.push(...filtered);
|
|
1549
1547
|
console.log(chalk.green(`\n ✓ Will install ${finalCommands.length} new command(s), preserving ${commandsToOverwrite.length} existing`));
|
|
1548
|
+
} else if (createBackups) {
|
|
1549
|
+
console.log(chalk.cyan(`\n ✓ Will backup and overwrite ${commandsToOverwrite.length} existing command(s)`));
|
|
1550
1550
|
} else {
|
|
1551
1551
|
console.log(chalk.yellow(`\n ⚠ Will overwrite ${commandsToOverwrite.length} existing command(s)`));
|
|
1552
1552
|
}
|
|
1553
1553
|
}
|
|
1554
1554
|
|
|
1555
|
+
// Track if we should create backups (set outside the if block for use later)
|
|
1556
|
+
const createBackups = options.backup || (typeof overwrite !== 'undefined' && commandsToOverwrite.length > 0 && !options.force);
|
|
1557
|
+
let backedUpFiles = [];
|
|
1558
|
+
|
|
1555
1559
|
// Step 7: Install commands
|
|
1556
1560
|
console.log(chalk.bold('Step 6: Installing slash commands\n'));
|
|
1557
1561
|
|
|
@@ -1594,6 +1598,14 @@ export async function runInit(options = {}) {
|
|
|
1594
1598
|
}
|
|
1595
1599
|
}
|
|
1596
1600
|
|
|
1601
|
+
// Create backup if overwriting existing file
|
|
1602
|
+
if (existsSync(cmdPath) && createBackups) {
|
|
1603
|
+
const backupPath = createBackup(cmdPath);
|
|
1604
|
+
if (backupPath) {
|
|
1605
|
+
backedUpFiles.push({ original: cmdPath, backup: backupPath });
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1597
1609
|
writeFileSync(cmdPath, content, 'utf8');
|
|
1598
1610
|
installed.push(cmdName);
|
|
1599
1611
|
} catch (error) {
|
|
@@ -1603,6 +1615,11 @@ export async function runInit(options = {}) {
|
|
|
1603
1615
|
|
|
1604
1616
|
spinner.stop();
|
|
1605
1617
|
|
|
1618
|
+
// Show backup summary if any files were backed up
|
|
1619
|
+
if (backedUpFiles.length > 0) {
|
|
1620
|
+
console.log(chalk.cyan(`\n 📁 Backed up ${backedUpFiles.length} file(s) to .claude/backups/`));
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1606
1623
|
// Step 7: Generate INDEX.md
|
|
1607
1624
|
const indexPath = join(commandsDir, 'INDEX.md');
|
|
1608
1625
|
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'));
|