@juho0719/cckit 0.1.1 → 0.2.1

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.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: Claude Behavioral Guidelines
3
+ description: LLM coding best practices - think before coding, simplicity, surgical changes, goal-driven execution
4
+ ---
5
+ # Claude Behavioral Guidelines
6
+
7
+ Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
8
+
9
+ **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
10
+
11
+ ## 1. Think Before Coding
12
+
13
+ **Don't assume. Don't hide confusion. Surface tradeoffs.**
14
+
15
+ Before implementing:
16
+ - State your assumptions explicitly. If uncertain, ask.
17
+ - If multiple interpretations exist, present them - don't pick silently.
18
+ - If a simpler approach exists, say so. Push back when warranted.
19
+ - If something is unclear, stop. Name what's confusing. Ask.
20
+
21
+ ## 2. Simplicity First
22
+
23
+ **Minimum code that solves the problem. Nothing speculative.**
24
+
25
+ - No features beyond what was asked.
26
+ - No abstractions for single-use code.
27
+ - No "flexibility" or "configurability" that wasn't requested.
28
+ - No error handling for impossible scenarios.
29
+ - If you write 200 lines and it could be 50, rewrite it.
30
+
31
+ Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
32
+
33
+ ## 3. Surgical Changes
34
+
35
+ **Touch only what you must. Clean up only your own mess.**
36
+
37
+ When editing existing code:
38
+ - Don't "improve" adjacent code, comments, or formatting.
39
+ - Don't refactor things that aren't broken.
40
+ - Match existing style, even if you'd do it differently.
41
+ - If you notice unrelated dead code, mention it - don't delete it.
42
+
43
+ When your changes create orphans:
44
+ - Remove imports/variables/functions that YOUR changes made unused.
45
+ - Don't remove pre-existing dead code unless asked.
46
+
47
+ The test: Every changed line should trace directly to the user's request.
48
+
49
+ ## 4. Goal-Driven Execution
50
+
51
+ **Define success criteria. Loop until verified.**
52
+
53
+ Transform tasks into verifiable goals:
54
+ - "Add validation" → "Write tests for invalid inputs, then make them pass"
55
+ - "Fix the bug" → "Write a test that reproduces it, then make it pass"
56
+ - "Refactor X" → "Ensure tests pass before and after"
57
+
58
+ For multi-step tasks, state a brief plan:
59
+ ```
60
+ 1. [Step] → verify: [check]
61
+ 2. [Step] → verify: [check]
62
+ 3. [Step] → verify: [check]
63
+ ```
64
+
65
+ Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
66
+
67
+ ---
68
+
69
+ **These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
@@ -98,6 +98,21 @@ async function getRuleCategories() {
98
98
  }
99
99
  return categories;
100
100
  }
101
+ async function getClaudemdItems() {
102
+ const dir = join(getAssetsDir(), "claudemd");
103
+ const files = (await readdir(dir)).filter((f) => f.endsWith(".md"));
104
+ const items = [];
105
+ for (const file of files) {
106
+ const content = await readFile(join(dir, file), "utf-8");
107
+ const fm = parseFrontmatter(content);
108
+ items.push({
109
+ name: fm["name"] || basename(file, ".md"),
110
+ description: fm["description"] || "",
111
+ file
112
+ });
113
+ }
114
+ return items;
115
+ }
101
116
  async function getMcpServers() {
102
117
  const filePath = join(getAssetsDir(), "mcps", "mcp-servers.json");
103
118
  const content = await readFile(filePath, "utf-8");
@@ -132,5 +147,6 @@ export {
132
147
  getCommands,
133
148
  getHooks,
134
149
  getRuleCategories,
150
+ getClaudemdItems,
135
151
  getMcpServers
136
152
  };
@@ -0,0 +1,35 @@
1
+ import {
2
+ getAssetsDir
3
+ } from "./chunk-5XOKKPAA.js";
4
+
5
+ // src/installers/claudemd.ts
6
+ import { join } from "path";
7
+ import { readFile, writeFile, access } from "fs/promises";
8
+ import { constants } from "fs";
9
+ async function installClaudemd(items, targetDir) {
10
+ const claudemdDir = join(getAssetsDir(), "claudemd");
11
+ const targetClaudeMd = join(targetDir, "CLAUDE.md");
12
+ let existing = "";
13
+ try {
14
+ await access(targetClaudeMd, constants.F_OK);
15
+ existing = await readFile(targetClaudeMd, "utf-8");
16
+ } catch {
17
+ }
18
+ const toAppend = [];
19
+ for (const item of items) {
20
+ const raw = await readFile(join(claudemdDir, item.file), "utf-8");
21
+ const content = raw.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "").trim();
22
+ const headerMatch = content.match(/^#{1,2}\s+.+/m);
23
+ if (headerMatch) {
24
+ if (existing.includes(headerMatch[0])) continue;
25
+ }
26
+ toAppend.push(content);
27
+ }
28
+ if (toAppend.length === 0) return;
29
+ const appendContent = (existing.endsWith("\n") ? "" : "\n") + toAppend.join("\n\n") + "\n";
30
+ await writeFile(targetClaudeMd, existing + appendContent, "utf-8");
31
+ }
32
+
33
+ export {
34
+ installClaudemd
35
+ };
@@ -0,0 +1,7 @@
1
+ import {
2
+ installClaudemd
3
+ } from "./chunk-SW3OJLHC.js";
4
+ import "./chunk-5XOKKPAA.js";
5
+ export {
6
+ installClaudemd
7
+ };
@@ -1,14 +1,18 @@
1
1
  import {
2
2
  installMcps
3
3
  } from "./chunk-W63UKEIT.js";
4
+ import {
5
+ installClaudemd
6
+ } from "./chunk-SW3OJLHC.js";
4
7
  import {
5
8
  getAgents,
9
+ getClaudemdItems,
6
10
  getCommands,
7
11
  getHooks,
8
12
  getMcpServers,
9
13
  getRuleCategories,
10
14
  getSkills
11
- } from "./chunk-6B46AIFM.js";
15
+ } from "./chunk-OLLOS3GG.js";
12
16
  import {
13
17
  installAgents
14
18
  } from "./chunk-EYY2IZ7N.js";
