claude-cli-advanced-starter-pack 1.8.1 → 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 +11 -0
- package/package.json +1 -1
- package/src/commands/install-panel-hook.js +21 -11
- package/src/commands/uninstall.js +407 -0
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
|
@@ -66,24 +66,34 @@ export async function runInstallPanelHook(options = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// Ensure hooks
|
|
69
|
+
// Ensure hooks object exists (new v2.x format)
|
|
70
70
|
if (!settings.hooks) {
|
|
71
|
-
settings.hooks =
|
|
71
|
+
settings.hooks = {};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
};
|
|
74
|
+
// Ensure UserPromptSubmit array exists
|
|
75
|
+
if (!settings.hooks.UserPromptSubmit) {
|
|
76
|
+
settings.hooks.UserPromptSubmit = [];
|
|
77
|
+
}
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
h.command?.includes('panel-queue-reader')
|
|
79
|
+
// Check if hook already registered (new v2.x format)
|
|
80
|
+
const existingHook = settings.hooks.UserPromptSubmit.find(h =>
|
|
81
|
+
h.hooks?.some(hook => hook.command?.includes('panel-queue-reader'))
|
|
83
82
|
);
|
|
84
83
|
|
|
85
84
|
if (!existingHook) {
|
|
86
|
-
|
|
85
|
+
// New Claude Code v2.x hooks format
|
|
86
|
+
const hookConfig = {
|
|
87
|
+
matcher: '', // Empty string = match all (NOT empty object!)
|
|
88
|
+
hooks: [
|
|
89
|
+
{
|
|
90
|
+
type: 'command',
|
|
91
|
+
command: `node ${hookPath}`
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
settings.hooks.UserPromptSubmit.push(hookConfig);
|
|
87
97
|
|
|
88
98
|
// Ensure settings directory exists
|
|
89
99
|
const settingsDir = dirname(settingsPath);
|
|
@@ -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
|
+
}
|