claude-cli-advanced-starter-pack 1.8.2 → 1.8.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/bin/gtask.js CHANGED
@@ -35,6 +35,7 @@ import { installSkillCommand, listSkills } from '../src/commands/install-skill.j
35
35
  import { runInstallScripts } from '../src/commands/install-scripts.js';
36
36
  import { runPanel, launchPanel } from '../src/commands/panel.js';
37
37
  import { runInstallPanelHook } from '../src/commands/install-panel-hook.js';
38
+ import { runUninstall } from '../src/commands/uninstall.js';
38
39
  import { getVersion, checkPrerequisites } from '../src/utils.js';
39
40
 
40
41
  program
@@ -51,6 +52,16 @@ program
51
52
  await runInit(options);
52
53
  });
53
54
 
55
+ // Uninstall command - remove CCASP from project
56
+ program
57
+ .command('uninstall')
58
+ .description('Remove CCASP from current project and restore backups')
59
+ .option('--force', 'Skip confirmation prompts')
60
+ .option('--all', 'Remove entire .claude/ directory')
61
+ .action(async (options) => {
62
+ await runUninstall(options);
63
+ });
64
+
54
65
  // Interactive menu (default when no command)
55
66
  program
56
67
  .command('menu', { isDefault: true })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cli-advanced-starter-pack",