@@ -74,7 +78,8 @@ async function runCli() {
74
78
  { name: "Commands - Slash commands", value: "commands" },
75
79
  { name: "Hooks - Post-tool hooks", value: "hooks" },
76
80
  { name: "Rules - CLAUDE.md rules", value: "rules" },
77
- { name: "MCPs - MCP server configs", value: "mcps" }
81
+ { name: "MCPs - MCP server configs", value: "mcps" },
82
+ { name: "ClaudeMd - Behavioral guidelines", value: "claudemd" }
78
83
  ]
79
84
  });
80
85
  if (selectedCategories.length === 0) {
@@ -89,8 +94,10 @@ async function runCli() {
89
94
  hooks: [],
90
95
  ruleCategories: [],
91
96
  mcps: [],
92
- mcpEnvValues: /* @__PURE__ */ new Map()
97
+ mcpEnvValues: /* @__PURE__ */ new Map(),
98
+ claudemds: []
93
99
  };
100
+ const skippedMcps = /* @__PURE__ */ new Map();
94
101
  if (selectedCategories.includes("agents")) {
95
102
  const allAgents = await getAgents();
96
103
  plan.agents = await checkbox({
@@ -146,6 +153,17 @@ async function runCli() {
146
153
  }))
147
154
  });
148
155
  }
