@juho0719/cckit 0.2.5 → 0.2.7

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,477 @@
1
+ import {
2
+ installMcps
3
+ } from "./chunk-W63UKEIT.js";
4
+ import {
5
+ installClaudemd
6
+ } from "./chunk-SW3OJLHC.js";
7
+ import {
8
+ getAgents,
9
+ getClaudemdItems,
10
+ getCommands,
11
+ getHooks,
12
+ getMcpServers,
13
+ getRuleCategories,
14
+ getSkills
15
+ } from "./chunk-E3INXQNO.js";
16
+ import {
17
+ installAgents
18
+ } from "./chunk-EYY2IZ7N.js";
19
+ import {
20
+ installSkills
21
+ } from "./chunk-KEENFBLL.js";
22
+ import {
23
+ installCommands
24
+ } from "./chunk-3Y26YU4R.js";
25
+ import {
26
+ installHooks
27
+ } from "./chunk-W7RWPDBH.js";
28
+ import {
29
+ installRules
30
+ } from "./chunk-ID643AV4.js";
31
+ import {
32
+ backupIfExists
33
+ } from "./chunk-K25UZZVG.js";
34
+ import {
35
+ copyFileUtil
36
+ } from "./chunk-3GUKEMND.js";
37
+ import {
38
+ getAssetsDir,
39
+ getGlobalDir,
40
+ getProjectDir
41
+ } from "./chunk-5XOKKPAA.js";
42
+
43
+ // src/cli.ts
44
+ import { checkbox, select, input, confirm } from "@inquirer/prompts";
45
+ import { AbortPromptError, ExitPromptError } from "@inquirer/core";
46
+ import chalk from "chalk";
47
+ import ora from "ora";
48
+ import { join as join2 } from "path";
49
+
50
+ // src/installers/statusline.ts
51
+ import { join } from "path";
52
+ import { chmod } from "fs/promises";
53
+ async function installStatusline() {
54
+ const src = join(getAssetsDir(), "statusline", "statusline.sh");
55
+ const dest = join(getGlobalDir(), "statusline.sh");
56
+ await backupIfExists(dest);
57
+ await copyFileUtil(src, dest);
58
+ await chmod(dest, 493);
59
+ }
60
+
61
+ // src/cli.ts
62
+ var BACK = /* @__PURE__ */ Symbol("BACK");
63
+ var STEP_SCOPE = 0;
64
+ var STEP_CATEGORIES = 1;
65
+ var STEP_ITEMS = 2;
66
+ var STEP_CONFIRM = 3;
67
+ async function withEsc(promptFn) {
68
+ const ac = new AbortController();
69
+ const onData = (data) => {
70
+ if (data.length === 1 && data[0] === 27) {
71
+ ac.abort();
72
+ }
73
+ };
74
+ process.stdin.on("data", onData);
75
+ try {
76
+ return await promptFn({ signal: ac.signal });
77
+ } catch (err) {
78
+ if (err instanceof AbortPromptError) {
79
+ return BACK;
80
+ }
81
+ throw err;
82
+ } finally {
83
+ process.stdin.removeListener("data", onData);
84
+ }
85
+ }
86
+ function printBanner() {
87
+ console.log(
88
+ chalk.cyan.bold(`
89
+ \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
90
+ \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
91
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551
92
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551
93
+ \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
94
+ \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
95
+ `)
96
+ );
97
+ console.log(chalk.gray(" Claude Code Harness Installer"));
98
+ console.log(chalk.gray(" ESC: back / Ctrl+C: exit\n"));
99
+ }
100
+ function createEmptyPlan(scope) {
101
+ return {
102
+ scope,
103
+ agents: [],
104
+ skills: [],
105
+ commands: [],
106
+ hooks: [],
107
+ ruleCategories: [],
108
+ mcps: [],
109
+ mcpEnvValues: /* @__PURE__ */ new Map(),
110
+ claudemds: [],
111
+ statusline: false
112
+ };
113
+ }
114
+ async function runCli() {
115
+ printBanner();
116
+ let step = STEP_SCOPE;
117
+ let scope = "global";
118
+ let targetDir = "";
119
+ let selectedCategories = [];
120
+ let plan = createEmptyPlan(scope);
121
+ let skippedMcps = /* @__PURE__ */ new Map();
122
+ try {
123
+ while (step <= STEP_CONFIRM) {
124
+ switch (step) {
125
+ // ── Step 0: 설치 범위 선택 ──
126
+ case STEP_SCOPE: {
127
+ const result = await withEsc(
128
+ (ctx) => select({
129
+ message: "Install scope:",
130
+ choices: [
131
+ {
132
+ name: `Global ${chalk.gray("~/.claude/")}`,
133
+ value: "global"
134
+ },
135
+ {
136
+ name: `Project ${chalk.gray("./.claude/")}`,
137
+ value: "project"
138
+ }
139
+ ]
140
+ }, ctx)
141
+ );
142
+ if (result === BACK) {
143
+ console.log(chalk.yellow("\n Exiting."));
144
+ return;
145
+ }
146
+ scope = result;
147
+ targetDir = scope === "global" ? getGlobalDir() : getProjectDir();
148
+ step = STEP_CATEGORIES;
149
+ break;
150
+ }
151
+ // ── Step 1: 카테고리 멀티셀렉트 ──
152
+ case STEP_CATEGORIES: {
153
+ const result = await withEsc(
154
+ (ctx) => checkbox({
155
+ message: "Select categories to install:",
156
+ choices: [
157
+ { name: "Agents - AI sub-agents", value: "agents" },
158
+ { name: "Skills - Reusable skill prompts", value: "skills" },
159
+ { name: "Commands - Slash commands", value: "commands" },
160
+ { name: "Hooks - Post-tool hooks", value: "hooks" },
161
+ { name: "Rules - CLAUDE.md rules", value: "rules" },
162
+ { name: "MCPs - MCP server configs", value: "mcps" },
163
+ { name: "ClaudeMd - Behavioral guidelines", value: "claudemd" },
164
+ { name: "Statusline - Terminal statusline script", value: "statusline" }
165
+ ]
166
+ }, ctx)
167
+ );
168
+ if (result === BACK) {
169
+ step = STEP_SCOPE;
170
+ break;
171
+ }
172
+ if (result.length === 0) {
173
+ console.log(chalk.yellow("\n No categories selected.\n"));
174
+ break;
175
+ }
176
+ selectedCategories = result;
177
+ plan = createEmptyPlan(scope);
178
+ skippedMcps = /* @__PURE__ */ new Map();
179
+ step = STEP_ITEMS;
180
+ break;
181
+ }
182
+ // ── Step 2: 각 카테고리별 항목 선택 ──
183
+ case STEP_ITEMS: {
184
+ let wentBack = false;
185
+ for (const cat of selectedCategories) {
186
+ if (cat === "statusline") {
187
+ plan.statusline = true;
188
+ continue;
189
+ }
190
+ if (cat === "agents") {
191
+ const allAgents = await getAgents();
192
+ const result = await withEsc(
193
+ (ctx) => checkbox({
194
+ message: "Select agents:",
195
+ choices: allAgents.map((a) => ({
196
+ name: `${chalk.bold(a.name.padEnd(30))} ${chalk.gray(a.description.slice(0, 60))}`,
197
+ value: a,
198
+ checked: false
199
+ }))
200
+ }, ctx)
201
+ );
202
+ if (result === BACK) {
203
+ wentBack = true;
204
+ break;
205
+ }
206
+ plan.agents = result;
207
+ }
208
+ if (cat === "skills") {
209
+ const allSkills = await getSkills();
210
+ const result = await withEsc(
211
+ (ctx) => checkbox({
212
+ message: "Select skills:",
213
+ choices: allSkills.map((s) => ({
214
+ name: `${chalk.bold(s.name.padEnd(35))} ${chalk.gray(s.description.slice(0, 55))}`,
215
+ value: s,
216
+ checked: false
217
+ }))
218
+ }, ctx)
219
+ );
220
+ if (result === BACK) {
221
+ wentBack = true;
222
+ break;
223
+ }
224
+ plan.skills = result;
225
+ }
226
+ if (cat === "commands") {
227
+ const allCommands = await getCommands();
228
+ const result = await withEsc(
229
+ (ctx) => checkbox({
230
+ message: "Select commands:",
231
+ choices: allCommands.map((c) => ({
232
+ name: `${chalk.bold(c.name.padEnd(25))} ${chalk.gray(c.description.slice(0, 65))}`,
233
+ value: c,
234
+ checked: false
235
+ }))
236
+ }, ctx)
237
+ );
238
+ if (result === BACK) {
239
+ wentBack = true;
240
+ break;
241
+ }
242
+ plan.commands = result;
243
+ }
244
+ if (cat === "hooks") {
245
+ const allHooks = await getHooks();
246
+ const result = await withEsc(
247
+ (ctx) => checkbox({
248
+ message: "Select hooks:",
249
+ choices: allHooks.map((h) => ({
250
+ name: `${chalk.bold(h.name.padEnd(35))} ${chalk.gray(h.description.slice(0, 55))}`,
251
+ value: h,
252
+ checked: false
253
+ }))
254
+ }, ctx)
255
+ );
256
+ if (result === BACK) {
257
+ wentBack = true;
258
+ break;
259
+ }
260
+ plan.hooks = result;
261
+ }
262
+ if (cat === "rules") {
263
+ const allCategories = await getRuleCategories();
264
+ const result = await withEsc(
265
+ (ctx) => checkbox({
266
+ message: "Select rule categories:",
267
+ choices: allCategories.map((rc) => ({
268
+ name: `${chalk.bold(rc.category.padEnd(20))} ${chalk.gray(`(${rc.files.length} files)`)}`,
269
+ value: rc.category,
270
+ checked: false
271
+ }))
272
+ }, ctx)
273
+ );
274
+ if (result === BACK) {
275
+ wentBack = true;
276
+ break;
277
+ }
278
+ plan.ruleCategories = result;
279
+ }
280
+ if (cat === "claudemd") {
281
+ const allClaudemds = await getClaudemdItems();
282
+ const result = await withEsc(
283
+ (ctx) => checkbox({
284
+ message: "Select claudemd items:",
285
+ choices: allClaudemds.map((c) => ({
286
+ name: `${chalk.bold(c.name.padEnd(35))} ${chalk.gray(c.description.slice(0, 55))}`,
287
+ value: c,
288
+ checked: false
289
+ }))
290
+ }, ctx)
291
+ );
292
+ if (result === BACK) {
293
+ wentBack = true;
294
+ break;
295
+ }
296
+ plan.claudemds = result;
297
+ }
298
+ if (cat === "mcps") {
299
+ const allMcps = await getMcpServers();
300
+ const mcpResult = await withEsc(
301
+ (ctx) => checkbox({
302
+ message: "Select MCP servers:",
303
+ choices: allMcps.map((m) => ({
304
+ name: `${chalk.bold(m.name.padEnd(30))} ${chalk.gray(m.description.slice(0, 55))}`,
305
+ value: m,
306
+ checked: false
307
+ }))
308
+ }, ctx)
309
+ );
310
+ if (mcpResult === BACK) {
311
+ wentBack = true;
312
+ break;
313
+ }
314
+ plan.mcps = mcpResult;
315
+ for (const server of plan.mcps) {
316
+ if (server.type === "http" || server.envVars.length === 0) continue;
317
+ console.log(chalk.yellow(`
318
+ ${server.name} requires environment variables:`));
319
+ const envMap = {};
320
+ const skippedKeys = [];
321
+ for (const envKey of server.envVars) {
322
+ const action = await withEsc(
323
+ (ctx) => select({
324
+ message: ` How do you want to set ${chalk.bold(envKey)}?`,
325
+ choices: [
326
+ { name: "Enter value now", value: "enter" },
327
+ { name: `Skip ${chalk.gray(`(keep placeholder: YOUR_${envKey}_HERE)`)}`, value: "skip" }
328
+ ]
329
+ }, ctx)
330
+ );
331
+ if (action === BACK) {
332
+ wentBack = true;
333
+ break;
334
+ }
335
+ if (action === "skip") {
336
+ skippedKeys.push(envKey);
337
+ } else {
338
+ const value = await withEsc(
339
+ (ctx) => input({
340
+ message: ` ${envKey}:`,
341
+ default: ""
342
+ }, ctx)
343
+ );
344
+ if (value === BACK) {
345
+ wentBack = true;
346
+ break;
347
+ }
348
+ if (value) envMap[envKey] = value;
349
+ }
350
+ }
351
+ if (wentBack) break;
352
+ plan.mcpEnvValues.set(server.name, envMap);
353
+ if (skippedKeys.length > 0) {
354
+ skippedMcps.set(server.name, skippedKeys);
355
+ }
356
+ }
357
+ if (wentBack) break;
358
+ }
359
+ }
360
+ if (wentBack) {
361
+ plan = createEmptyPlan(scope);
362
+ skippedMcps = /* @__PURE__ */ new Map();
363
+ step = STEP_CATEGORIES;
364
+ } else {
365
+ step = STEP_CONFIRM;
366
+ }
367
+ break;
368
+ }
369
+ // ── Step 3: 설치 요약 + 확인 ──
370
+ case STEP_CONFIRM: {
371
+ console.log(chalk.bold("\n Installation Summary"));
372
+ console.log(chalk.gray(" \u2500".repeat(40)));
373
+ console.log(` Scope : ${chalk.cyan(scope)} (${targetDir})`);
374
+ if (plan.agents.length) console.log(` Agents : ${plan.agents.map((a) => a.name).join(", ")}`);
375
+ if (plan.skills.length) console.log(` Skills : ${plan.skills.map((s) => s.name).join(", ")}`);
376
+ if (plan.commands.length) console.log(` Commands: ${plan.commands.map((c) => c.name).join(", ")}`);
377
+ if (plan.hooks.length) console.log(` Hooks : ${plan.hooks.map((h) => h.name).join(", ")}`);
378
+ if (plan.ruleCategories.length) console.log(` Rules : ${plan.ruleCategories.join(", ")}`);
379
+ if (plan.mcps.length) console.log(` MCPs : ${plan.mcps.map((m) => m.name).join(", ")}`);
380
+ if (plan.claudemds.length) console.log(` ClaudeMd : ${plan.claudemds.map((c) => c.name).join(", ")}`);
381
+ if (plan.statusline) console.log(` Statusline: statusline.sh \u2192 ~/.claude/statusline.sh`);
382
+ console.log("");
383
+ const totalItems = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcps.length + plan.claudemds.length + (plan.statusline ? 1 : 0);
384
+ if (totalItems === 0) {
385
+ console.log(chalk.yellow(" Nothing selected.\n"));
386
+ plan = createEmptyPlan(scope);
387
+ skippedMcps = /* @__PURE__ */ new Map();
388
+ step = STEP_CATEGORIES;
389
+ break;
390
+ }
391
+ const ok = await withEsc(
392
+ (ctx) => confirm({ message: "Proceed with installation?", default: true }, ctx)
393
+ );
394
+ if (ok === BACK) {
395
+ plan = createEmptyPlan(scope);
396
+ skippedMcps = /* @__PURE__ */ new Map();
397
+ step = STEP_CATEGORIES;
398
+ break;
399
+ }
400
+ if (!ok) {
401
+ console.log(chalk.yellow("\n Aborted."));
402
+ return;
403
+ }
404
+ console.log("");
405
+ const spinner = ora("Installing...").start();
406
+ try {
407
+ if (plan.agents.length) {
408
+ spinner.text = "Installing agents...";
409
+ await installAgents(plan.agents, targetDir);
410
+ }
411
+ if (plan.skills.length) {
412
+ spinner.text = "Installing skills...";
413
+ await installSkills(plan.skills, targetDir);
414
+ }
415
+ if (plan.commands.length) {
416
+ spinner.text = "Installing commands...";
417
+ await installCommands(plan.commands, targetDir);
418
+ }
419
+ if (plan.hooks.length) {
420
+ spinner.text = "Installing hooks...";
421
+ await installHooks(plan.hooks, targetDir);
422
+ }
423
+ if (plan.ruleCategories.length) {
424
+ spinner.text = "Installing rules...";
425
+ await installRules(plan.ruleCategories, targetDir);
426
+ }
427
+ if (plan.mcps.length) {
428
+ spinner.text = "Installing MCP servers...";
429
+ await installMcps(plan.mcps, plan.scope, plan.mcpEnvValues);
430
+ }
431
+ if (plan.claudemds.length) {
432
+ spinner.text = "Installing claudemd...";
433
+ await installClaudemd(plan.claudemds, targetDir);
434
+ }
435
+ if (plan.statusline) {
436
+ spinner.text = "Installing statusline...";
437
+ await installStatusline();
438
+ }
439
+ spinner.succeed(chalk.green("Installation complete!"));
440
+ console.log("");
441
+ if (plan.agents.length) console.log(` ${chalk.green("\u2713")} ${plan.agents.length} agent(s) \u2192 ${join2(targetDir, "agents")}`);
442
+ if (plan.skills.length) console.log(` ${chalk.green("\u2713")} ${plan.skills.length} skill(s) \u2192 ${join2(targetDir, "skills")}`);
443
+ if (plan.commands.length) console.log(` ${chalk.green("\u2713")} ${plan.commands.length} command(s) \u2192 ${join2(targetDir, "commands")}`);
444
+ if (plan.hooks.length) console.log(` ${chalk.green("\u2713")} ${plan.hooks.length} hook(s) \u2192 ${join2(targetDir, "hooks")}`);
445
+ if (plan.ruleCategories.length) console.log(` ${chalk.green("\u2713")} Rules copied \u2192 ${join2(targetDir, "rules")}`);
446
+ if (plan.mcps.length) console.log(` ${chalk.green("\u2713")} ${plan.mcps.length} MCP server(s) \u2192 ${scope === "global" ? "~/.claude.json" : "./.claude.json"}`);
447
+ if (plan.claudemds.length) console.log(` ${chalk.green("\u2713")} ClaudeMd appended \u2192 ${join2(targetDir, "CLAUDE.md")}`);
448
+ if (plan.statusline) console.log(` ${chalk.green("\u2713")} Statusline installed \u2192 ~/.claude/statusline.sh`);
449
+ console.log("");
450
+ if (skippedMcps.size > 0) {
451
+ console.log(chalk.yellow(" \u26A0 The following MCP servers have placeholder env vars:"));
452
+ for (const [serverName, keys] of skippedMcps) {
453
+ console.log(` ${chalk.bold("-")} ${serverName} ${chalk.gray(`(${keys.join(", ")})`)} `);
454
+ }
455
+ const configPath = scope === "global" ? "~/.claude.json" : "./.claude.json";
456
+ console.log(chalk.gray(` \u2192 Edit ${configPath} to set the actual values`));
457
+ console.log("");
458
+ }
459
+ } catch (err) {
460
+ spinner.fail(chalk.red("Installation failed"));
461
+ throw err;
462
+ }
463
+ return;
464
+ }
465
+ }
466
+ }
467
+ } catch (err) {
468
+ if (err instanceof ExitPromptError) {
469
+ console.log(chalk.yellow("\n Exiting."));
470
+ return;
471
+ }
472
+ throw err;
473
+ }
474
+ }
475
+ export {
476
+ runCli
477
+ };
package/dist/index.js CHANGED
@@ -134,7 +134,7 @@ async function main() {
134
134
  await installAll(scope);
135
135
  return;
136
136
  }