3
- "version": "1.8.2",
3
+ "version": "1.8.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": {
@@ -0,0 +1,407 @@
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
+
12
+ /**
13
+ * Prompt user for confirmation
14
+ */
15
+ function confirm(question) {
16
+ const rl = createInterface({
17
+ input: process.stdin,
18
+ output: process.stdout
19
+ });
20
+
21
+ return new Promise((resolve) => {
22
+ rl.question(question, (answer) => {
23
+ rl.close();
24
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
25
+ });
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Find all backup files in .claude/backups/
31
+ * Returns map of original filename -> most recent backup path
32
+ */
33
+ function findBackups(projectDir) {
34
+ const backupDir = join(projectDir, '.claude', 'backups');
35
+ const backups = new Map();
36
+
37
+ if (!existsSync(backupDir)) {
38
+ return backups;
39
+ }
40
+
41
+ try {
42
+ const files = readdirSync(backupDir);
43
+
44
+ for (const file of files) {
45
+ if (!file.endsWith('.bak')) continue;
46
+
47
+ // Parse filename: original.YYYY-MM-DDTHH-MM-SS.bak
48
+ const match = file.match(/^(.+)\.(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})\.bak$/);
49
+ if (!match) continue;
50
+
51
+ const [, originalName, timestamp] = match;
52
+ const backupPath = join(backupDir, file);
53
+
54
+ // Keep only the most recent backup for each file
55
+ if (!backups.has(originalName) || backups.get(originalName).timestamp < timestamp) {
56
+ backups.set(originalName, {
57
+ path: backupPath,
58
+ timestamp,
59
+ originalName
60
+ });
61
+ }
62
+ }
63
+ } catch (err) {
64
+ console.log(chalk.yellow(` Warning: Could not read backups directory: ${err.message}`));
65
+ }
66
+
67
+ return backups;
68
+ }
69
+
70
+ /**
71
+ * Get list of CCASP-installed files by checking ccasp-state.json or common patterns
72
+ */
73
+ function findCcaspFiles(projectDir) {
74
+ const files = {
75
+ commands: [],
76
+ hooks: [],
77
+ skills: [],
78
+ config: [],
79
+ docs: [],
80
+ agents: []
81
+ };
82
+
83
+ const claudeDir = join(projectDir, '.claude');
84
+ if (!existsSync(claudeDir)) {
85
+ return files;
86
+ }
87
+
88
+ // Commands
89
+ const commandsDir = join(claudeDir, 'commands');
90
+ if (existsSync(commandsDir)) {
91
+ try {
92
+ const entries = readdirSync(commandsDir);
93
+ files.commands = entries
94
+ .filter(f => f.endsWith('.md'))
95
+ .map(f => join(commandsDir, f));
96
+ } catch {}
97
+ }
98
+
99
+ // Hooks
100
+ const hooksDir = join(claudeDir, 'hooks');
101
+ if (existsSync(hooksDir)) {
102
+ try {
103
+ const entries = readdirSync(hooksDir);
104
+ files.hooks = entries
105
+ .filter(f => f.endsWith('.js'))
106
+ .map(f => join(hooksDir, f));
107
+ } catch {}
108
+ }
109
+
110
+ // Skills (directories)
111
+ const skillsDir = join(claudeDir, 'skills');
112
+ if (existsSync(skillsDir)) {
113
+ try {
114
+ const entries = readdirSync(skillsDir, { withFileTypes: true });
115
+ files.skills = entries
116
+ .filter(e => e.isDirectory())
117
+ .map(e => join(skillsDir, e.name));
118
+ } catch {}
119
+ }
120
+
121
+ // Config
122
+ const configDir = join(claudeDir, 'config');
123
+ if (existsSync(configDir)) {
124
+ try {
125
+ const entries = readdirSync(configDir);
126
+ files.config = entries.map(f => join(configDir, f));
127
+ } catch {}
128
+ }
129
+
130
+ // Docs
131
+ const docsDir = join(claudeDir, 'docs');
132
+ if (existsSync(docsDir)) {
133
+ files.docs.push(docsDir);
134
+ }
135
+
136
+ // Agents
137
+ const agentsDir = join(claudeDir, 'agents');
138
+ if (existsSync(agentsDir)) {
139
+ try {
140
+ const entries = readdirSync(agentsDir);
141
+ files.agents = entries
142
+ .filter(f => f.endsWith('.md'))
143
+ .map(f => join(agentsDir, f));
144
+ } catch {}
145
+ }
146
+
147
+ return files;
148
+ }
149
+
150
+ /**
151
+ * Recursively remove a directory
152
+ */
153
+ function removeDir(dirPath) {
154
+ if (!existsSync(dirPath)) return;
155
+
156
+ const entries = readdirSync(dirPath, { withFileTypes: true });
157
+ for (const entry of entries) {
158
+ const fullPath = join(dirPath, entry.name);
159
+ if (entry.isDirectory()) {
160
+ removeDir(fullPath);
161
+ } else {
162
+ unlinkSync(fullPath);
163
+ }
164
+ }
165
+ rmdirSync(dirPath);
166
+ }
167
+
168
+ /**
169
+ * Check if CCASP is installed in the project
170
+ */
171
+ function isCcaspInstalled(projectDir) {
172
+ const markers = [
173
+ join(projectDir, '.claude', 'config', 'ccasp-state.json'),
174
+ join(projectDir, '.claude', 'commands', 'menu.md'),
175
+ join(projectDir, '.claude', 'commands', 'ccasp-setup.md')
176
+ ];
177
+
178
+ return markers.some(m => existsSync(m));
179
+ }
180
+
181
+ /**
182
+ * Run the uninstall command
183
+ */
184
+ export async function runUninstall(options = {}) {
185
+ const projectDir = process.cwd();
186
+
187
+ console.log(chalk.cyan('\n CCASP Uninstall\n'));
188
+ console.log(chalk.dim(` Project: ${projectDir}\n`));
189
+
190
+ // Check if CCASP is installed
191
+ if (!isCcaspInstalled(projectDir)) {
192
+ console.log(chalk.yellow(' CCASP does not appear to be installed in this directory.'));
193
+ console.log(chalk.dim(' No .claude/config/ccasp-state.json or CCASP commands found.\n'));
194
+ return;
195
+ }
196
+
197
+ // Find what's installed
198
+ const ccaspFiles = findCcaspFiles(projectDir);
199
+ const backups = findBackups(projectDir);
200
+
201
+ // Show what will be affected
202
+ console.log(chalk.white.bold(' Found CCASP Installation:\n'));
203
+
204
+ const totalCommands = ccaspFiles.commands.length;
205
+ const totalHooks = ccaspFiles.hooks.length;
206
+ const totalSkills = ccaspFiles.skills.length;
207
+ const totalConfig = ccaspFiles.config.length;
208
+ const totalAgents = ccaspFiles.agents.length;
209
+
210
+ if (totalCommands > 0) {
211
+ console.log(chalk.white(` Commands: ${totalCommands}`));
212
+ }
213
+ if (totalHooks > 0) {
214
+ console.log(chalk.white(` Hooks: ${totalHooks}`));
215
+ }
216
+ if (totalSkills > 0) {
217
+ console.log(chalk.white(` Skills: ${totalSkills}`));
218
+ }
219
+ if (totalConfig > 0) {
220
+ console.log(chalk.white(` Config files: ${totalConfig}`));
221
+ }
222
+ if (totalAgents > 0) {
223
+ console.log(chalk.white(` Agents: ${totalAgents}`));
224
+ }
225
+
226
+ if (backups.size > 0) {
227
+ console.log(chalk.green(`\n Backups found: ${backups.size} (will be restored)`));
228
+ for (const [name, backup] of backups) {
229
+ console.log(chalk.dim(` - ${name} (${backup.timestamp})`));
230
+ }
231
+ }
232
+
233
+ console.log('');
234
+
235
+ // Confirm unless --force
236
+ if (!options.force) {
237
+ const shouldContinue = await confirm(chalk.yellow(' Remove CCASP and restore backups? (y/N): '));
238
+ if (!shouldContinue) {
239
+ console.log(chalk.dim('\n Uninstall cancelled.\n'));
240
+ return;
241
+ }
242
+ }
243
+
244
+ console.log('');
245
+
246
+ // Step 1: Restore backups
247
+ if (backups.size > 0) {
248
+ console.log(chalk.cyan(' Restoring backups...\n'));
249
+
250
+ for (const [originalName, backup] of backups) {
251
+ // Determine where to restore based on file extension
252
+ let targetDir;
253
+ if (originalName.endsWith('.md')) {
254
+ // Could be command, agent, or doc
255
+ if (ccaspFiles.commands.some(c => basename(c) === originalName)) {
256
+ targetDir = join(projectDir, '.claude', 'commands');
257
+ } else if (ccaspFiles.agents.some(a => basename(a) === originalName)) {
258
+ targetDir = join(projectDir, '.claude', 'agents');
259
+ } else {
260
+ targetDir = join(projectDir, '.claude', 'commands'); // Default
261
+ }
262
+ } else if (originalName.endsWith('.js')) {
263
+ targetDir = join(projectDir, '.claude', 'hooks');
264
+ } else if (originalName.endsWith('.json')) {
265
+ targetDir = join(projectDir, '.claude', 'config');
266
+ } else {
267
+ targetDir = join(projectDir, '.claude');
268
+ }
269
+
270
+ const targetPath = join(targetDir, originalName);
271
+
272
+ try {
273
+ copyFileSync(backup.path, targetPath);
274
+ console.log(chalk.green(` ✓ Restored: ${originalName}`));
275
+ } catch (err) {
276
+ console.log(chalk.red(` ✗ Failed to restore ${originalName}: ${err.message}`));
277
+ }
278
+ }
279
+
280
+ console.log('');
281
+ }
282
+
283
+ // Step 2: Remove CCASP files (that weren't backed up)
284
+ console.log(chalk.cyan(' Removing CCASP files...\n'));
285
+
286
+ let removedCount = 0;
287
+
288
+ // Remove commands (except those that were restored from backup)
289
+ for (const cmdPath of ccaspFiles.commands) {
290
+ const name = basename(cmdPath);
291
+ if (!backups.has(name)) {
292
+ try {
293
+ unlinkSync(cmdPath);
294
+ removedCount++;
295
+ } catch {}
296
+ }
297
+ }
298
+
299
+ // Remove hooks (except those that were restored from backup)
300
+ for (const hookPath of ccaspFiles.hooks) {
301
+ const name = basename(hookPath);
302
+ if (!backups.has(name)) {
303
+ try {
304
+ unlinkSync(hookPath);
305
+ removedCount++;
306
+ } catch {}
307
+ }
308
+ }
309
+
310
+ // Remove skills directories
311
+ for (const skillDir of ccaspFiles.skills) {
312
+ try {
313
+ removeDir(skillDir);
314
+ removedCount++;
315
+ } catch {}
316
+ }
317
+
318
+ // Remove config files
319
+ for (const configPath of ccaspFiles.config) {
320
+ try {
321
+ unlinkSync(configPath);
322
+ removedCount++;
323
+ } catch {}
324
+ }
325
+
326
+ // Remove agents (except those restored)
327
+ for (const agentPath of ccaspFiles.agents) {
328
+ const name = basename(agentPath);
329
+ if (!backups.has(name)) {
330
+ try {
331
+ unlinkSync(agentPath);
332
+ removedCount++;
333
+ } catch {}
334
+ }
335
+ }
336
+
337
+ console.log(chalk.green(` ✓ Removed ${removedCount} CCASP file(s)\n`));
338
+
339
+ // Step 3: Remove backup directory
340
+ const backupDir = join(projectDir, '.claude', 'backups');
341
+ if (existsSync(backupDir)) {
342
+ try {
343
+ removeDir(backupDir);
344
+ console.log(chalk.green(' ✓ Removed backups directory\n'));
345
+ } catch {}
346
+ }
347
+
348
+ // Step 4: Clean up empty directories
349
+ const dirsToCheck = [
350
+ join(projectDir, '.claude', 'commands'),
351
+ join(projectDir, '.claude', 'hooks'),
352
+ join(projectDir, '.claude', 'skills'),
353
+ join(projectDir, '.claude', 'config'),
354
+ join(projectDir, '.claude', 'agents'),
355
+ join(projectDir, '.claude', 'docs')
356
+ ];
357
+
358
+ for (const dir of dirsToCheck) {
359
+ if (existsSync(dir)) {
360
+ try {
361
+ const entries = readdirSync(dir);
362
+ if (entries.length === 0) {
363
+ rmdirSync(dir);
364
+ }
365
+ } catch {}
366
+ }
367
+ }
368
+
369
+ // Step 5: Optionally remove entire .claude directory if empty or --all flag
370
+ const claudeDir = join(projectDir, '.claude');
371
+ if (options.all) {
372
+ if (existsSync(claudeDir)) {
373
+ const shouldRemoveAll = options.force || await confirm(chalk.yellow(' Remove entire .claude/ directory? (y/N): '));
374
+ if (shouldRemoveAll) {
375
+ try {
376
+ removeDir(claudeDir);
377
+ console.log(chalk.green(' ✓ Removed .claude/ directory\n'));
378
+ } catch (err) {
379
+ console.log(chalk.red(` ✗ Could not remove .claude/: ${err.message}\n`));
380
+ }
381
+ }
382
+ }
383
+ } else {
384
+ // Check if .claude is empty
385
+ if (existsSync(claudeDir)) {
386
+ try {
387
+ const remaining = readdirSync(claudeDir);
388
+ if (remaining.length === 0) {
389
+ rmdirSync(claudeDir);
390
+ console.log(chalk.green(' ✓ Removed empty .claude/ directory\n'));
391
+ } else {
392
+ console.log(chalk.dim(` .claude/ directory kept (${remaining.length} items remain)`));
393
+ console.log(chalk.dim(' Use --all to remove entire .claude/ directory\n'));
394
+ }
395
+ } catch {}
396
+ }
397
+ }
398
+
399
+ // Done
400
+ console.log(chalk.green.bold(' ✓ CCASP uninstalled successfully!\n'));
401
+
402
+ if (backups.size > 0) {
403
+ console.log(chalk.dim(' Your backed-up files have been restored.'));
404
+ }
405
+
406
+ console.log(chalk.dim(' To reinstall, run: ccasp init\n'));
407
+ }