@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
|
|
166
|
-
message: ` ${envKey}
|
|
167
|
-
|
|
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 (
|
|
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
|
|
24
|
-
cckit --
|
|
25
|
-
cckit --
|
|
26
|
-
cckit --
|
|
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
|
-
--
|
|
30
|
-
|
|
31
|
-
|
|
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-
|
|
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