@juho0719/cckit 0.1.1 → 0.2.0

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.
@@ -91,6 +91,7 @@ async function runCli() {
91
91
  mcps: [],
92
92
  mcpEnvValues: /* @__PURE__ */ new Map()
93
93
  };
94
+ const skippedMcps = /* @__PURE__ */ new Map();
94
95
  if (selectedCategories.includes("agents")) {
95
96
  const allAgents = await getAgents();
96
97
  plan.agents = await checkbox({
@@ -161,14 +162,29 @@ async function runCli() {
161
162
  console.log(chalk.yellow(`
162
163
  ${server.name} requires environment variables:`));
163
164
  const envMap = {};
165
+ const skippedKeys = [];
164
166
  for (const envKey of server.envVars) {
165
- const value = await input({
166
- message: ` ${envKey}:`,
167
- default: ""
167
+ const action = await select({
168
+ message: ` How do you want to set ${chalk.bold(envKey)}?`,
169
+ choices: [
170
+ { name: "Enter value now", value: "enter" },
171
+ { name: `Skip ${chalk.gray(`(keep placeholder: YOUR_${envKey}_HERE)`)}`, value: "skip" }
172
+ ]
168
173
  });
169
- if (value) envMap[envKey] = value;
174
+ if (action === "skip") {
175
+ skippedKeys.push(envKey);
176
+ } else {
177
+ const value = await input({
178
+ message: ` ${envKey}:`,
179
+ default: ""
180
+ });
181
+ if (value) envMap[envKey] = value;
182
+ }
170
183
  }
171
184
  plan.mcpEnvValues.set(server.name, envMap);
185
+ if (skippedKeys.length > 0) {
186
+ skippedMcps.set(server.name, skippedKeys);
187
+ }
172
188
  }
173
189
  }
174
190
  console.log(chalk.bold("\n Installation Summary"));
@@ -228,6 +244,15 @@ async function runCli() {
228
244
  if (plan.ruleCategories.length) console.log(` ${chalk.green("\u2713")} Rules appended \u2192 ${join(targetDir, "CLAUDE.md")}`);
229
245
  if (plan.mcps.length) console.log(` ${chalk.green("\u2713")} ${plan.mcps.length} MCP server(s) \u2192 ${scope === "global" ? "~/.claude.json" : "./.claude.json"}`);
230
246
  console.log("");
247
+ if (skippedMcps.size > 0) {
248
+ console.log(chalk.yellow(" \u26A0 The following MCP servers have placeholder env vars:"));
249
+ for (const [serverName, keys] of skippedMcps) {
250
+ console.log(` ${chalk.bold("-")} ${serverName} ${chalk.gray(`(${keys.join(", ")})`)} `);
251
+ }
252
+ const configPath = scope === "global" ? "~/.claude.json" : "./.claude.json";
253
+ console.log(chalk.gray(` \u2192 Edit ${configPath} to set the actual values`));
254
+ console.log("");
255
+ }
231
256
  } catch (err) {
232
257
  spinner.fail(chalk.red("Installation failed"));
233
258
  throw err;
package/dist/index.js CHANGED
@@ -20,15 +20,17 @@ function printHelp() {
20
20
  cckit - Claude Code Harness Installer v${getVersion()}
21
21
 
22
22
  Usage:
23
- cckit Interactive installation wizard
24
- cckit --all Install all items (global scope)
25
- cckit --version Show version
26
- cckit --help Show this help
23
+ cckit Interactive installation wizard
24
+ cckit --uninstall Interactive uninstall wizard
25
+ cckit --all Install all items (global scope)
26
+ cckit --version Show version
27
+ cckit --help Show this help
27
28
 
28
29
  Options:
29
- --all Install all agents, skills, commands, hooks, rules, and MCPs
30
- to ~/.claude/ without interactive prompts
31
- --scope Scope for --all flag: global (default) or project
30
+ --uninstall, -u Launch interactive uninstall wizard
31
+ --all Install all agents, skills, commands, hooks, rules, and MCPs
32
+ to ~/.claude/ without interactive prompts
33
+ --scope Scope for --all flag: global (default) or project
32
34
  `);
33
35
  }
34
36
  async function installAll(scope) {
@@ -114,13 +116,18 @@ async function main() {
114
116
  printHelp();
115
117
  return;
116
118
  }
119
+ if (args.includes("--uninstall") || args.includes("-u")) {
120
+ const { runUninstallCli } = await import("./uninstall-cli-KYQTJCKO.js");
121
+ await runUninstallCli();
122
+ return;
123
+ }
117
124
  if (args.includes("--all")) {
118
125
  const scopeIdx = args.indexOf("--scope");
119
126
  const scope = scopeIdx !== -1 && args[scopeIdx + 1] === "project" ? "project" : "global";
120
127
  await installAll(scope);
121
128
  return;
122
129
  }
123
- const { runCli } = await import("./cli-VZRGF733.js");
130
+ const { runCli } = await import("./cli-H6IDWH7Q.js");
124
131
  await runCli();
125
132
  }
126
133
  main().catch((err) => {
@@ -0,0 +1,544 @@
1
+ import {
2
+ getAgents,
3
+ getCommands,
4
+ getHooks,
5
+ getMcpServers,
6
+ getRuleCategories,
7
+ getSkills
8
+ } from "./chunk-6B46AIFM.js";
9
+ import {
10
+ getAssetsDir,
11
+ getGlobalDir,
12
+ getProjectDir
13
+ } from "./chunk-5XOKKPAA.js";
14
+
15
+ // src/uninstall-cli.ts
16
+ import { checkbox, select, confirm } from "@inquirer/prompts";
17
+ import chalk from "chalk";
18
+ import ora from "ora";
19
+ import { join as join8 } from "path";
20
+
21
+ // src/scanner.ts
22
+ import { readdir, readFile, access } from "fs/promises";
23
+ import { constants } from "fs";
24
+ import { join } from "path";
25
+ import { homedir } from "os";
26
+ function getClaudeJsonPath(scope) {
27
+ if (scope === "global") {
28
+ return join(homedir(), ".claude.json");
29
+ }
30
+ return join(process.cwd(), ".claude.json");
31
+ }
32
+ async function getInstalledAgents(targetDir) {
33
+ const agentsDir = join(targetDir, "agents");
34
+ let installedFiles = [];
35
+ try {
36
+ installedFiles = (await readdir(agentsDir)).filter((f) => f.endsWith(".md"));
37
+ } catch {
38
+ return [];
39
+ }
40
+ const registryAgents = await getAgents();
41
+ return registryAgents.filter((a) => installedFiles.includes(a.file));
42
+ }
43
+ async function getInstalledSkills(targetDir) {
44
+ const skillsDir = join(targetDir, "skills");
45
+ let installedDirs = [];
46
+ try {
47
+ const entries = await readdir(skillsDir, { withFileTypes: true });
48
+ installedDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
49
+ } catch {
50
+ return [];
51
+ }
52
+ const registrySkills = await getSkills();
53
+ return registrySkills.filter((s) => installedDirs.includes(s.dir));
54
+ }
55
+ async function getInstalledCommands(targetDir) {
56
+ const commandsDir = join(targetDir, "commands");
57
+ let installedFiles = [];
58
+ try {
59
+ installedFiles = (await readdir(commandsDir)).filter((f) => f.endsWith(".md"));
60
+ } catch {
61
+ return [];
62
+ }
63
+ const registryCommands = await getCommands();
64
+ return registryCommands.filter((c) => installedFiles.includes(c.file));
65
+ }
66
+ async function getInstalledHooks(targetDir) {
67
+ const hooksDir = join(targetDir, "hooks");
68
+ let installedFiles = [];
69
+ try {
70
+ installedFiles = (await readdir(hooksDir)).filter((f) => f.endsWith(".js"));
71
+ } catch {
72
+ return [];
73
+ }
74
+ const registryHooks = await getHooks();
75
+ return registryHooks.filter((h) => installedFiles.includes(h.file));
76
+ }
77
+ async function getInstalledRuleCategories(targetDir) {
78
+ const claudeMdPath = join(targetDir, "CLAUDE.md");
79
+ let content = "";
80
+ try {
81
+ await access(claudeMdPath, constants.F_OK);
82
+ content = await readFile(claudeMdPath, "utf-8");
83
+ } catch {
84
+ return [];
85
+ }
86
+ const installedHeaders = /* @__PURE__ */ new Set();
87
+ for (const line of content.split("\n")) {
88
+ const match = line.match(/^(#{1,2}\s+.+)/);
89
+ if (match) installedHeaders.add(match[1].trim());
90
+ }
91
+ const allCategories = await getRuleCategories();
92
+ const installedCategories = [];
93
+ for (const rc of allCategories) {
94
+ const catDir = join(getAssetsDir(), "rules", rc.category);
95
+ let found = false;
96
+ for (const file of rc.files) {
97
+ try {
98
+ const fileContent = await readFile(join(catDir, file), "utf-8");
99
+ const headerMatch = fileContent.match(/^(#{1,2}\s+.+)/m);
100
+ if (headerMatch && installedHeaders.has(headerMatch[1].trim())) {
101
+ found = true;
102
+ break;
103
+ }
104
+ } catch {
105
+ }
106
+ }
107
+ if (found) installedCategories.push(rc.category);
108
+ }
109
+ return installedCategories;
110
+ }
111
+ async function getInstalledMcps(scope) {
112
+ const filePath = getClaudeJsonPath(scope);
113
+ let claudeJson = {};
114
+ try {
115
+ await access(filePath, constants.F_OK);
116
+ const raw = await readFile(filePath, "utf-8");
117
+ claudeJson = JSON.parse(raw);
118
+ } catch {
119
+ return [];
120
+ }
121
+ if (!claudeJson.mcpServers) return [];
122
+ const installedNames = new Set(Object.keys(claudeJson.mcpServers));
123
+ const registryMcps = await getMcpServers();
124
+ return registryMcps.filter((m) => installedNames.has(m.name)).map((m) => m.name);
125
+ }
126
+
127
+ // src/uninstallers/agents.ts
128
+ import { join as join2 } from "path";
129
+ import { rm, access as access2 } from "fs/promises";
130
+ import { constants as constants2 } from "fs";
131
+ async function uninstallAgents(agents, targetDir) {
132
+ const agentsDir = join2(targetDir, "agents");
133
+ for (const agent of agents) {
134
+ const filePath = join2(agentsDir, agent.file);
135
+ try {
136
+ await access2(filePath, constants2.F_OK);
137
+ await rm(filePath);
138
+ } catch {
139
+ }
140
+ }
141
+ }
142
+
143
+ // src/uninstallers/skills.ts
144
+ import { join as join3 } from "path";
145
+ import { rm as rm2, access as access3 } from "fs/promises";
146
+ import { constants as constants3 } from "fs";
147
+ async function uninstallSkills(skills, targetDir) {
148
+ const skillsDir = join3(targetDir, "skills");
149
+ for (const skill of skills) {
150
+ const dirPath = join3(skillsDir, skill.dir);
151
+ try {
152
+ await access3(dirPath, constants3.F_OK);
153
+ await rm2(dirPath, { recursive: true });
154
+ } catch {
155
+ }
156
+ }
157
+ }
158
+
159
+ // src/uninstallers/commands.ts
160
+ import { join as join4 } from "path";
161
+ import { rm as rm3, access as access4 } from "fs/promises";
162
+ import { constants as constants4 } from "fs";
163
+ async function uninstallCommands(commands, targetDir) {
164
+ const commandsDir = join4(targetDir, "commands");
165
+ for (const cmd of commands) {
166
+ const filePath = join4(commandsDir, cmd.file);
167
+ try {
168
+ await access4(filePath, constants4.F_OK);
169
+ await rm3(filePath);
170
+ } catch {
171
+ }
172
+ }
173
+ }
174
+
175
+ // src/uninstallers/hooks.ts
176
+ import { join as join5 } from "path";
177
+ import { rm as rm4, readFile as readFile2, writeFile, access as access5 } from "fs/promises";
178
+ import { constants as constants5 } from "fs";
179
+ async function removeHookFromSettings(settingsPath, hookCommand) {
180
+ let settings = {};
181
+ try {
182
+ await access5(settingsPath, constants5.F_OK);
183
+ const content = await readFile2(settingsPath, "utf-8");
184
+ settings = JSON.parse(content);
185
+ } catch {
186
+ return;
187
+ }
188
+ if (!settings.hooks) return;
189
+ for (const hookType of Object.keys(settings.hooks)) {
190
+ const matchers = settings.hooks[hookType];
191
+ for (let i = matchers.length - 1; i >= 0; i--) {
192
+ const matcher = matchers[i];
193
+ matcher.hooks = matcher.hooks.filter((h) => h.command !== hookCommand);
194
+ if (matcher.hooks.length === 0) {
195
+ matchers.splice(i, 1);
196
+ }
197
+ }
198
+ if (matchers.length === 0) {
199
+ delete settings.hooks[hookType];
200
+ }
201
+ }
202
+ if (Object.keys(settings.hooks).length === 0) {
203
+ delete settings.hooks;
204
+ }
205
+ await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
206
+ }
207
+ async function uninstallHooks(hooks, targetDir) {
208
+ const hooksDir = join5(targetDir, "hooks");
209
+ const settingsPath = join5(targetDir, "settings.json");
210
+ for (const hook of hooks) {
211
+ const filePath = join5(hooksDir, hook.file);
212
+ const hookCommand = `node ${filePath}`;
213
+ await removeHookFromSettings(settingsPath, hookCommand);
214
+ try {
215
+ await access5(filePath, constants5.F_OK);
216
+ await rm4(filePath);
217
+ } catch {
218
+ }
219
+ }
220
+ }
221
+
222
+ // src/uninstallers/rules.ts
223
+ import { readFile as readFile3, writeFile as writeFile2, access as access6 } from "fs/promises";
224
+ import { constants as constants6 } from "fs";
225
+ import { join as join6 } from "path";
226
+ async function uninstallRules(categories, claudeMdPath) {
227
+ let existing = "";
228
+ try {
229
+ await access6(claudeMdPath, constants6.F_OK);
230
+ existing = await readFile3(claudeMdPath, "utf-8");
231
+ } catch {
232
+ return;
233
+ }
234
+ const headersToRemove = /* @__PURE__ */ new Set();
235
+ for (const category of categories) {
236
+ const catDir = join6(getAssetsDir(), "rules", category);
237
+ let files;
238
+ try {
239
+ const { readdir: readdir2 } = await import("fs/promises");
240
+ files = (await readdir2(catDir)).filter((f) => f.endsWith(".md"));
241
+ } catch {
242
+ continue;
243
+ }
244
+ for (const file of files) {
245
+ try {
246
+ const content = await readFile3(join6(catDir, file), "utf-8");
247
+ const headerMatch = content.match(/^(#{1,2}\s+.+)/m);
248
+ if (headerMatch) {
249
+ headersToRemove.add(headerMatch[1].trim());
250
+ }
251
+ } catch {
252
+ }
253
+ }
254
+ }
255
+ if (headersToRemove.size === 0) return;
256
+ const lines = existing.split("\n");
257
+ const result = [];
258
+ let inRemovedSection = false;
259
+ let currentDepth = 0;
260
+ for (const line of lines) {
261
+ const headerMatch = line.match(/^(#{1,2})\s+/);
262
+ if (headerMatch) {
263
+ const depth = headerMatch[1].length;
264
+ const trimmed = line.trim();
265
+ if (headersToRemove.has(trimmed)) {
266
+ inRemovedSection = true;
267
+ currentDepth = depth;
268
+ continue;
269
+ }
270
+ if (inRemovedSection && depth <= currentDepth) {
271
+ inRemovedSection = false;
272
+ }
273
+ }
274
+ if (!inRemovedSection) {
275
+ result.push(line);
276
+ }
277
+ }
278
+ const cleaned = result.join("\n").replace(/\n{3,}/g, "\n\n");
279
+ await writeFile2(claudeMdPath, cleaned, "utf-8");
280
+ }
281
+
282
+ // src/uninstallers/mcps.ts
283
+ import { join as join7 } from "path";
284
+ import { readFile as readFile4, writeFile as writeFile3, access as access7 } from "fs/promises";
285
+ import { constants as constants7 } from "fs";
286
+ import { homedir as homedir2 } from "os";
287
+ function getClaudeJsonPath2(scope) {
288
+ if (scope === "global") {
289
+ return join7(homedir2(), ".claude.json");
290
+ }
291
+ return join7(process.cwd(), ".claude.json");
292
+ }
293
+ async function uninstallMcps(serverNames, scope) {
294
+ const filePath = getClaudeJsonPath2(scope);
295
+ let claudeJson = {};
296
+ try {
297
+ await access7(filePath, constants7.F_OK);
298
+ const content = await readFile4(filePath, "utf-8");
299
+ claudeJson = JSON.parse(content);
300
+ } catch {
301
+ return;
302
+ }
303
+ if (!claudeJson.mcpServers) return;
304
+ for (const name of serverNames) {
305
+ delete claudeJson.mcpServers[name];
306
+ }
307
+ await writeFile3(filePath, JSON.stringify(claudeJson, null, 2) + "\n", "utf-8");
308
+ }
309
+
310
+ // src/uninstall-cli.ts
311
+ function printBanner() {
312
+ console.log(
313
+ chalk.red.bold(`
314
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
315
+ \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
316
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551
317
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551
318
+ \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
319
+ \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
320
+ `)
321
+ );
322
+ console.log(chalk.gray(" Claude Code Harness Uninstaller\n"));
323
+ }
324
+ async function runUninstallCli() {
325
+ printBanner();
326
+ const scope = await select({
327
+ message: "Uninstall scope:",
328
+ choices: [
329
+ {
330
+ name: `Global ${chalk.gray("~/.claude/")}`,
331
+ value: "global"
332
+ },
333
+ {
334
+ name: `Project ${chalk.gray("./.claude/")}`,
335
+ value: "project"
336
+ }
337
+ ]
338
+ });
339
+ const targetDir = scope === "global" ? getGlobalDir() : getProjectDir();
340
+ console.log(chalk.gray("\n Scanning installed items...\n"));
341
+ const [
342
+ installedAgents,
343
+ installedSkills,
344
+ installedCommands,
345
+ installedHooks,
346
+ installedRuleCategories,
347
+ installedMcpNames
348
+ ] = await Promise.all([
349
+ getInstalledAgents(targetDir),
350
+ getInstalledSkills(targetDir),
351
+ getInstalledCommands(targetDir),
352
+ getInstalledHooks(targetDir),
353
+ getInstalledRuleCategories(targetDir),
354
+ getInstalledMcps(scope)
355
+ ]);
356
+ const totalInstalled = installedAgents.length + installedSkills.length + installedCommands.length + installedHooks.length + installedRuleCategories.length + installedMcpNames.length;
357
+ if (totalInstalled === 0) {
358
+ console.log(chalk.yellow(" Nothing installed. Exiting."));
359
+ return;
360
+ }
361
+ const availableCategories = [];
362
+ if (installedAgents.length > 0)
363
+ availableCategories.push({
364
+ name: `Agents ${chalk.gray(`(${installedAgents.length} installed)`)}`,
365
+ value: "agents"
366
+ });
367
+ if (installedSkills.length > 0)
368
+ availableCategories.push({
369
+ name: `Skills ${chalk.gray(`(${installedSkills.length} installed)`)}`,
370
+ value: "skills"
371
+ });
372
+ if (installedCommands.length > 0)
373
+ availableCategories.push({
374
+ name: `Commands ${chalk.gray(`(${installedCommands.length} installed)`)}`,
375
+ value: "commands"
376
+ });
377
+ if (installedHooks.length > 0)
378
+ availableCategories.push({
379
+ name: `Hooks ${chalk.gray(`(${installedHooks.length} installed)`)}`,
380
+ value: "hooks"
381
+ });
382
+ if (installedRuleCategories.length > 0)
383
+ availableCategories.push({
384
+ name: `Rules ${chalk.gray(`(${installedRuleCategories.length} categories)`)}`,
385
+ value: "rules"
386
+ });
387
+ if (installedMcpNames.length > 0)
388
+ availableCategories.push({
389
+ name: `MCPs ${chalk.gray(`(${installedMcpNames.length} installed)`)}`,
390
+ value: "mcps"
391
+ });
392
+ const selectedCategories = await checkbox({
393
+ message: "Select categories to uninstall:",
394
+ choices: availableCategories
395
+ });
396
+ if (selectedCategories.length === 0) {
397
+ console.log(chalk.yellow("\n No categories selected. Exiting."));
398
+ return;
399
+ }
400
+ const plan = {
401
+ scope,
402
+ agents: [],
403
+ skills: [],
404
+ commands: [],
405
+ hooks: [],
406
+ ruleCategories: [],
407
+ mcpNames: []
408
+ };
409
+ if (selectedCategories.includes("agents") && installedAgents.length > 0) {
410
+ plan.agents = await checkbox({
411
+ message: "Select agents to uninstall:",
412
+ choices: installedAgents.map((a) => ({
413
+ name: `${chalk.bold(a.name.padEnd(30))} ${chalk.gray(a.description.slice(0, 60))}`,
414
+ value: a,
415
+ checked: false
416
+ }))
417
+ });
418
+ }
419
+ if (selectedCategories.includes("skills") && installedSkills.length > 0) {
420
+ plan.skills = await checkbox({
421
+ message: "Select skills to uninstall:",
422
+ choices: installedSkills.map((s) => ({
423
+ name: `${chalk.bold(s.name.padEnd(35))} ${chalk.gray(s.description.slice(0, 55))}`,
424
+ value: s,
425
+ checked: false
426
+ }))
427
+ });
428
+ }
429
+ if (selectedCategories.includes("commands") && installedCommands.length > 0) {
430
+ plan.commands = await checkbox({
431
+ message: "Select commands to uninstall:",
432
+ choices: installedCommands.map((c) => ({
433
+ name: `${chalk.bold(c.name.padEnd(25))} ${chalk.gray(c.description.slice(0, 65))}`,
434
+ value: c,
435
+ checked: false
436
+ }))
437
+ });
438
+ }
439
+ if (selectedCategories.includes("hooks") && installedHooks.length > 0) {
440
+ plan.hooks = await checkbox({
441
+ message: "Select hooks to uninstall:",
442
+ choices: installedHooks.map((h) => ({
443
+ name: `${chalk.bold(h.name.padEnd(35))} ${chalk.gray(h.description.slice(0, 55))}`,
444
+ value: h,
445
+ checked: false
446
+ }))
447
+ });
448
+ }
449
+ if (selectedCategories.includes("rules") && installedRuleCategories.length > 0) {
450
+ plan.ruleCategories = await checkbox({
451
+ message: "Select rule categories to uninstall:",
452
+ choices: installedRuleCategories.map((cat) => ({
453
+ name: chalk.bold(cat),
454
+ value: cat,
455
+ checked: false
456
+ }))
457
+ });
458
+ }
459
+ if (selectedCategories.includes("mcps") && installedMcpNames.length > 0) {
460
+ plan.mcpNames = await checkbox({
461
+ message: "Select MCP servers to uninstall:",
462
+ choices: installedMcpNames.map((name) => ({
463
+ name: chalk.bold(name),
464
+ value: name,
465
+ checked: false
466
+ }))
467
+ });
468
+ }
469
+ const totalToRemove = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcpNames.length;
470
+ if (totalToRemove === 0) {
471
+ console.log(chalk.yellow("\n Nothing selected. Exiting."));
472
+ return;
473
+ }
474
+ console.log(chalk.bold("\n Uninstall Summary"));
475
+ console.log(chalk.gray(" \u2500".repeat(40)));
476
+ console.log(` Scope : ${chalk.cyan(scope)} (${targetDir})`);
477
+ if (plan.agents.length) console.log(` Agents : ${plan.agents.map((a) => a.name).join(", ")}`);
478
+ if (plan.skills.length) console.log(` Skills : ${plan.skills.map((s) => s.name).join(", ")}`);
479
+ if (plan.commands.length)
480
+ console.log(` Commands: ${plan.commands.map((c) => c.name).join(", ")}`);
481
+ if (plan.hooks.length) console.log(` Hooks : ${plan.hooks.map((h) => h.name).join(", ")}`);
482
+ if (plan.ruleCategories.length)
483
+ console.log(` Rules : ${plan.ruleCategories.join(", ")}`);
484
+ if (plan.mcpNames.length) console.log(` MCPs : ${plan.mcpNames.join(", ")}`);
485
+ console.log("");
486
+ const ok = await confirm({
487
+ message: chalk.red("Proceed with uninstallation? This cannot be undone."),
488
+ default: false
489
+ });
490
+ if (!ok) {
491
+ console.log(chalk.yellow("\n Aborted."));
492
+ return;
493
+ }
494
+ console.log("");
495
+ const spinner = ora("Uninstalling...").start();
496
+ try {
497
+ if (plan.agents.length) {
498
+ spinner.text = "Removing agents...";
499
+ await uninstallAgents(plan.agents, targetDir);
500
+ }
501
+ if (plan.skills.length) {
502
+ spinner.text = "Removing skills...";
503
+ await uninstallSkills(plan.skills, targetDir);
504
+ }
505
+ if (plan.commands.length) {
506
+ spinner.text = "Removing commands...";
507
+ await uninstallCommands(plan.commands, targetDir);
508
+ }
509
+ if (plan.hooks.length) {
510
+ spinner.text = "Removing hooks...";
511
+ await uninstallHooks(plan.hooks, targetDir);
512
+ }
513
+ if (plan.ruleCategories.length) {
514
+ spinner.text = "Removing rules...";
515
+ const claudeMd = join8(targetDir, "CLAUDE.md");
516
+ await uninstallRules(plan.ruleCategories, claudeMd);
517
+ }
518
+ if (plan.mcpNames.length) {
519
+ spinner.text = "Removing MCP servers...";
520
+ await uninstallMcps(plan.mcpNames, scope);
521
+ }
522
+ spinner.succeed(chalk.green("Uninstallation complete!"));
523
+ console.log("");
524
+ if (plan.agents.length)
525
+ console.log(` ${chalk.red("\u2717")} ${plan.agents.length} agent(s) removed`);
526
+ if (plan.skills.length)
527
+ console.log(` ${chalk.red("\u2717")} ${plan.skills.length} skill(s) removed`);
528
+ if (plan.commands.length)
529
+ console.log(` ${chalk.red("\u2717")} ${plan.commands.length} command(s) removed`);
530
+ if (plan.hooks.length)
531
+ console.log(` ${chalk.red("\u2717")} ${plan.hooks.length} hook(s) removed`);
532
+ if (plan.ruleCategories.length)
533
+ console.log(` ${chalk.red("\u2717")} Rules removed (${plan.ruleCategories.join(", ")})`);
534
+ if (plan.mcpNames.length)
535
+ console.log(` ${chalk.red("\u2717")} ${plan.mcpNames.length} MCP server(s) removed`);
536
+ console.log("");
537
+ } catch (err) {
538
+ spinner.fail(chalk.red("Uninstallation failed"));
539
+ throw err;
540
+ }
541
+ }
542
+ export {
543
+ runUninstallCli
544
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juho0719/cckit",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Claude Code Harness Installer - Interactive CLI for installing Claude Code agents, skills, commands, hooks, rules, and MCP servers",
5
5
  "type": "module",
6
6
  "bin": {