claude-cli-advanced-starter-pack 1.8.2 → 1.8.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/README.md +6 -2
- package/bin/gtask.js +35 -0
- package/package.json +1 -1
- package/src/commands/global-reinstall.js +243 -0
- package/src/commands/global-uninstall.js +229 -0
- package/src/commands/init.js +224 -0
- package/src/commands/uninstall.js +414 -0
- package/src/data/releases.json +15 -0
- package/src/utils/global-registry.js +230 -0
- package/src/utils/paths.js +146 -0
- package/templates/commands/create-task-list-for-issue.template.md +216 -0
- package/templates/commands/create-task-list.template.md +280 -82
- package/templates/commands/menu-issues-list.template.md +288 -0
package/src/commands/init.js
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
generateMergeExplanation,
|
|
29
29
|
formatMergeOptions,
|
|
30
30
|
} from '../utils/smart-merge.js';
|
|
31
|
+
import { registerProject } from '../utils/global-registry.js';
|
|
32
|
+
import { replacePlaceholders } from '../utils/template-engine.js';
|
|
31
33
|
|
|
32
34
|
const __filename = fileURLToPath(import.meta.url);
|
|
33
35
|
const __dirname = dirname(__filename);
|
|
@@ -169,6 +171,18 @@ const AVAILABLE_COMMANDS = [
|
|
|
169
171
|
category: 'GitHub',
|
|
170
172
|
selected: true,
|
|
171
173
|
},
|
|
174
|
+
{
|
|
175
|
+
name: 'menu-issues-list',
|
|
176
|
+
description: 'Mobile-friendly menu of open GitHub issues',
|
|
177
|
+
category: 'GitHub',
|
|
178
|
+
selected: true,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'create-task-list-for-issue',
|
|
182
|
+
description: 'Start working on a GitHub issue by number',
|
|
183
|
+
category: 'GitHub',
|
|
184
|
+
selected: true,
|
|
185
|
+
},
|
|
172
186
|
{
|
|
173
187
|
name: 'phase-dev-plan',
|
|
174
188
|
description: 'Create phased development plans (95%+ success rate)',
|
|
@@ -1410,10 +1424,206 @@ function generateSettingsLocalJson() {
|
|
|
1410
1424
|
}, null, 2);
|
|
1411
1425
|
}
|
|
1412
1426
|
|
|
1427
|
+
/**
|
|
1428
|
+
* Run dev mode - rapid template testing workflow
|
|
1429
|
+
* Loads existing tech-stack.json, processes templates, overwrites commands
|
|
1430
|
+
*/
|
|
1431
|
+
async function runDevMode(options = {}) {
|
|
1432
|
+
const cwd = process.cwd();
|
|
1433
|
+
const projectName = basename(cwd);
|
|
1434
|
+
const claudeDir = join(cwd, '.claude');
|
|
1435
|
+
const commandsDir = join(claudeDir, 'commands');
|
|
1436
|
+
const hooksDir = join(claudeDir, 'hooks');
|
|
1437
|
+
const configDir = join(claudeDir, 'config');
|
|
1438
|
+
const techStackPath = join(configDir, 'tech-stack.json');
|
|
1439
|
+
|
|
1440
|
+
console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
1441
|
+
console.log(chalk.magenta.bold(' 🔧 DEV MODE - Template Testing'));
|
|
1442
|
+
console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
1443
|
+
console.log('');
|
|
1444
|
+
console.log(chalk.cyan(` Project: ${chalk.bold(projectName)}`));
|
|
1445
|
+
console.log(chalk.cyan(` Location: ${cwd}`));
|
|
1446
|
+
console.log('');
|
|
1447
|
+
|
|
1448
|
+
// Load existing tech-stack.json
|
|
1449
|
+
let techStack = {};
|
|
1450
|
+
if (existsSync(techStackPath)) {
|
|
1451
|
+
try {
|
|
1452
|
+
techStack = JSON.parse(readFileSync(techStackPath, 'utf8'));
|
|
1453
|
+
console.log(chalk.green(' ✓ Loaded existing tech-stack.json'));
|
|
1454
|
+
} catch (err) {
|
|
1455
|
+
console.log(chalk.yellow(` ⚠ Could not parse tech-stack.json: ${err.message}`));
|
|
1456
|
+
}
|
|
1457
|
+
} else {
|
|
1458
|
+
console.log(chalk.yellow(' ⚠ No tech-stack.json found - templates will have unprocessed placeholders'));
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// Ensure directories exist
|
|
1462
|
+
if (!existsSync(commandsDir)) {
|
|
1463
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
1464
|
+
console.log(chalk.green(' ✓ Created .claude/commands/'));
|
|
1465
|
+
}
|
|
1466
|
+
if (!existsSync(hooksDir)) {
|
|
1467
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
1468
|
+
console.log(chalk.green(' ✓ Created .claude/hooks/'));
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// Identify custom commands (no matching template) to preserve
|
|
1472
|
+
const templatesDir = join(__dirname, '..', '..', 'templates', 'commands');
|
|
1473
|
+
const hooksTemplatesDir = join(__dirname, '..', '..', 'templates', 'hooks');
|
|
1474
|
+
|
|
1475
|
+
const templateCommandNames = existsSync(templatesDir)
|
|
1476
|
+
? readdirSync(templatesDir).filter(f => f.endsWith('.template.md')).map(f => f.replace('.template.md', ''))
|
|
1477
|
+
: [];
|
|
1478
|
+
const templateHookNames = existsSync(hooksTemplatesDir)
|
|
1479
|
+
? readdirSync(hooksTemplatesDir).filter(f => f.endsWith('.template.js')).map(f => f.replace('.template.js', ''))
|
|
1480
|
+
: [];
|
|
1481
|
+
|
|
1482
|
+
// Find existing custom commands (those without matching templates)
|
|
1483
|
+
const existingCommands = existsSync(commandsDir)
|
|
1484
|
+
? readdirSync(commandsDir).filter(f => f.endsWith('.md')).map(f => f.replace('.md', ''))
|
|
1485
|
+
: [];
|
|
1486
|
+
const customCommands = existingCommands.filter(cmd =>
|
|
1487
|
+
!templateCommandNames.includes(cmd) &&
|
|
1488
|
+
cmd !== 'menu' &&
|
|
1489
|
+
cmd !== 'INDEX' &&
|
|
1490
|
+
cmd !== 'README'
|
|
1491
|
+
);
|
|
1492
|
+
|
|
1493
|
+
if (customCommands.length > 0) {
|
|
1494
|
+
console.log(chalk.blue(` 📌 Preserving ${customCommands.length} custom command(s):`));
|
|
1495
|
+
for (const cmd of customCommands) {
|
|
1496
|
+
console.log(chalk.dim(` • /${cmd}`));
|
|
1497
|
+
}
|
|
1498
|
+
console.log('');
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
console.log(chalk.bold('Processing and deploying templates...\n'));
|
|
1502
|
+
|
|
1503
|
+
const spinner = ora('Processing templates...').start();
|
|
1504
|
+
const deployed = { commands: [], hooks: [], preserved: customCommands };
|
|
1505
|
+
const failed = [];
|
|
1506
|
+
|
|
1507
|
+
// Get all command templates
|
|
1508
|
+
if (existsSync(templatesDir)) {
|
|
1509
|
+
const templateFiles = readdirSync(templatesDir).filter(f => f.endsWith('.template.md'));
|
|
1510
|
+
|
|
1511
|
+
for (const templateFile of templateFiles) {
|
|
1512
|
+
const cmdName = templateFile.replace('.template.md', '');
|
|
1513
|
+
const templatePath = join(templatesDir, templateFile);
|
|
1514
|
+
const outputPath = join(commandsDir, `${cmdName}.md`);
|
|
1515
|
+
|
|
1516
|
+
try {
|
|
1517
|
+
let content = readFileSync(templatePath, 'utf8');
|
|
1518
|
+
|
|
1519
|
+
// Process template with tech-stack values
|
|
1520
|
+
const { content: processed, warnings } = replacePlaceholders(content, techStack, {
|
|
1521
|
+
preserveUnknown: false,
|
|
1522
|
+
warnOnMissing: false,
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
writeFileSync(outputPath, processed, 'utf8');
|
|
1526
|
+
deployed.commands.push(cmdName);
|
|
1527
|
+
} catch (err) {
|
|
1528
|
+
failed.push({ name: cmdName, type: 'command', error: err.message });
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// Also process hook templates
|
|
1534
|
+
if (existsSync(hooksTemplatesDir)) {
|
|
1535
|
+
const hookFiles = readdirSync(hooksTemplatesDir).filter(f => f.endsWith('.template.js'));
|
|
1536
|
+
|
|
1537
|
+
for (const hookFile of hookFiles) {
|
|
1538
|
+
const hookName = hookFile.replace('.template.js', '');
|
|
1539
|
+
const templatePath = join(hooksTemplatesDir, hookFile);
|
|
1540
|
+
const outputPath = join(hooksDir, `${hookName}.js`);
|
|
1541
|
+
|
|
1542
|
+
try {
|
|
1543
|
+
let content = readFileSync(templatePath, 'utf8');
|
|
1544
|
+
|
|
1545
|
+
// Process template with tech-stack values
|
|
1546
|
+
const { content: processed } = replacePlaceholders(content, techStack, {
|
|
1547
|
+
preserveUnknown: false,
|
|
1548
|
+
warnOnMissing: false,
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
writeFileSync(outputPath, processed, 'utf8');
|
|
1552
|
+
deployed.hooks.push(hookName);
|
|
1553
|
+
} catch (err) {
|
|
1554
|
+
failed.push({ name: hookName, type: 'hook', error: err.message });
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Generate menu command from scratch (uses COMMAND_TEMPLATES)
|
|
1560
|
+
const menuTemplate = COMMAND_TEMPLATES['menu'];
|
|
1561
|
+
if (menuTemplate) {
|
|
1562
|
+
const installedAgents = existsSync(join(claudeDir, 'agents'))
|
|
1563
|
+
? readdirSync(join(claudeDir, 'agents')).filter(f => f.endsWith('.md')).map(f => f.replace('.md', ''))
|
|
1564
|
+
: [];
|
|
1565
|
+
const installedSkills = existsSync(join(claudeDir, 'skills'))
|
|
1566
|
+
? readdirSync(join(claudeDir, 'skills')).filter(f => !f.startsWith('.'))
|
|
1567
|
+
: [];
|
|
1568
|
+
const installedHooks = existsSync(hooksDir)
|
|
1569
|
+
? readdirSync(hooksDir).filter(f => f.endsWith('.js')).map(f => f.replace('.js', ''))
|
|
1570
|
+
: [];
|
|
1571
|
+
|
|
1572
|
+
const menuContent = generateMenuCommand(projectName, deployed.commands, installedAgents, installedSkills, installedHooks);
|
|
1573
|
+
writeFileSync(join(commandsDir, 'menu.md'), menuContent, 'utf8');
|
|
1574
|
+
deployed.commands.push('menu');
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// Generate INDEX.md
|
|
1578
|
+
const indexContent = generateIndexFile(deployed.commands, projectName);
|
|
1579
|
+
writeFileSync(join(commandsDir, 'INDEX.md'), indexContent, 'utf8');
|
|
1580
|
+
|
|
1581
|
+
// Generate README.md
|
|
1582
|
+
const readmeContent = generateReadmeFile(deployed.commands, projectName);
|
|
1583
|
+
writeFileSync(join(commandsDir, 'README.md'), readmeContent, 'utf8');
|
|
1584
|
+
|
|
1585
|
+
spinner.stop();
|
|
1586
|
+
|
|
1587
|
+
// Summary
|
|
1588
|
+
console.log('');
|
|
1589
|
+
console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
1590
|
+
console.log(chalk.green.bold(' ✓ DEV MODE: Templates Deployed'));
|
|
1591
|
+
console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
1592
|
+
console.log('');
|
|
1593
|
+
console.log(chalk.cyan(` Commands: ${deployed.commands.length} deployed`));
|
|
1594
|
+
console.log(chalk.cyan(` Hooks: ${deployed.hooks.length} deployed`));
|
|
1595
|
+
if (deployed.preserved && deployed.preserved.length > 0) {
|
|
1596
|
+
console.log(chalk.blue(` Custom: ${deployed.preserved.length} preserved`));
|
|
1597
|
+
}
|
|
1598
|
+
if (failed.length > 0) {
|
|
1599
|
+
console.log(chalk.yellow(` Failed: ${failed.length}`));
|
|
1600
|
+
for (const f of failed) {
|
|
1601
|
+
console.log(chalk.red(` • ${f.type}/${f.name}: ${f.error}`));
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
console.log('');
|
|
1605
|
+
console.log(chalk.dim(' tech-stack.json: Preserved'));
|
|
1606
|
+
console.log(chalk.dim(' settings.json: Preserved'));
|
|
1607
|
+
if (deployed.preserved && deployed.preserved.length > 0) {
|
|
1608
|
+
console.log(chalk.dim(` Custom commands: ${deployed.preserved.join(', ')}`));
|
|
1609
|
+
}
|
|
1610
|
+
console.log('');
|
|
1611
|
+
console.log(chalk.yellow.bold(' ⚠ Restart Claude Code CLI to use new commands'));
|
|
1612
|
+
console.log('');
|
|
1613
|
+
|
|
1614
|
+
return { deployed, failed };
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1413
1617
|
/**
|
|
1414
1618
|
* Run the init wizard
|
|
1415
1619
|
*/
|
|
1416
1620
|
export async function runInit(options = {}) {
|
|
1621
|
+
// DEV MODE: Fast path for template testing
|
|
1622
|
+
if (options.dev) {
|
|
1623
|
+
showHeader('Claude CLI Advanced Starter Pack - DEV MODE');
|
|
1624
|
+
return runDevMode(options);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1417
1627
|
showHeader('Claude CLI Advanced Starter Pack - Project Setup');
|
|
1418
1628
|
|
|
1419
1629
|
const cwd = process.cwd();
|
|
@@ -2416,6 +2626,20 @@ export async function runInit(options = {}) {
|
|
|
2416
2626
|
writeFileSync(ccaspStatePath, JSON.stringify(ccaspState, null, 2), 'utf8');
|
|
2417
2627
|
console.log(chalk.green(` ✓ Updated ccasp-state.json (v${currentVersion})`));
|
|
2418
2628
|
|
|
2629
|
+
// Register project in global registry (unless --no-register flag is set)
|
|
2630
|
+
if (!options.noRegister) {
|
|
2631
|
+
const isNewProject = registerProject(cwd, {
|
|
2632
|
+
name: projectName,
|
|
2633
|
+
version: currentVersion,
|
|
2634
|
+
features: selectedFeatures
|
|
2635
|
+
});
|
|
2636
|
+
if (isNewProject) {
|
|
2637
|
+
console.log(chalk.green(` ✓ Registered project in global CCASP registry`));
|
|
2638
|
+
} else {
|
|
2639
|
+
console.log(chalk.dim(` ○ Updated project in global CCASP registry`));
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2419
2643
|
// Show next steps
|
|
2420
2644
|
console.log(chalk.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
2421
2645
|
console.log(chalk.bold('Next Steps:\n'));
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uninstall Command
|
|
3
|
+
*
|
|
4
|
+
* Fully removes CCASP from a project directory and restores backups.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, unlinkSync, rmdirSync, copyFileSync, statSync } from 'fs';
|
|
9
|
+
import { join, basename, dirname } from 'path';
|
|
10
|
+
import { createInterface } from 'readline';
|
|
11
|
+
import { unregisterProject } from '../utils/global-registry.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Prompt user for confirmation
|
|
15
|
+
*/
|
|
16
|
+
function confirm(question) {
|
|
17
|
+
const rl = createInterface({
|
|
18
|
+
input: process.stdin,
|
|
19
|
+
output: process.stdout
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
rl.question(question, (answer) => {
|
|
24
|
+
rl.close();
|
|
25
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Find all backup files in .claude/backups/
|
|
32
|
+
* Returns map of original filename -> most recent backup path
|
|
33
|
+
*/
|
|
34
|
+
function findBackups(projectDir) {
|
|
35
|
+
const backupDir = join(projectDir, '.claude', 'backups');
|
|
36
|
+
const backups = new Map();
|
|
37
|
+
|
|
38
|
+
if (!existsSync(backupDir)) {
|
|
39
|
+
return backups;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const files = readdirSync(backupDir);
|
|
44
|
+
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
if (!file.endsWith('.bak')) continue;
|
|
47
|
+
|
|
48
|
+
// Parse filename: original.YYYY-MM-DDTHH-MM-SS.bak
|
|
49
|
+
const match = file.match(/^(.+)\.(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})\.bak$/);
|
|
50
|
+
if (!match) continue;
|
|
51
|
+
|
|
52
|
+
const [, originalName, timestamp] = match;
|
|
53
|
+
const backupPath = join(backupDir, file);
|
|
54
|
+
|
|
55
|
+
// Keep only the most recent backup for each file
|
|
56
|
+
if (!backups.has(originalName) || backups.get(originalName).timestamp < timestamp) {
|
|
57
|
+
backups.set(originalName, {
|
|
58
|
+
path: backupPath,
|
|
59
|
+
timestamp,
|
|
60
|
+
originalName
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.log(chalk.yellow(` Warning: Could not read backups directory: ${err.message}`));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return backups;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get list of CCASP-installed files by checking ccasp-state.json or common patterns
|
|
73
|
+
*/
|
|
74
|
+
function findCcaspFiles(projectDir) {
|
|
75
|
+
const files = {
|
|
76
|
+
commands: [],
|
|
77
|
+
hooks: [],
|
|
78
|
+
skills: [],
|
|
79
|
+
config: [],
|
|
80
|
+
docs: [],
|
|
81
|
+
agents: []
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const claudeDir = join(projectDir, '.claude');
|
|
85
|
+
if (!existsSync(claudeDir)) {
|
|
86
|
+
return files;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Commands
|
|
90
|
+
const commandsDir = join(claudeDir, 'commands');
|
|
91
|
+
if (existsSync(commandsDir)) {
|
|
92
|
+
try {
|
|
93
|
+
const entries = readdirSync(commandsDir);
|
|
94
|
+
files.commands = entries
|
|
95
|
+
.filter(f => f.endsWith('.md'))
|
|
96
|
+
.map(f => join(commandsDir, f));
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Hooks
|
|
101
|
+
const hooksDir = join(claudeDir, 'hooks');
|
|
102
|
+
if (existsSync(hooksDir)) {
|
|
103
|
+
try {
|
|
104
|
+
const entries = readdirSync(hooksDir);
|
|
105
|
+
files.hooks = entries
|
|
106
|
+
.filter(f => f.endsWith('.js'))
|
|
107
|
+
.map(f => join(hooksDir, f));
|
|
108
|
+
} catch {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Skills (directories)
|
|
112
|
+
const skillsDir = join(claudeDir, 'skills');
|
|
113
|
+
if (existsSync(skillsDir)) {
|
|
114
|
+
try {
|
|
115
|
+
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
116
|
+
files.skills = entries
|
|
117
|
+
.filter(e => e.isDirectory())
|
|
118
|
+
.map(e => join(skillsDir, e.name));
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Config
|
|
123
|
+
const configDir = join(claudeDir, 'config');
|
|
124
|
+
if (existsSync(configDir)) {
|
|
125
|
+
try {
|
|
126
|
+
const entries = readdirSync(configDir);
|
|
127
|
+
files.config = entries.map(f => join(configDir, f));
|
|
128
|
+
} catch {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Docs
|
|
132
|
+
const docsDir = join(claudeDir, 'docs');
|
|
133
|
+
if (existsSync(docsDir)) {
|
|
134
|
+
files.docs.push(docsDir);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Agents
|
|
138
|
+
const agentsDir = join(claudeDir, 'agents');
|
|
139
|
+
if (existsSync(agentsDir)) {
|
|
140
|
+
try {
|
|
141
|
+
const entries = readdirSync(agentsDir);
|
|
142
|
+
files.agents = entries
|
|
143
|
+
.filter(f => f.endsWith('.md'))
|
|
144
|
+
.map(f => join(agentsDir, f));
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return files;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Recursively remove a directory
|
|
153
|
+
*/
|
|
154
|
+
function removeDir(dirPath) {
|
|
155
|
+
if (!existsSync(dirPath)) return;
|
|
156
|
+
|
|
157
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
const fullPath = join(dirPath, entry.name);
|
|
160
|
+
if (entry.isDirectory()) {
|
|
161
|
+
removeDir(fullPath);
|
|
162
|
+
} else {
|
|
163
|
+
unlinkSync(fullPath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
rmdirSync(dirPath);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if CCASP is installed in the project
|
|
171
|
+
*/
|
|
172
|
+
function isCcaspInstalled(projectDir) {
|
|
173
|
+
const markers = [
|
|
174
|
+
join(projectDir, '.claude', 'config', 'ccasp-state.json'),
|
|
175
|
+
join(projectDir, '.claude', 'commands', 'menu.md'),
|
|
176
|
+
join(projectDir, '.claude', 'commands', 'ccasp-setup.md')
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
return markers.some(m => existsSync(m));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Run the uninstall command
|
|
184
|
+
*/
|
|
185
|
+
export async function runUninstall(options = {}) {
|
|
186
|
+
const projectDir = process.cwd();
|
|
187
|
+
|
|
188
|
+
console.log(chalk.cyan('\n CCASP Uninstall\n'));
|
|
189
|
+
console.log(chalk.dim(` Project: ${projectDir}\n`));
|
|
190
|
+
|
|
191
|
+
// Check if CCASP is installed
|
|
192
|
+
if (!isCcaspInstalled(projectDir)) {
|
|
193
|
+
console.log(chalk.yellow(' CCASP does not appear to be installed in this directory.'));
|
|
194
|
+
console.log(chalk.dim(' No .claude/config/ccasp-state.json or CCASP commands found.\n'));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Find what's installed
|
|
199
|
+
const ccaspFiles = findCcaspFiles(projectDir);
|
|
200
|
+
const backups = findBackups(projectDir);
|
|
201
|
+
|
|
202
|
+
// Show what will be affected
|
|
203
|
+
console.log(chalk.white.bold(' Found CCASP Installation:\n'));
|
|
204
|
+
|
|
205
|
+
const totalCommands = ccaspFiles.commands.length;
|
|
206
|
+
const totalHooks = ccaspFiles.hooks.length;
|
|
207
|
+
const totalSkills = ccaspFiles.skills.length;
|
|
208
|
+
const totalConfig = ccaspFiles.config.length;
|
|
209
|
+
const totalAgents = ccaspFiles.agents.length;
|
|
210
|
+
|
|
211
|
+
if (totalCommands > 0) {
|
|
212
|
+
console.log(chalk.white(` Commands: ${totalCommands}`));
|
|
213
|
+
}
|
|
214
|
+
if (totalHooks > 0) {
|
|
215
|
+
console.log(chalk.white(` Hooks: ${totalHooks}`));
|
|
216
|
+
}
|
|
217
|
+
if (totalSkills > 0) {
|
|
218
|
+
console.log(chalk.white(` Skills: ${totalSkills}`));
|
|
219
|
+
}
|
|
220
|
+
if (totalConfig > 0) {
|
|
221
|
+
console.log(chalk.white(` Config files: ${totalConfig}`));
|
|
222
|
+
}
|
|
223
|
+
if (totalAgents > 0) {
|
|
224
|
+
console.log(chalk.white(` Agents: ${totalAgents}`));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (backups.size > 0) {
|
|
228
|
+
console.log(chalk.green(`\n Backups found: ${backups.size} (will be restored)`));
|
|
229
|
+
for (const [name, backup] of backups) {
|
|
230
|
+
console.log(chalk.dim(` - ${name} (${backup.timestamp})`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log('');
|
|
235
|
+
|
|
236
|
+
// Confirm unless --force
|
|
237
|
+
if (!options.force) {
|
|
238
|
+
const shouldContinue = await confirm(chalk.yellow(' Remove CCASP and restore backups? (y/N): '));
|
|
239
|
+
if (!shouldContinue) {
|
|
240
|
+
console.log(chalk.dim('\n Uninstall cancelled.\n'));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log('');
|
|
246
|
+
|
|
247
|
+
// Step 1: Restore backups
|
|
248
|
+
if (backups.size > 0) {
|
|
249
|
+
console.log(chalk.cyan(' Restoring backups...\n'));
|
|
250
|
+
|
|
251
|
+
for (const [originalName, backup] of backups) {
|
|
252
|
+
// Determine where to restore based on file extension
|
|
253
|
+
let targetDir;
|
|
254
|
+
if (originalName.endsWith('.md')) {
|
|
255
|
+
// Could be command, agent, or doc
|
|
256
|
+
if (ccaspFiles.commands.some(c => basename(c) === originalName)) {
|
|
257
|
+
targetDir = join(projectDir, '.claude', 'commands');
|
|
258
|
+
} else if (ccaspFiles.agents.some(a => basename(a) === originalName)) {
|
|
259
|
+
targetDir = join(projectDir, '.claude', 'agents');
|
|
260
|
+
} else {
|
|
261
|
+
targetDir = join(projectDir, '.claude', 'commands'); // Default
|
|
262
|
+
}
|
|
263
|
+
} else if (originalName.endsWith('.js')) {
|
|
264
|
+
targetDir = join(projectDir, '.claude', 'hooks');
|
|
265
|
+
} else if (originalName.endsWith('.json')) {
|
|
266
|
+
targetDir = join(projectDir, '.claude', 'config');
|
|
267
|
+
} else {
|
|
268
|
+
targetDir = join(projectDir, '.claude');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const targetPath = join(targetDir, originalName);
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
copyFileSync(backup.path, targetPath);
|
|
275
|
+
console.log(chalk.green(` ✓ Restored: ${originalName}`));
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.log(chalk.red(` ✗ Failed to restore ${originalName}: ${err.message}`));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log('');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Step 2: Remove CCASP files (that weren't backed up)
|
|
285
|
+
console.log(chalk.cyan(' Removing CCASP files...\n'));
|
|
286
|
+
|
|
287
|
+
let removedCount = 0;
|
|
288
|
+
|
|
289
|
+
// Remove commands (except those that were restored from backup)
|
|
290
|
+
for (const cmdPath of ccaspFiles.commands) {
|
|
291
|
+
const name = basename(cmdPath);
|
|
292
|
+
if (!backups.has(name)) {
|
|
293
|
+
try {
|
|
294
|
+
unlinkSync(cmdPath);
|
|
295
|
+
removedCount++;
|
|
296
|
+
} catch {}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Remove hooks (except those that were restored from backup)
|
|
301
|
+
for (const hookPath of ccaspFiles.hooks) {
|
|
302
|
+
const name = basename(hookPath);
|
|
303
|
+
if (!backups.has(name)) {
|
|
304
|
+
try {
|
|
305
|
+
unlinkSync(hookPath);
|
|
306
|
+
removedCount++;
|
|
307
|
+
} catch {}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Remove skills directories
|
|
312
|
+
for (const skillDir of ccaspFiles.skills) {
|
|
313
|
+
try {
|
|
314
|
+
removeDir(skillDir);
|
|
315
|
+
removedCount++;
|
|
316
|
+
} catch {}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Remove config files
|
|
320
|
+
for (const configPath of ccaspFiles.config) {
|
|
321
|
+
try {
|
|
322
|
+
unlinkSync(configPath);
|
|
323
|
+
removedCount++;
|
|
324
|
+
} catch {}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Remove agents (except those restored)
|
|
328
|
+
for (const agentPath of ccaspFiles.agents) {
|
|
329
|
+
const name = basename(agentPath);
|
|
330
|
+
if (!backups.has(name)) {
|
|
331
|
+
try {
|
|
332
|
+
unlinkSync(agentPath);
|
|
333
|
+
removedCount++;
|
|
334
|
+
} catch {}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.log(chalk.green(` ✓ Removed ${removedCount} CCASP file(s)\n`));
|
|
339
|
+
|
|
340
|
+
// Step 3: Remove backup directory
|
|
341
|
+
const backupDir = join(projectDir, '.claude', 'backups');
|
|
342
|
+
if (existsSync(backupDir)) {
|
|
343
|
+
try {
|
|
344
|
+
removeDir(backupDir);
|
|
345
|
+
console.log(chalk.green(' ✓ Removed backups directory\n'));
|
|
346
|
+
} catch {}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Step 4: Clean up empty directories
|
|
350
|
+
const dirsToCheck = [
|
|
351
|
+
join(projectDir, '.claude', 'commands'),
|
|
352
|
+
join(projectDir, '.claude', 'hooks'),
|
|
353
|
+
join(projectDir, '.claude', 'skills'),
|
|
354
|
+
join(projectDir, '.claude', 'config'),
|
|
355
|
+
join(projectDir, '.claude', 'agents'),
|
|
356
|
+
join(projectDir, '.claude', 'docs')
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
for (const dir of dirsToCheck) {
|
|
360
|
+
if (existsSync(dir)) {
|
|
361
|
+
try {
|
|
362
|
+
const entries = readdirSync(dir);
|
|
363
|
+
if (entries.length === 0) {
|
|
364
|
+
rmdirSync(dir);
|
|
365
|
+
}
|
|
366
|
+
} catch {}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Step 5: Optionally remove entire .claude directory if empty or --all flag
|
|
371
|
+
const claudeDir = join(projectDir, '.claude');
|
|
372
|
+
if (options.all) {
|
|
373
|
+
if (existsSync(claudeDir)) {
|
|
374
|
+
const shouldRemoveAll = options.force || await confirm(chalk.yellow(' Remove entire .claude/ directory? (y/N): '));
|
|
375
|
+
if (shouldRemoveAll) {
|
|
376
|
+
try {
|
|
377
|
+
removeDir(claudeDir);
|
|
378
|
+
console.log(chalk.green(' ✓ Removed .claude/ directory\n'));
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.log(chalk.red(` ✗ Could not remove .claude/: ${err.message}\n`));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
// Check if .claude is empty
|
|
386
|
+
if (existsSync(claudeDir)) {
|
|
387
|
+
try {
|
|
388
|
+
const remaining = readdirSync(claudeDir);
|
|
389
|
+
if (remaining.length === 0) {
|
|
390
|
+
rmdirSync(claudeDir);
|
|
391
|
+
console.log(chalk.green(' ✓ Removed empty .claude/ directory\n'));
|
|
392
|
+
} else {
|
|
393
|
+
console.log(chalk.dim(` .claude/ directory kept (${remaining.length} items remain)`));
|
|
394
|
+
console.log(chalk.dim(' Use --all to remove entire .claude/ directory\n'));
|
|
395
|
+
}
|
|
396
|
+
} catch {}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Remove from global registry
|
|
401
|
+
const wasRegistered = unregisterProject(projectDir);
|
|
402
|
+
if (wasRegistered) {
|
|
403
|
+
console.log(chalk.green(' ✓ Removed from global CCASP registry'));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Done
|
|
407
|
+
console.log(chalk.green.bold('\n ✓ CCASP uninstalled successfully!\n'));
|
|
408
|
+
|
|
409
|
+
if (backups.size > 0) {
|
|
410
|
+
console.log(chalk.dim(' Your backed-up files have been restored.'));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
console.log(chalk.dim(' To reinstall, run: ccasp init\n'));
|
|
414
|
+
}
|
package/src/data/releases.json
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"releases": [
|
|
3
|
+
{
|
|
4
|
+
"version": "1.8.4",
|
|
5
|
+
"date": "2026-01-31",
|
|
6
|
+
"summary": "Release notes pending",
|
|
7
|
+
"highlights": [],
|
|
8
|
+
"newFeatures": {
|
|
9
|
+
"commands": [],
|
|
10
|
+
"agents": [],
|
|
11
|
+
"skills": [],
|
|
12
|
+
"hooks": [],
|
|
13
|
+
"other": []
|
|
14
|
+
},
|
|
15
|
+
"breaking": [],
|
|
16
|
+
"deprecated": []
|
|
17
|
+
},
|
|
3
18
|
{
|
|
4
19
|
"version": "1.5.0",
|
|
5
20
|
"date": "2026-01-30",
|