156
+ if (selectedCategories.includes("claudemd")) {
157
+ const allClaudemds = await getClaudemdItems();
158
+ plan.claudemds = await checkbox({
159
+ message: "Select claudemd items:",
160
+ choices: allClaudemds.map((c) => ({
161
+ name: `${chalk.bold(c.name.padEnd(35))} ${chalk.gray(c.description.slice(0, 55))}`,
162
+ value: c,
163
+ checked: false
164
+ }))
165
+ });
166
+ }
149
167
  if (selectedCategories.includes("mcps")) {
150
168
  const allMcps = await getMcpServers();
151
169
  plan.mcps = await checkbox({
@@ -161,14 +179,29 @@ async function runCli() {
161
179
  console.log(chalk.yellow(`
162
180
  ${server.name} requires environment variables:`));
163
181
  const envMap = {};
182
+ const skippedKeys = [];
164
183
  for (const envKey of server.envVars) {
165
- const value = await input({
166
- message: ` ${envKey}:`,
167
- default: ""
184
+ const action = await select({
185
+ message: ` How do you want to set ${chalk.bold(envKey)}?`,
186
+ choices: [
187
+ { name: "Enter value now", value: "enter" },
188
+ { name: `Skip ${chalk.gray(`(keep placeholder: YOUR_${envKey}_HERE)`)}`, value: "skip" }
189
+ ]
168
190
  });
169
- if (value) envMap[envKey] = value;
191
+ if (action === "skip") {
192
+ skippedKeys.push(envKey);
193
+ } else {
194
+ const value = await input({
195
+ message: ` ${envKey}:`,
196
+ default: ""
197
+ });
198
+ if (value) envMap[envKey] = value;
199
+ }
170
200
  }
171
201
  plan.mcpEnvValues.set(server.name, envMap);
202
+ if (skippedKeys.length > 0) {
203
+ skippedMcps.set(server.name, skippedKeys);
204
+ }
172
205
  }
173
206
  }
174
207
  console.log(chalk.bold("\n Installation Summary"));
@@ -180,8 +213,9 @@ async function runCli() {
180
213
  if (plan.hooks.length) console.log(` Hooks : ${plan.hooks.map((h) => h.name).join(", ")}`);
181
214
  if (plan.ruleCategories.length) console.log(` Rules : ${plan.ruleCategories.join(", ")}`);
182
215
  if (plan.mcps.length) console.log(` MCPs : ${plan.mcps.map((m) => m.name).join(", ")}`);
216
+ if (plan.claudemds.length) console.log(` ClaudeMd: ${plan.claudemds.map((c) => c.name).join(", ")}`);
183
217
  console.log("");
184
- const totalItems = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcps.length;
218
+ const totalItems = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcps.length + plan.claudemds.length;
185
219
  if (totalItems === 0) {
186
220
  console.log(chalk.yellow(" Nothing selected. Exiting."));
187
221
  return;
@@ -219,6 +253,10 @@ async function runCli() {
219
253
  spinner.text = "Installing MCP servers...";
220
254
  await installMcps(plan.mcps, plan.scope, plan.mcpEnvValues);
221
255
  }
256
+ if (plan.claudemds.length) {
257
+ spinner.text = "Installing claudemd...";
258
+ await installClaudemd(plan.claudemds, targetDir);
259
+ }
222
260
  spinner.succeed(chalk.green("Installation complete!"));
223
261
  console.log("");
224
262
  if (plan.agents.length) console.log(` ${chalk.green("\u2713")} ${plan.agents.length} agent(s) \u2192 ${join(targetDir, "agents")}`);
@@ -227,7 +265,17 @@ async function runCli() {
227
265
  if (plan.hooks.length) console.log(` ${chalk.green("\u2713")} ${plan.hooks.length} hook(s) \u2192 ${join(targetDir, "hooks")}`);
228
266
  if (plan.ruleCategories.length) console.log(` ${chalk.green("\u2713")} Rules appended \u2192 ${join(targetDir, "CLAUDE.md")}`);
229
267
  if (plan.mcps.length) console.log(` ${chalk.green("\u2713")} ${plan.mcps.length} MCP server(s) \u2192 ${scope === "global" ? "~/.claude.json" : "./.claude.json"}`);
268
+ if (plan.claudemds.length) console.log(` ${chalk.green("\u2713")} ClaudeMd appended \u2192 ${join(targetDir, "CLAUDE.md")}`);
230
269
  console.log("");
270
+ if (skippedMcps.size > 0) {
271
+ console.log(chalk.yellow(" \u26A0 The following MCP servers have placeholder env vars:"));
272
+ for (const [serverName, keys] of skippedMcps) {
273
+ console.log(` ${chalk.bold("-")} ${serverName} ${chalk.gray(`(${keys.join(", ")})`)} `);
274
+ }
275
+ const configPath = scope === "global" ? "~/.claude.json" : "./.claude.json";
276
+ console.log(chalk.gray(` \u2192 Edit ${configPath} to set the actual values`));
277
+ console.log("");
278
+ }
231
279
  } catch (err) {
232
280
  spinner.fail(chalk.red("Installation failed"));
233
281
  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) {
@@ -49,33 +51,38 @@ Installing all to ${targetDir}...
49
51
  { getHooks },
50
52
  { getRuleCategories },
51
53
  { getMcpServers },
54
+ { getClaudemdItems },
52
55
  { installAgents },
53
56
  { installSkills },
54
57
  { installCommands },
55
58
  { installHooks },
56
59
  { installRules },
57
- { installMcps }
60
+ { installMcps },
61
+ { installClaudemd }
58
62
  ] = await Promise.all([
59
- import("./registry-EGXWYWWK.js"),
60
- import("./registry-EGXWYWWK.js"),
61
- import("./registry-EGXWYWWK.js"),
62
- import("./registry-EGXWYWWK.js"),
63
- import("./registry-EGXWYWWK.js"),
64
- import("./registry-EGXWYWWK.js"),
63
+ import("./registry-BU55RMHU.js"),
64
+ import("./registry-BU55RMHU.js"),
65
+ import("./registry-BU55RMHU.js"),
66
+ import("./registry-BU55RMHU.js"),
67
+ import("./registry-BU55RMHU.js"),
68
+ import("./registry-BU55RMHU.js"),
69
+ import("./registry-BU55RMHU.js"),
65
70
  import("./agents-AEKT67A6.js"),
66
71
  import("./skills-ULMW3UCM.js"),
67
72
  import("./commands-P5LILVZ5.js"),
68
73
  import("./hooks-IIG2XK4I.js"),
69
74
  import("./rules-2CPBVNNJ.js"),
70
- import("./mcps-67Q7TBGW.js")
75
+ import("./mcps-67Q7TBGW.js"),
76
+ import("./claudemd-KKQ2DL7P.js")
71
77
  ]);
72
- const [agents, skills, commands, hooks, ruleCategories, mcps] = await Promise.all([
78
+ const [agents, skills, commands, hooks, ruleCategories, mcps, claudemds] = await Promise.all([
73
79
  getAgents(),
74
80
  getSkills(),
75
81
  getCommands(),
76
82
  getHooks(),
77
83
  getRuleCategories(),
78
- getMcpServers()
84
+ getMcpServers(),
85
+ getClaudemdItems()
79
86
  ]);
80
87
  spinner.text = "Installing agents...";
81
88
  await installAgents(agents, targetDir);
@@ -90,6 +97,8 @@ Installing all to ${targetDir}...
90
97
  await installRules(ruleCategories.map((rc) => rc.category), claudeMd);
91
98
  spinner.text = "Installing MCP servers...";
92
99
  await installMcps(mcps, scope, /* @__PURE__ */ new Map());
100
+ spinner.text = "Installing claudemd...";
101
+ await installClaudemd(claudemds, targetDir);
93
102
  spinner.succeed(chalk.green("All items installed successfully!"));
94
103
  console.log(`
95
104
  ${chalk.green("\u2713")} ${agents.length} agents`);
@@ -97,7 +106,8 @@ Installing all to ${targetDir}...
97
106
  console.log(` ${chalk.green("\u2713")} ${commands.length} commands`);
98
107
  console.log(` ${chalk.green("\u2713")} ${hooks.length} hooks`);
99
108
  console.log(` ${chalk.green("\u2713")} Rules (${ruleCategories.length} categories)`);
100
- console.log(` ${chalk.green("\u2713")} ${mcps.length} MCP servers
109
+ console.log(` ${chalk.green("\u2713")} ${mcps.length} MCP servers`);
110
+ console.log(` ${chalk.green("\u2713")} ${claudemds.length} claudemd item(s)
101
111
  `);
102
112
  } catch (err) {
103
113
  spinner.fail("Installation failed");
@@ -114,13 +124,18 @@ async function main() {
114
124
  printHelp();
115
125
  return;
116
126
  }
127
+ if (args.includes("--uninstall") || args.includes("-u")) {
128
+ const { runUninstallCli } = await import("./uninstall-cli-VCOZGDBM.js");
129
+ await runUninstallCli();
130
+ return;
131
+ }
117
132
  if (args.includes("--all")) {
118
133
  const scopeIdx = args.indexOf("--scope");
119
134
  const scope = scopeIdx !== -1 && args[scopeIdx + 1] === "project" ? "project" : "global";
120
135
  await installAll(scope);
121
136
  return;
122
137
  }
123
- const { runCli } = await import("./cli-VZRGF733.js");
138
+ const { runCli } = await import("./cli-D2Q5QUO7.js");
124
139
  await runCli();
125
140
  }
126
141
  main().catch((err) => {
@@ -1,14 +1,16 @@
1
1
  import {
2
2
  getAgents,
3
+ getClaudemdItems,
3
4
  getCommands,
4
5
  getHooks,
5
6
  getMcpServers,
6
7
  getRuleCategories,
7
8
  getSkills
8
- } from "./chunk-6B46AIFM.js";
9
+ } from "./chunk-OLLOS3GG.js";
9
10
  import "./chunk-5XOKKPAA.js";
10
11
  export {
11
12
  getAgents,
13
+ getClaudemdItems,
12
14
  getCommands,
13
15
  getHooks,
14
16
  getMcpServers,
@@ -0,0 +1,651 @@
1
+ import {
2
+ getAgents,
3
+ getClaudemdItems,
4
+ getCommands,
5
+ getHooks,
6
+ getMcpServers,
7
+ getRuleCategories,
8
+ getSkills
9
+ } from "./chunk-OLLOS3GG.js";
10
+ import {
11
+ getAssetsDir,
12
+ getGlobalDir,
13
+ getProjectDir
14
+ } from "./chunk-5XOKKPAA.js";
15
+
16
+ // src/uninstall-cli.ts
17
+ import { checkbox, select, confirm } from "@inquirer/prompts";
18
+ import chalk from "chalk";
19
+ import ora from "ora";
20
+ import { join as join9 } from "path";
21
+
22
+ // src/scanner.ts
23
+ import { readdir, readFile, access } from "fs/promises";
24
+ import { constants } from "fs";
25
+ import { join } from "path";
26
+ import { homedir } from "os";
27
+ function getClaudeJsonPath(scope) {
28
+ if (scope === "global") {
29
+ return join(homedir(), ".claude.json");
30
+ }
31
+ return join(process.cwd(), ".claude.json");
32
+ }
33
+ async function getInstalledAgents(targetDir) {
34
+ const agentsDir = join(targetDir, "agents");
35
+ let installedFiles = [];
36
+ try {
37
+ installedFiles = (await readdir(agentsDir)).filter((f) => f.endsWith(".md"));
38
+ } catch {
39
+ return [];
40
+ }
41
+ const registryAgents = await getAgents();
42
+ return registryAgents.filter((a) => installedFiles.includes(a.file));
43
+ }
44
+ async function getInstalledSkills(targetDir) {
45
+ const skillsDir = join(targetDir, "skills");
46
+ let installedDirs = [];
47
+ try {
48
+ const entries = await readdir(skillsDir, { withFileTypes: true });
49
+ installedDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
50
+ } catch {
51
+ return [];
52
+ }
53
+ const registrySkills = await getSkills();
54
+ return registrySkills.filter((s) => installedDirs.includes(s.dir));
55
+ }
56
+ async function getInstalledCommands(targetDir) {
57
+ const commandsDir = join(targetDir, "commands");
58
+ let installedFiles = [];
59
+ try {
60
+ installedFiles = (await readdir(commandsDir)).filter((f) => f.endsWith(".md"));
61
+ } catch {
62
+ return [];
63
+ }
64
+ const registryCommands = await getCommands();
65
+ return registryCommands.filter((c) => installedFiles.includes(c.file));
66
+ }
67
+ async function getInstalledHooks(targetDir) {
68
+ const hooksDir = join(targetDir, "hooks");
69
+ let installedFiles = [];
70
+ try {
71
+ installedFiles = (await readdir(hooksDir)).filter((f) => f.endsWith(".js"));
72
+ } catch {
73
+ return [];
74
+ }
75
+ const registryHooks = await getHooks();
76
+ return registryHooks.filter((h) => installedFiles.includes(h.file));
77
+ }
78
+ async function getInstalledRuleCategories(targetDir) {
79
+ const claudeMdPath = join(targetDir, "CLAUDE.md");
80
+ let content = "";
81
+ try {
82
+ await access(claudeMdPath, constants.F_OK);
83
+ content = await readFile(claudeMdPath, "utf-8");
84
+ } catch {
85
+ return [];
86
+ }
87
+ const installedHeaders = /* @__PURE__ */ new Set();
88
+ for (const line of content.split("\n")) {
89
+ const match = line.match(/^(#{1,2}\s+.+)/);
90
+ if (match) installedHeaders.add(match[1].trim());
91
+ }
92
+ const allCategories = await getRuleCategories();
93
+ const installedCategories = [];
94
+ for (const rc of allCategories) {
95
+ const catDir = join(getAssetsDir(), "rules", rc.category);
96
+ let found = false;
97
+ for (const file of rc.files) {
98
+ try {
99
+ const fileContent = await readFile(join(catDir, file), "utf-8");
100
+ const headerMatch = fileContent.match(/^(#{1,2}\s+.+)/m);
101
+ if (headerMatch && installedHeaders.has(headerMatch[1].trim())) {
102
+ found = true;
103
+ break;
104
+ }
105
+ } catch {
106
+ }
107
+ }
108
+ if (found) installedCategories.push(rc.category);
109
+ }
110
+ return installedCategories;
111
+ }
112
+ async function getInstalledClaudemd(targetDir) {
113
+ const claudeMdPath = join(targetDir, "CLAUDE.md");
114
+ let content = "";
115
+ try {
116
+ await access(claudeMdPath, constants.F_OK);
117
+ content = await readFile(claudeMdPath, "utf-8");
118
+ } catch {
119
+ return [];
120
+ }
121
+ const installedHeaders = /* @__PURE__ */ new Set();
122
+ for (const line of content.split("\n")) {
123
+ const match = line.match(/^(#{1,2}\s+.+)/);
124
+ if (match) installedHeaders.add(match[1].trim());
125
+ }
126
+ const allItems = await getClaudemdItems();
127
+ const result = [];
128
+ for (const item of allItems) {
129
+ try {
130
+ const raw = await readFile(join(getAssetsDir(), "claudemd", item.file), "utf-8");
131
+ const itemContent = raw.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
132
+ const headerMatch = itemContent.match(/^(#{1,2}\s+.+)/m);
133
+ if (headerMatch && installedHeaders.has(headerMatch[1].trim())) {
134
+ result.push(item);
135
+ }
136
+ } catch {
137
+ }
138
+ }
139
+ return result;
140
+ }
141
+ async function getInstalledMcps(scope) {
142
+ const filePath = getClaudeJsonPath(scope);
143
+ let claudeJson = {};
144
+ try {
145
+ await access(filePath, constants.F_OK);
146
+ const raw = await readFile(filePath, "utf-8");
147
+ claudeJson = JSON.parse(raw);
148
+ } catch {
149
+ return [];
150
+ }
151
+ if (!claudeJson.mcpServers) return [];
152
+ const installedNames = new Set(Object.keys(claudeJson.mcpServers));
153
+ const registryMcps = await getMcpServers();
154
+ return registryMcps.filter((m) => installedNames.has(m.name)).map((m) => m.name);
155
+ }
156
+
157
+ // src/uninstallers/agents.ts
158
+ import { join as join2 } from "path";
159
+ import { rm, access as access2 } from "fs/promises";
160
+ import { constants as constants2 } from "fs";
161
+ async function uninstallAgents(agents, targetDir) {
162
+ const agentsDir = join2(targetDir, "agents");
163
+ for (const agent of agents) {
164
+ const filePath = join2(agentsDir, agent.file);
165
+ try {
166
+ await access2(filePath, constants2.F_OK);
167
+ await rm(filePath);
168
+ } catch {
169
+ }
170
+ }
171
+ }
172
+
173
+ // src/uninstallers/skills.ts
174
+ import { join as join3 } from "path";
175
+ import { rm as rm2, access as access3 } from "fs/promises";
176
+ import { constants as constants3 } from "fs";
177
+ async function uninstallSkills(skills, targetDir) {
178
+ const skillsDir = join3(targetDir, "skills");
179
+ for (const skill of skills) {
180
+ const dirPath = join3(skillsDir, skill.dir);
181
+ try {
182
+ await access3(dirPath, constants3.F_OK);
183
+ await rm2(dirPath, { recursive: true });
184
+ } catch {
185
+ }
186
+ }
187
+ }
188
+
189
+ // src/uninstallers/commands.ts
190
+ import { join as join4 } from "path";
191
+ import { rm as rm3, access as access4 } from "fs/promises";
192
+ import { constants as constants4 } from "fs";
193
+ async function uninstallCommands(commands, targetDir) {
194
+ const commandsDir = join4(targetDir, "commands");
195
+ for (const cmd of commands) {
196
+ const filePath = join4(commandsDir, cmd.file);
197
+ try {
198
+ await access4(filePath, constants4.F_OK);
199
+ await rm3(filePath);
200
+ } catch {
201
+ }
202
+ }
203
+ }
204
+
205
+ // src/uninstallers/hooks.ts
206
+ import { join as join5 } from "path";
207
+ import { rm as rm4, readFile as readFile2, writeFile, access as access5 } from "fs/promises";
208
+ import { constants as constants5 } from "fs";
209
+ async function removeHookFromSettings(settingsPath, hookCommand) {
210
+ let settings = {};
211
+ try {
212
+ await access5(settingsPath, constants5.F_OK);
213
+ const content = await readFile2(settingsPath, "utf-8");
214
+ settings = JSON.parse(content);
215
+ } catch {
216
+ return;
217
+ }
218
+ if (!settings.hooks) return;
219
+ for (const hookType of Object.keys(settings.hooks)) {
220
+ const matchers = settings.hooks[hookType];
221
+ for (let i = matchers.length - 1; i >= 0; i--) {
222
+ const matcher = matchers[i];
223
+ matcher.hooks = matcher.hooks.filter((h) => h.command !== hookCommand);
224
+ if (matcher.hooks.length === 0) {
225
+ matchers.splice(i, 1);
226
+ }
227
+ }
228
+ if (matchers.length === 0) {
229
+ delete settings.hooks[hookType];
230
+ }
231
+ }
232
+ if (Object.keys(settings.hooks).length === 0) {
233
+ delete settings.hooks;
234
+ }
235
+ await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
236
+ }
237
+ async function uninstallHooks(hooks, targetDir) {
238
+ const hooksDir = join5(targetDir, "hooks");
239
+ const settingsPath = join5(targetDir, "settings.json");
240
+ for (const hook of hooks) {
241
+ const filePath = join5(hooksDir, hook.file);
242
+ const hookCommand = `node ${filePath}`;
243
+ await removeHookFromSettings(settingsPath, hookCommand);
244
+ try {
245
+ await access5(filePath, constants5.F_OK);
246
+ await rm4(filePath);
247
+ } catch {
248
+ }
249
+ }
250
+ }
251
+
252
+ // src/uninstallers/rules.ts
253
+ import { readFile as readFile3, writeFile as writeFile2, access as access6 } from "fs/promises";
254
+ import { constants as constants6 } from "fs";
255
+ import { join as join6 } from "path";
256
+ async function uninstallRules(categories, claudeMdPath) {
257
+ let existing = "";
258
+ try {
259
+ await access6(claudeMdPath, constants6.F_OK);
260
+ existing = await readFile3(claudeMdPath, "utf-8");
261
+ } catch {
262
+ return;
263
+ }
264
+ const headersToRemove = /* @__PURE__ */ new Set();
265
+ for (const category of categories) {
266
+ const catDir = join6(getAssetsDir(), "rules", category);
267
+ let files;
268
+ try {
269
+ const { readdir: readdir2 } = await import("fs/promises");
270
+ files = (await readdir2(catDir)).filter((f) => f.endsWith(".md"));
271
+ } catch {
272
+ continue;
273
+ }
274
+ for (const file of files) {
275
+ try {
276
+ const content = await readFile3(join6(catDir, file), "utf-8");
277
+ const headerMatch = content.match(/^(#{1,2}\s+.+)/m);
278
+ if (headerMatch) {
279
+ headersToRemove.add(headerMatch[1].trim());
280
+ }
281
+ } catch {
282
+ }
283
+ }
284
+ }
285
+ if (headersToRemove.size === 0) return;
286
+ const lines = existing.split("\n");
287
+ const result = [];
288
+ let inRemovedSection = false;
289
+ let currentDepth = 0;
290
+ for (const line of lines) {
291
+ const headerMatch = line.match(/^(#{1,2})\s+/);
292
+ if (headerMatch) {
293
+ const depth = headerMatch[1].length;
294
+ const trimmed = line.trim();
295
+ if (headersToRemove.has(trimmed)) {
296
+ inRemovedSection = true;
297
+ currentDepth = depth;
298
+ continue;
299
+ }
300
+ if (inRemovedSection && depth <= currentDepth) {
301
+ inRemovedSection = false;
302
+ }
303
+ }
304
+ if (!inRemovedSection) {
305
+ result.push(line);
306
+ }
307
+ }
308
+ const cleaned = result.join("\n").replace(/\n{3,}/g, "\n\n");
309
+ await writeFile2(claudeMdPath, cleaned, "utf-8");
310
+ }
311
+
312
+ // src/uninstallers/mcps.ts
313
+ import { join as join7 } from "path";
314
+ import { readFile as readFile4, writeFile as writeFile3, access as access7 } from "fs/promises";
315
+ import { constants as constants7 } from "fs";
316
+ import { homedir as homedir2 } from "os";
317
+ function getClaudeJsonPath2(scope) {
318
+ if (scope === "global") {
319
+ return join7(homedir2(), ".claude.json");
320
+ }
321
+ return join7(process.cwd(), ".claude.json");
322
+ }
323
+ async function uninstallMcps(serverNames, scope) {
324
+ const filePath = getClaudeJsonPath2(scope);
325
+ let claudeJson = {};
326
+ try {
327
+ await access7(filePath, constants7.F_OK);
328
+ const content = await readFile4(filePath, "utf-8");
329
+ claudeJson = JSON.parse(content);
330
+ } catch {
331
+ return;
332
+ }
333
+ if (!claudeJson.mcpServers) return;
334
+ for (const name of serverNames) {
335
+ delete claudeJson.mcpServers[name];
336
+ }
337
+ await writeFile3(filePath, JSON.stringify(claudeJson, null, 2) + "\n", "utf-8");
338
+ }
339
+
340
+ // src/uninstallers/claudemd.ts
341
+ import { readFile as readFile5, writeFile as writeFile4, access as access8 } from "fs/promises";
342
+ import { constants as constants8 } from "fs";
343
+ import { join as join8 } from "path";
344
+ async function uninstallClaudemd(items, targetDir) {
345
+ const claudeMdPath = join8(targetDir, "CLAUDE.md");
346
+ let existing = "";
347
+ try {
348
+ await access8(claudeMdPath, constants8.F_OK);
349
+ existing = await readFile5(claudeMdPath, "utf-8");
350
+ } catch {
351
+ return;
352
+ }
353
+ const headersToRemove = /* @__PURE__ */ new Set();
354
+ for (const item of items) {
355
+ try {
356
+ const raw = await readFile5(join8(getAssetsDir(), "claudemd", item.file), "utf-8");
357
+ const content = raw.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
358
+ const headerMatch = content.match(/^(#{1,2}\s+.+)/m);
359
+ if (headerMatch) {
360
+ headersToRemove.add(headerMatch[1].trim());
361
+ }
362
+ } catch {
363
+ }
364
+ }
365
+ if (headersToRemove.size === 0) return;
366
+ const lines = existing.split("\n");
367
+ const result = [];
368
+ let inRemovedSection = false;
369
+ let currentDepth = 0;
370
+ for (const line of lines) {
371
+ const headerMatch = line.match(/^(#{1,2})\s+/);
372
+ if (headerMatch) {
373
+ const depth = headerMatch[1].length;
374
+ const trimmed = line.trim();
375
+ if (headersToRemove.has(trimmed)) {
376
+ inRemovedSection = true;
377
+ currentDepth = depth;
378
+ continue;
379
+ }
380
+ if (inRemovedSection && depth <= currentDepth) {
381
+ inRemovedSection = false;
382
+ }
383
+ }
384
+ if (!inRemovedSection) {
385
+ result.push(line);
386
+ }
387
+ }
388
+ const cleaned = result.join("\n").replace(/\n{3,}/g, "\n\n");
389
+ await writeFile4(claudeMdPath, cleaned, "utf-8");
390
+ }
391
+
392
+ // src/uninstall-cli.ts
393
+ function printBanner() {
394
+ console.log(
395
+ chalk.red.bold(`
396
+ \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
397
+ \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
398
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551
399
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551
400
+ \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
401
+ \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
402
+ `)
403
+ );
404
+ console.log(chalk.gray(" Claude Code Harness Uninstaller\n"));
405
+ }
406
+ async function runUninstallCli() {
407
+ printBanner();
408
+ const scope = await select({
409
+ message: "Uninstall scope:",
410
+ choices: [
411
+ {
412
+ name: `Global ${chalk.gray("~/.claude/")}`,
413
+ value: "global"
414
+ },
415
+ {
416
+ name: `Project ${chalk.gray("./.claude/")}`,
417
+ value: "project"
418
+ }
419
+ ]
420
+ });
421
+ const targetDir = scope === "global" ? getGlobalDir() : getProjectDir();
422
+ console.log(chalk.gray("\n Scanning installed items...\n"));
423
+ const [
424
+ installedAgents,
425
+ installedSkills,
426
+ installedCommands,
427
+ installedHooks,
428
+ installedRuleCategories,
429
+ installedMcpNames,
430
+ installedClaudemds
431
+ ] = await Promise.all([
432
+ getInstalledAgents(targetDir),
433
+ getInstalledSkills(targetDir),
434
+ getInstalledCommands(targetDir),
435
+ getInstalledHooks(targetDir),
436
+ getInstalledRuleCategories(targetDir),
437
+ getInstalledMcps(scope),
438
+ getInstalledClaudemd(targetDir)
439
+ ]);
440
+ const totalInstalled = installedAgents.length + installedSkills.length + installedCommands.length + installedHooks.length + installedRuleCategories.length + installedMcpNames.length + installedClaudemds.length;
441
+ if (totalInstalled === 0) {
442
+ console.log(chalk.yellow(" Nothing installed. Exiting."));
443
+ return;
444
+ }
445
+ const availableCategories = [];
446
+ if (installedAgents.length > 0)
447
+ availableCategories.push({
448
+ name: `Agents ${chalk.gray(`(${installedAgents.length} installed)`)}`,
449
+ value: "agents"
450
+ });
451
+ if (installedSkills.length > 0)
452
+ availableCategories.push({
453
+ name: `Skills ${chalk.gray(`(${installedSkills.length} installed)`)}`,
454
+ value: "skills"
455
+ });
456
+ if (installedCommands.length > 0)
457
+ availableCategories.push({
458
+ name: `Commands ${chalk.gray(`(${installedCommands.length} installed)`)}`,
459
+ value: "commands"
460
+ });
461
+ if (installedHooks.length > 0)
462
+ availableCategories.push({
463
+ name: `Hooks ${chalk.gray(`(${installedHooks.length} installed)`)}`,
464
+ value: "hooks"
465
+ });
466
+ if (installedRuleCategories.length > 0)
467
+ availableCategories.push({
468
+ name: `Rules ${chalk.gray(`(${installedRuleCategories.length} categories)`)}`,
469
+ value: "rules"
470
+ });
471
+ if (installedMcpNames.length > 0)
472
+ availableCategories.push({
473
+ name: `MCPs ${chalk.gray(`(${installedMcpNames.length} installed)`)}`,
474
+ value: "mcps"
475
+ });
476
+ if (installedClaudemds.length > 0)
477
+ availableCategories.push({
478
+ name: `ClaudeMd ${chalk.gray(`(${installedClaudemds.length} installed)`)}`,
479
+ value: "claudemd"
480
+ });
481
+ const selectedCategories = await checkbox({
482
+ message: "Select categories to uninstall:",
483
+ choices: availableCategories
484
+ });
485
+ if (selectedCategories.length === 0) {
486
+ console.log(chalk.yellow("\n No categories selected. Exiting."));
487
+ return;
488
+ }
489
+ const plan = {
490
+ scope,
491
+ agents: [],
492
+ skills: [],
493
+ commands: [],
494
+ hooks: [],
495
+ ruleCategories: [],
496
+ mcpNames: [],
497
+ claudemds: []
498
+ };
499
+ if (selectedCategories.includes("agents") && installedAgents.length > 0) {
500
+ plan.agents = await checkbox({
501
+ message: "Select agents to uninstall:",
502
+ choices: installedAgents.map((a) => ({
503
+ name: `${chalk.bold(a.name.padEnd(30))} ${chalk.gray(a.description.slice(0, 60))}`,
504
+ value: a,
505
+ checked: false
506
+ }))
507
+ });
508
+ }
509
+ if (selectedCategories.includes("skills") && installedSkills.length > 0) {
510
+ plan.skills = await checkbox({
511
+ message: "Select skills to uninstall:",
512
+ choices: installedSkills.map((s) => ({
513
+ name: `${chalk.bold(s.name.padEnd(35))} ${chalk.gray(s.description.slice(0, 55))}`,
514
+ value: s,
515
+ checked: false
516
+ }))
517
+ });
518
+ }
519
+ if (selectedCategories.includes("commands") && installedCommands.length > 0) {
520
+ plan.commands = await checkbox({
521
+ message: "Select commands to uninstall:",
522
+ choices: installedCommands.map((c) => ({
523
+ name: `${chalk.bold(c.name.padEnd(25))} ${chalk.gray(c.description.slice(0, 65))}`,
524
+ value: c,
525
+ checked: false
526
+ }))
527
+ });
528
+ }
529
+ if (selectedCategories.includes("hooks") && installedHooks.length > 0) {
530
+ plan.hooks = await checkbox({
531
+ message: "Select hooks to uninstall:",
532
+ choices: installedHooks.map((h) => ({
533
+ name: `${chalk.bold(h.name.padEnd(35))} ${chalk.gray(h.description.slice(0, 55))}`,
534
+ value: h,
535
+ checked: false
536
+ }))
537
+ });
538
+ }
539
+ if (selectedCategories.includes("rules") && installedRuleCategories.length > 0) {
540
+ plan.ruleCategories = await checkbox({
541
+ message: "Select rule categories to uninstall:",
542
+ choices: installedRuleCategories.map((cat) => ({
543
+ name: chalk.bold(cat),
544
+ value: cat,
545
+ checked: false
546
+ }))
547
+ });
548
+ }
549
+ if (selectedCategories.includes("mcps") && installedMcpNames.length > 0) {
550
+ plan.mcpNames = await checkbox({
551
+ message: "Select MCP servers to uninstall:",
552
+ choices: installedMcpNames.map((name) => ({
553
+ name: chalk.bold(name),
554
+ value: name,
555
+ checked: false
556
+ }))
557
+ });
558
+ }
559
+ if (selectedCategories.includes("claudemd") && installedClaudemds.length > 0) {
560
+ plan.claudemds = await checkbox({
561
+ message: "Select claudemd items to uninstall:",
562
+ choices: installedClaudemds.map((c) => ({
563
+ name: `${chalk.bold(c.name.padEnd(35))} ${chalk.gray(c.description.slice(0, 55))}`,
564
+ value: c,
565
+ checked: false
566
+ }))
567
+ });
568
+ }
569
+ const totalToRemove = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcpNames.length + plan.claudemds.length;
570
+ if (totalToRemove === 0) {
571
+ console.log(chalk.yellow("\n Nothing selected. Exiting."));
572
+ return;
573
+ }
574
+ console.log(chalk.bold("\n Uninstall Summary"));
575
+ console.log(chalk.gray(" \u2500".repeat(40)));
576
+ console.log(` Scope : ${chalk.cyan(scope)} (${targetDir})`);
577
+ if (plan.agents.length) console.log(` Agents : ${plan.agents.map((a) => a.name).join(", ")}`);
578
+ if (plan.skills.length) console.log(` Skills : ${plan.skills.map((s) => s.name).join(", ")}`);
579
+ if (plan.commands.length)
580
+ console.log(` Commands: ${plan.commands.map((c) => c.name).join(", ")}`);
581
+ if (plan.hooks.length) console.log(` Hooks : ${plan.hooks.map((h) => h.name).join(", ")}`);
582
+ if (plan.ruleCategories.length)
583
+ console.log(` Rules : ${plan.ruleCategories.join(", ")}`);
584
+ if (plan.mcpNames.length) console.log(` MCPs : ${plan.mcpNames.join(", ")}`);
585
+ if (plan.claudemds.length) console.log(` ClaudeMd: ${plan.claudemds.map((c) => c.name).join(", ")}`);
586
+ console.log("");
587
+ const ok = await confirm({
588
+ message: chalk.red("Proceed with uninstallation? This cannot be undone."),
589
+ default: false
590
+ });
591
+ if (!ok) {
592
+ console.log(chalk.yellow("\n Aborted."));
593
+ return;
594
+ }
595
+ console.log("");
596
+ const spinner = ora("Uninstalling...").start();
597
+ try {
598
+ if (plan.agents.length) {
599
+ spinner.text = "Removing agents...";
600
+ await uninstallAgents(plan.agents, targetDir);
601
+ }
602
+ if (plan.skills.length) {
603
+ spinner.text = "Removing skills...";
604
+ await uninstallSkills(plan.skills, targetDir);
605
+ }
606
+ if (plan.commands.length) {
607
+ spinner.text = "Removing commands...";
608
+ await uninstallCommands(plan.commands, targetDir);
609
+ }
610
+ if (plan.hooks.length) {
611
+ spinner.text = "Removing hooks...";
612
+ await uninstallHooks(plan.hooks, targetDir);
613
+ }
614
+ if (plan.ruleCategories.length) {
615
+ spinner.text = "Removing rules...";
616
+ const claudeMd = join9(targetDir, "CLAUDE.md");
617
+ await uninstallRules(plan.ruleCategories, claudeMd);
618
+ }
619
+ if (plan.mcpNames.length) {
620
+ spinner.text = "Removing MCP servers...";
621
+ await uninstallMcps(plan.mcpNames, scope);
622
+ }
623
+ if (plan.claudemds.length) {
624
+ spinner.text = "Removing claudemd...";
625
+ await uninstallClaudemd(plan.claudemds, targetDir);
626
+ }
627
+ spinner.succeed(chalk.green("Uninstallation complete!"));
628
+ console.log("");
629
+ if (plan.agents.length)
630
+ console.log(` ${chalk.red("\u2717")} ${plan.agents.length} agent(s) removed`);
631
+ if (plan.skills.length)
632
+ console.log(` ${chalk.red("\u2717")} ${plan.skills.length} skill(s) removed`);
633
+ if (plan.commands.length)
634
+ console.log(` ${chalk.red("\u2717")} ${plan.commands.length} command(s) removed`);
635
+ if (plan.hooks.length)
636
+ console.log(` ${chalk.red("\u2717")} ${plan.hooks.length} hook(s) removed`);
637
+ if (plan.ruleCategories.length)
638
+ console.log(` ${chalk.red("\u2717")} Rules removed (${plan.ruleCategories.join(", ")})`);
639
+ if (plan.mcpNames.length)
640
+ console.log(` ${chalk.red("\u2717")} ${plan.mcpNames.length} MCP server(s) removed`);
641
+ if (plan.claudemds.length)
642
+ console.log(` ${chalk.red("\u2717")} ClaudeMd removed (${plan.claudemds.map((c) => c.name).join(", ")})`);
643
+ console.log("");
644
+ } catch (err) {
645
+ spinner.fail(chalk.red("Uninstallation failed"));
646
+ throw err;
647
+ }
648
+ }
649
+ export {
650
+ runUninstallCli
651
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juho0719/cckit",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
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": {