137
- const { runCli } = await import("./cli-KZYBSIXO.js");
137
+ const { runCli } = await import("./cli-DABSKXWN.js");
138
138
  await runCli();
139
139
  }
140
140
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juho0719/cckit",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
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": {
@@ -24,6 +24,7 @@
24
24
  "author": "",
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
+ "@inquirer/core": "^10.0.0",
27
28
  "@inquirer/prompts": "^7.0.0",
28
29
  "chalk": "^5.0.0",
29
30
  "ora": "^8.0.0"
@@ -1,314 +0,0 @@
1
- import {
2
- installMcps
3
- } from "./chunk-W63UKEIT.js";
4
- import {
5
- installClaudemd
6
- } from "./chunk-SW3OJLHC.js";
7
- import {
8
- getAgents,
9
- getClaudemdItems,
10
- getCommands,
11
- getHooks,
12
- getMcpServers,
13
- getRuleCategories,
14
- getSkills
15
- } from "./chunk-E3INXQNO.js";
16
- import {
17
- installAgents
18
- } from "./chunk-EYY2IZ7N.js";
19
- import {
20
- installSkills
21
- } from "./chunk-KEENFBLL.js";
22
- import {
23
- installCommands
24
- } from "./chunk-3Y26YU4R.js";
25
- import {
26
- installHooks
27
- } from "./chunk-W7RWPDBH.js";
28
- import {
29
- installRules
30
- } from "./chunk-ID643AV4.js";
31
- import {
32
- backupIfExists
33
- } from "./chunk-K25UZZVG.js";
34
- import {
35
- copyFileUtil
36
- } from "./chunk-3GUKEMND.js";
37
- import {
38
- getAssetsDir,
39
- getGlobalDir,
40
- getProjectDir
41
- } from "./chunk-5XOKKPAA.js";
42
-
43
- // src/cli.ts
44
- import { checkbox, select, input, confirm } from "@inquirer/prompts";
45
- import chalk from "chalk";
46
- import ora from "ora";
47
- import { join as join2 } from "path";
48
-
49
- // src/installers/statusline.ts
50
- import { join } from "path";
51
- import { chmod } from "fs/promises";
52
- async function installStatusline() {
53
- const src = join(getAssetsDir(), "statusline", "statusline.sh");
54
- const dest = join(getGlobalDir(), "statusline.sh");
55
- await backupIfExists(dest);
56
- await copyFileUtil(src, dest);
57
- await chmod(dest, 493);
58
- }
59
-
60
- // src/cli.ts
61
- function printBanner() {
62
- console.log(
63
- chalk.cyan.bold(`
64
- \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
65
- \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
66
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551
67
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551
68
- \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
69
- \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
70
- `)
71
- );
72
- console.log(chalk.gray(" Claude Code Harness Installer\n"));
73
- }
74
- async function runCli() {
75
- printBanner();
76
- const scopeAnswer = await select({
77
- message: "Install scope:",
78
- choices: [
79
- {
80
- name: `Global ${chalk.gray("~/.claude/")}`,
81
- value: "global"
82
- },
83
- {
84
- name: `Project ${chalk.gray("./.claude/")}`,
85
- value: "project"
86
- }
87
- ]
88
- });
89
- const scope = scopeAnswer;
90
- const targetDir = scope === "global" ? getGlobalDir() : getProjectDir();
91
- const selectedCategories = await checkbox({
92
- message: "Select categories to install:",
93
- choices: [
94
- { name: "Agents - AI sub-agents", value: "agents" },
95
- { name: "Skills - Reusable skill prompts", value: "skills" },
96
- { name: "Commands - Slash commands", value: "commands" },
97
- { name: "Hooks - Post-tool hooks", value: "hooks" },
98
- { name: "Rules - CLAUDE.md rules", value: "rules" },
99
- { name: "MCPs - MCP server configs", value: "mcps" },
100
- { name: "ClaudeMd - Behavioral guidelines", value: "claudemd" },
101
- { name: "Statusline - Terminal statusline script", value: "statusline" }
102
- ]
103
- });
104
- if (selectedCategories.length === 0) {
105
- console.log(chalk.yellow("\nNo categories selected. Exiting."));
106
- return;
107
- }
108
- const plan = {
109
- scope,
110
- agents: [],
111
- skills: [],
112
- commands: [],
113
- hooks: [],
114
- ruleCategories: [],
115
- mcps: [],
116
- mcpEnvValues: /* @__PURE__ */ new Map(),
117
- claudemds: [],
118
- statusline: false
119
- };
120
- const skippedMcps = /* @__PURE__ */ new Map();
121
- if (selectedCategories.includes("agents")) {
122
- const allAgents = await getAgents();
123
- plan.agents = await checkbox({
124
- message: "Select agents:",
125
- choices: allAgents.map((a) => ({
126
- name: `${chalk.bold(a.name.padEnd(30))} ${chalk.gray(a.description.slice(0, 60))}`,
127
- value: a,
128
- checked: false
129
- }))
130
- });
131
- }
132
- if (selectedCategories.includes("skills")) {
133
- const allSkills = await getSkills();
134
- plan.skills = await checkbox({
135
- message: "Select skills:",
136
- choices: allSkills.map((s) => ({
137
- name: `${chalk.bold(s.name.padEnd(35))} ${chalk.gray(s.description.slice(0, 55))}`,
138
- value: s,
139
- checked: false
140
- }))
141
- });
142
- }
143
- if (selectedCategories.includes("commands")) {
144
- const allCommands = await getCommands();
145
- plan.commands = await checkbox({
146
- message: "Select commands:",
147
- choices: allCommands.map((c) => ({
148
- name: `${chalk.bold(c.name.padEnd(25))} ${chalk.gray(c.description.slice(0, 65))}`,
149
- value: c,
150
- checked: false
151
- }))
152
- });
153
- }
154
- if (selectedCategories.includes("hooks")) {
155
- const allHooks = await getHooks();
156
- plan.hooks = await checkbox({
157
- message: "Select hooks:",
158
- choices: allHooks.map((h) => ({
159
- name: `${chalk.bold(h.name.padEnd(35))} ${chalk.gray(h.description.slice(0, 55))}`,
160
- value: h,
161
- checked: false
162
- }))
163
- });
164
- }
165
- if (selectedCategories.includes("rules")) {
166
- const allCategories = await getRuleCategories();
167
- plan.ruleCategories = await checkbox({
168
- message: "Select rule categories:",
169
- choices: allCategories.map((rc) => ({
170
- name: `${chalk.bold(rc.category.padEnd(20))} ${chalk.gray(`(${rc.files.length} files)`)}`,
171
- value: rc.category,
172
- checked: false
173
- }))
174
- });
175
- }
176
- if (selectedCategories.includes("claudemd")) {
177
- const allClaudemds = await getClaudemdItems();
178
- plan.claudemds = await checkbox({
179
- message: "Select claudemd items:",
180
- choices: allClaudemds.map((c) => ({
181
- name: `${chalk.bold(c.name.padEnd(35))} ${chalk.gray(c.description.slice(0, 55))}`,
182
- value: c,
183
- checked: false
184
- }))
185
- });
186
- }
187
- if (selectedCategories.includes("statusline")) {
188
- plan.statusline = true;
189
- }
190
- if (selectedCategories.includes("mcps")) {
191
- const allMcps = await getMcpServers();
192
- plan.mcps = await checkbox({
193
- message: "Select MCP servers:",
194
- choices: allMcps.map((m) => ({
195
- name: `${chalk.bold(m.name.padEnd(30))} ${chalk.gray(m.description.slice(0, 55))}`,
196
- value: m,
197
- checked: false
198
- }))
199
- });
200
- for (const server of plan.mcps) {
201
- if (server.type === "http" || server.envVars.length === 0) continue;
202
- console.log(chalk.yellow(`
203
- ${server.name} requires environment variables:`));
204
- const envMap = {};
205
- const skippedKeys = [];
206
- for (const envKey of server.envVars) {
207
- const action = await select({
208
- message: ` How do you want to set ${chalk.bold(envKey)}?`,
209
- choices: [
210
- { name: "Enter value now", value: "enter" },
211
- { name: `Skip ${chalk.gray(`(keep placeholder: YOUR_${envKey}_HERE)`)}`, value: "skip" }
212
- ]
213
- });
214
- if (action === "skip") {
215
- skippedKeys.push(envKey);
216
- } else {
217
- const value = await input({
218
- message: ` ${envKey}:`,
219
- default: ""
220
- });
221
- if (value) envMap[envKey] = value;
222
- }
223
- }
224
- plan.mcpEnvValues.set(server.name, envMap);
225
- if (skippedKeys.length > 0) {
226
- skippedMcps.set(server.name, skippedKeys);
227
- }
228
- }
229
- }
230
- console.log(chalk.bold("\n Installation Summary"));
231
- console.log(chalk.gray(" \u2500".repeat(40)));
232
- console.log(` Scope : ${chalk.cyan(scope)} (${targetDir})`);
233
- if (plan.agents.length) console.log(` Agents : ${plan.agents.map((a) => a.name).join(", ")}`);
234
- if (plan.skills.length) console.log(` Skills : ${plan.skills.map((s) => s.name).join(", ")}`);
235
- if (plan.commands.length) console.log(` Commands: ${plan.commands.map((c) => c.name).join(", ")}`);
236
- if (plan.hooks.length) console.log(` Hooks : ${plan.hooks.map((h) => h.name).join(", ")}`);
237
- if (plan.ruleCategories.length) console.log(` Rules : ${plan.ruleCategories.join(", ")}`);
238
- if (plan.mcps.length) console.log(` MCPs : ${plan.mcps.map((m) => m.name).join(", ")}`);
239
- if (plan.claudemds.length) console.log(` ClaudeMd : ${plan.claudemds.map((c) => c.name).join(", ")}`);
240
- if (plan.statusline) console.log(` Statusline: statusline.sh \u2192 ~/.claude/statusline.sh`);
241
- console.log("");
242
- const totalItems = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcps.length + plan.claudemds.length + (plan.statusline ? 1 : 0);
243
- if (totalItems === 0) {
244
- console.log(chalk.yellow(" Nothing selected. Exiting."));
245
- return;
246
- }
247
- const ok = await confirm({ message: "Proceed with installation?", default: true });
248
- if (!ok) {
249
- console.log(chalk.yellow("\n Aborted."));
250
- return;
251
- }
252
- console.log("");
253
- const spinner = ora("Installing...").start();
254
- try {
255
- if (plan.agents.length) {
256
- spinner.text = "Installing agents...";
257
- await installAgents(plan.agents, targetDir);
258
- }
259
- if (plan.skills.length) {
260
- spinner.text = "Installing skills...";
261
- await installSkills(plan.skills, targetDir);
262
- }
263
- if (plan.commands.length) {
264
- spinner.text = "Installing commands...";
265
- await installCommands(plan.commands, targetDir);
266
- }
267
- if (plan.hooks.length) {
268
- spinner.text = "Installing hooks...";
269
- await installHooks(plan.hooks, targetDir);
270
- }
271
- if (plan.ruleCategories.length) {
272
- spinner.text = "Installing rules...";
273
- await installRules(plan.ruleCategories, targetDir);
274
- }
275
- if (plan.mcps.length) {
276
- spinner.text = "Installing MCP servers...";
277
- await installMcps(plan.mcps, plan.scope, plan.mcpEnvValues);
278
- }
279
- if (plan.claudemds.length) {
280
- spinner.text = "Installing claudemd...";
281
- await installClaudemd(plan.claudemds, targetDir);
282
- }
283
- if (plan.statusline) {
284
- spinner.text = "Installing statusline...";
285
- await installStatusline();
286
- }
287
- spinner.succeed(chalk.green("Installation complete!"));
288
- console.log("");
289
- if (plan.agents.length) console.log(` ${chalk.green("\u2713")} ${plan.agents.length} agent(s) \u2192 ${join2(targetDir, "agents")}`);
290
- if (plan.skills.length) console.log(` ${chalk.green("\u2713")} ${plan.skills.length} skill(s) \u2192 ${join2(targetDir, "skills")}`);
291
- if (plan.commands.length) console.log(` ${chalk.green("\u2713")} ${plan.commands.length} command(s) \u2192 ${join2(targetDir, "commands")}`);
292
- if (plan.hooks.length) console.log(` ${chalk.green("\u2713")} ${plan.hooks.length} hook(s) \u2192 ${join2(targetDir, "hooks")}`);
293
- if (plan.ruleCategories.length) console.log(` ${chalk.green("\u2713")} Rules copied \u2192 ${join2(targetDir, "rules")}`);
294
- if (plan.mcps.length) console.log(` ${chalk.green("\u2713")} ${plan.mcps.length} MCP server(s) \u2192 ${scope === "global" ? "~/.claude.json" : "./.claude.json"}`);
295
- if (plan.claudemds.length) console.log(` ${chalk.green("\u2713")} ClaudeMd appended \u2192 ${join2(targetDir, "CLAUDE.md")}`);
296
- if (plan.statusline) console.log(` ${chalk.green("\u2713")} Statusline installed \u2192 ~/.claude/statusline.sh`);
297
- console.log("");
298
- if (skippedMcps.size > 0) {
299
- console.log(chalk.yellow(" \u26A0 The following MCP servers have placeholder env vars:"));
300
- for (const [serverName, keys] of skippedMcps) {
301
- console.log(` ${chalk.bold("-")} ${serverName} ${chalk.gray(`(${keys.join(", ")})`)} `);
302
- }
303
- const configPath = scope === "global" ? "~/.claude.json" : "./.claude.json";
304
- console.log(chalk.gray(` \u2192 Edit ${configPath} to set the actual values`));
305
- console.log("");
306
- }
307
- } catch (err) {
308
- spinner.fail(chalk.red("Installation failed"));
309
- throw err;
310
- }
311
- }
312
- export {
313
- runCli
314
- };