@arkhera30/cli 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compose/docker-compose.yml +3 -2
- package/dist/index.js +144 -21
- package/package.json +1 -1
|
@@ -198,8 +198,9 @@ services:
|
|
|
198
198
|
- FORGE_HOST_ANVIL_URL=http://localhost:${ANVIL_PORT:-8100}
|
|
199
199
|
- FORGE_HOST_VAULT_URL=http://localhost:${VAULT_MCP_PORT:-8300}
|
|
200
200
|
- FORGE_HOST_FORGE_URL=http://localhost:${FORGE_PORT:-8200}
|
|
201
|
-
# Colon-separated list of in-container paths to scan for git repos
|
|
202
|
-
|
|
201
|
+
# Colon-separated list of in-container paths to scan for git repos.
|
|
202
|
+
# Generated by the CLI from host_repos_extra_scan_dirs config — do not edit manually.
|
|
203
|
+
- FORGE_SCAN_PATHS=${FORGE_SCAN_PATHS:-/data/repos}
|
|
203
204
|
- GITHUB_TOKEN=${GITHUB_TOKEN:-}
|
|
204
205
|
depends_on:
|
|
205
206
|
anvil:
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import ora from "ora";
|
|
|
12
12
|
import { execSync } from "child_process";
|
|
13
13
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
14
14
|
import { join as join3 } from "path";
|
|
15
|
-
import { input, confirm, number, select } from "@inquirer/prompts";
|
|
15
|
+
import { input, confirm, number, select, password } from "@inquirer/prompts";
|
|
16
16
|
|
|
17
17
|
// src/lib/config.ts
|
|
18
18
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
@@ -58,6 +58,7 @@ function defaultConfig() {
|
|
|
58
58
|
git_host: "github.com",
|
|
59
59
|
repos: { ...DEFAULT_REPOS },
|
|
60
60
|
host_repos_path: "",
|
|
61
|
+
host_repos_extra_scan_dirs: [],
|
|
61
62
|
github_token: ""
|
|
62
63
|
};
|
|
63
64
|
}
|
|
@@ -91,6 +92,7 @@ function loadConfig() {
|
|
|
91
92
|
forge_registry: parsed.repos?.forge_registry ?? defaults.repos.forge_registry
|
|
92
93
|
},
|
|
93
94
|
host_repos_path: parsed.host_repos_path ?? defaults.host_repos_path,
|
|
95
|
+
host_repos_extra_scan_dirs: parsed.host_repos_extra_scan_dirs ?? defaults.host_repos_extra_scan_dirs,
|
|
94
96
|
github_token: parsed.github_token ?? defaults.github_token
|
|
95
97
|
};
|
|
96
98
|
}
|
|
@@ -108,6 +110,9 @@ function resolvePath(p) {
|
|
|
108
110
|
function generateEnv(config) {
|
|
109
111
|
const dataDir = resolvePath(config.data_dir);
|
|
110
112
|
const hostReposPath = config.host_repos_path ? resolvePath(config.host_repos_path) : "";
|
|
113
|
+
const baseScanPath = "/data/repos";
|
|
114
|
+
const extraScanPaths = (config.host_repos_extra_scan_dirs ?? []).map((d) => d.trim()).filter(Boolean).map((d) => `${baseScanPath}/${d}`);
|
|
115
|
+
const forgeScanPaths = [baseScanPath, ...extraScanPaths].join(":");
|
|
111
116
|
const lines = [
|
|
112
117
|
"# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
113
118
|
"# Horus \u2014 Generated .env file",
|
|
@@ -116,6 +121,7 @@ function generateEnv(config) {
|
|
|
116
121
|
"",
|
|
117
122
|
`HORUS_DATA_PATH=${dataDir}`,
|
|
118
123
|
`HOST_REPOS_PATH=${hostReposPath}`,
|
|
124
|
+
`FORGE_SCAN_PATHS=${forgeScanPaths}`,
|
|
119
125
|
"",
|
|
120
126
|
"# Ports",
|
|
121
127
|
`ANVIL_PORT=${config.ports.anvil}`,
|
|
@@ -127,6 +133,9 @@ function generateEnv(config) {
|
|
|
127
133
|
`ANVIL_REPO_URL=${config.repos.anvil_notes}`,
|
|
128
134
|
`VAULT_KNOWLEDGE_REPO_URL=${config.repos.vault_knowledge}`,
|
|
129
135
|
`FORGE_REGISTRY_REPO_URL=${config.repos.forge_registry}`,
|
|
136
|
+
"",
|
|
137
|
+
"# Authentication",
|
|
138
|
+
`GITHUB_TOKEN=${config.github_token}`,
|
|
130
139
|
""
|
|
131
140
|
];
|
|
132
141
|
return lines.join("\n");
|
|
@@ -139,6 +148,7 @@ function writeEnvFile(config) {
|
|
|
139
148
|
var CONFIG_KEYS = [
|
|
140
149
|
"data-dir",
|
|
141
150
|
"host-repos-path",
|
|
151
|
+
"host-repos-extra-scan-dirs",
|
|
142
152
|
"runtime",
|
|
143
153
|
"port.anvil",
|
|
144
154
|
"port.vault-rest",
|
|
@@ -156,6 +166,8 @@ function getConfigValue(config, key) {
|
|
|
156
166
|
return config.data_dir;
|
|
157
167
|
case "host-repos-path":
|
|
158
168
|
return config.host_repos_path;
|
|
169
|
+
case "host-repos-extra-scan-dirs":
|
|
170
|
+
return (config.host_repos_extra_scan_dirs ?? []).join(", ");
|
|
159
171
|
case "runtime":
|
|
160
172
|
return config.runtime;
|
|
161
173
|
case "port.anvil":
|
|
@@ -187,6 +199,9 @@ function setConfigValue(config, key, value) {
|
|
|
187
199
|
case "host-repos-path":
|
|
188
200
|
updated.host_repos_path = value;
|
|
189
201
|
break;
|
|
202
|
+
case "host-repos-extra-scan-dirs":
|
|
203
|
+
updated.host_repos_extra_scan_dirs = value.split(",").map((d) => d.trim()).filter(Boolean);
|
|
204
|
+
break;
|
|
190
205
|
case "runtime":
|
|
191
206
|
if (value !== "docker" && value !== "podman") {
|
|
192
207
|
throw new Error(`Invalid runtime: ${value}. Must be "docker" or "podman".`);
|
|
@@ -255,11 +270,13 @@ async function commandExists(command) {
|
|
|
255
270
|
}
|
|
256
271
|
function createRuntime(name) {
|
|
257
272
|
const bin = name;
|
|
273
|
+
const composeEnv = { ...process.env, HORUS_RUNTIME: name };
|
|
258
274
|
return {
|
|
259
275
|
name,
|
|
260
276
|
async compose(...args) {
|
|
261
277
|
const result = await execa(bin, ["compose", ...args], {
|
|
262
278
|
cwd: HORUS_DIR,
|
|
279
|
+
env: composeEnv,
|
|
263
280
|
reject: false
|
|
264
281
|
});
|
|
265
282
|
if (result.exitCode !== 0) {
|
|
@@ -287,6 +304,7 @@ function createRuntime(name) {
|
|
|
287
304
|
try {
|
|
288
305
|
const result = await execa(bin, ["compose", "ps", "--format", "json"], {
|
|
289
306
|
cwd: HORUS_DIR,
|
|
307
|
+
env: composeEnv,
|
|
290
308
|
reject: false
|
|
291
309
|
});
|
|
292
310
|
return result.exitCode === 0 && result.stdout.toString().trim().length > 0;
|
|
@@ -328,6 +346,7 @@ async function composeStreaming(runtime, args) {
|
|
|
328
346
|
const bin = runtime.name;
|
|
329
347
|
const result = await execa(bin, ["compose", ...args], {
|
|
330
348
|
cwd: HORUS_DIR,
|
|
349
|
+
env: { ...process.env, HORUS_RUNTIME: runtime.name },
|
|
331
350
|
stdio: "inherit",
|
|
332
351
|
reject: false
|
|
333
352
|
});
|
|
@@ -417,18 +436,38 @@ function getBundledComposePath() {
|
|
|
417
436
|
Searched: ${candidates.join(", ")}`
|
|
418
437
|
);
|
|
419
438
|
}
|
|
439
|
+
function applyPodmanUserOverride(compose) {
|
|
440
|
+
return compose.replace(
|
|
441
|
+
/^( image: .+)$/gm,
|
|
442
|
+
'$1\n user: "0:0"'
|
|
443
|
+
);
|
|
444
|
+
}
|
|
420
445
|
function composeFileExists() {
|
|
421
446
|
return existsSync2(COMPOSE_PATH);
|
|
422
447
|
}
|
|
423
|
-
function installComposeFile() {
|
|
448
|
+
function installComposeFile(runtime) {
|
|
424
449
|
ensureHorusDir();
|
|
425
450
|
const bundledPath = getBundledComposePath();
|
|
426
|
-
|
|
451
|
+
let content = readFileSync2(bundledPath, "utf-8");
|
|
452
|
+
if (runtime === "podman") {
|
|
453
|
+
content = applyPodmanUserOverride(content);
|
|
454
|
+
}
|
|
427
455
|
writeFileSync2(COMPOSE_PATH, content, "utf-8");
|
|
428
456
|
}
|
|
429
457
|
|
|
430
458
|
// src/commands/setup.ts
|
|
431
|
-
|
|
459
|
+
function injectToken(url, token) {
|
|
460
|
+
if (!token) return url;
|
|
461
|
+
try {
|
|
462
|
+
const parsed = new URL(url);
|
|
463
|
+
parsed.username = "oauth2";
|
|
464
|
+
parsed.password = token;
|
|
465
|
+
return parsed.toString();
|
|
466
|
+
} catch {
|
|
467
|
+
return url;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
var setupCommand = new Command("setup").description("Interactive first-run setup for Horus").option("-y, --yes", "Non-interactive mode (use defaults + env vars)").option("--runtime <runtime>", "Container runtime to use: docker or podman (non-interactive only)").option("--data-dir <path>", "Data directory path").option("--repos-path <path>", "Host repos path for Forge scanning").option("--git-host <host>", "Git server hostname (e.g., github.com, gitlab.corp.com)").option("--anvil-repo <url>", "Anvil notes repository URL").option("--vault-repo <url>", "Vault knowledge-base repository URL").option("--forge-repo <url>", "Forge registry repository URL").option("--github-token <token>", "GitHub personal access token for private repos").action(async (opts) => {
|
|
432
471
|
console.log("");
|
|
433
472
|
console.log(chalk.bold("Horus Setup"));
|
|
434
473
|
console.log(chalk.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
@@ -500,7 +539,8 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
|
|
|
500
539
|
anvil_notes: opts.anvilRepo || process.env.ANVIL_REPO_URL || defaults.repos.anvil_notes,
|
|
501
540
|
vault_knowledge: opts.vaultRepo || process.env.VAULT_KNOWLEDGE_REPO_URL || defaults.repos.vault_knowledge,
|
|
502
541
|
forge_registry: opts.forgeRepo || process.env.FORGE_REGISTRY_REPO_URL || defaults.repos.forge_registry
|
|
503
|
-
}
|
|
542
|
+
},
|
|
543
|
+
github_token: opts.githubToken || process.env.GITHUB_TOKEN || ""
|
|
504
544
|
};
|
|
505
545
|
} else {
|
|
506
546
|
const data_dir = await input({
|
|
@@ -511,6 +551,11 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
|
|
|
511
551
|
message: "Host repos path (for Forge repo scanning, leave empty to skip):",
|
|
512
552
|
default: ""
|
|
513
553
|
});
|
|
554
|
+
const extra_scan_dirs_raw = await input({
|
|
555
|
+
message: "Extra subdirectories to scan within repos path (comma-separated, e.g. ArjunKhera \u2014 leave empty to skip):",
|
|
556
|
+
default: ""
|
|
557
|
+
});
|
|
558
|
+
const host_repos_extra_scan_dirs = extra_scan_dirs_raw.split(",").map((d) => d.trim()).filter(Boolean);
|
|
514
559
|
const customize_ports = await confirm({
|
|
515
560
|
message: "Customize port assignments?",
|
|
516
561
|
default: false
|
|
@@ -547,7 +592,6 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
|
|
|
547
592
|
console.log("");
|
|
548
593
|
console.log(chalk.yellow(" Use HTTPS URLs \u2014 container services do not have SSH keys."));
|
|
549
594
|
console.log(chalk.dim(" SSH URLs (git@github.com:...) will fail at runtime inside Docker/Podman."));
|
|
550
|
-
console.log(chalk.dim(" Set GITHUB_TOKEN for private repos."));
|
|
551
595
|
console.log("");
|
|
552
596
|
const git_host = await input({
|
|
553
597
|
message: "Git server hostname:",
|
|
@@ -574,10 +618,19 @@ ${example("forge-registry")}
|
|
|
574
618
|
`,
|
|
575
619
|
validate: (v) => v.trim().length > 0 || "Forge needs a registry repo."
|
|
576
620
|
});
|
|
621
|
+
console.log("");
|
|
622
|
+
console.log(chalk.bold("Authentication"));
|
|
623
|
+
console.log(chalk.dim("A personal access token is required for private repositories."));
|
|
624
|
+
console.log("");
|
|
625
|
+
const github_token = await password({
|
|
626
|
+
message: "GitHub personal access token (leave empty to skip):",
|
|
627
|
+
mask: "*"
|
|
628
|
+
});
|
|
577
629
|
config = {
|
|
578
630
|
...defaultConfig(),
|
|
579
631
|
data_dir,
|
|
580
632
|
host_repos_path,
|
|
633
|
+
host_repos_extra_scan_dirs,
|
|
581
634
|
runtime: runtime.name,
|
|
582
635
|
ports,
|
|
583
636
|
git_host: git_host.trim(),
|
|
@@ -585,7 +638,8 @@ ${example("forge-registry")}
|
|
|
585
638
|
anvil_notes: anvil_notes.trim(),
|
|
586
639
|
vault_knowledge: vault_knowledge.trim(),
|
|
587
640
|
forge_registry: forge_registry.trim()
|
|
588
|
-
}
|
|
641
|
+
},
|
|
642
|
+
github_token: github_token.trim()
|
|
589
643
|
};
|
|
590
644
|
}
|
|
591
645
|
const configSpinner = ora("Saving configuration...").start();
|
|
@@ -608,7 +662,7 @@ ${example("forge-registry")}
|
|
|
608
662
|
}
|
|
609
663
|
const composeSpinner = ora("Installing docker-compose.yml...").start();
|
|
610
664
|
try {
|
|
611
|
-
installComposeFile();
|
|
665
|
+
installComposeFile(runtime.name);
|
|
612
666
|
composeSpinner.succeed("Compose file installed to ~/.horus/docker-compose.yml");
|
|
613
667
|
} catch (error) {
|
|
614
668
|
composeSpinner.fail("Failed to install compose file");
|
|
@@ -633,7 +687,8 @@ ${example("forge-registry")}
|
|
|
633
687
|
}
|
|
634
688
|
try {
|
|
635
689
|
mkdirSync2(repo.dest, { recursive: true });
|
|
636
|
-
|
|
690
|
+
const cloneUrl = injectToken(repo.url, config.github_token);
|
|
691
|
+
execSync(`git clone "${cloneUrl}" "${repo.dest}"`, {
|
|
637
692
|
stdio: "pipe",
|
|
638
693
|
timeout: 6e4
|
|
639
694
|
});
|
|
@@ -647,7 +702,9 @@ ${example("forge-registry")}
|
|
|
647
702
|
console.log(chalk.dim(` ${msg.split("\n")[0]}`));
|
|
648
703
|
}
|
|
649
704
|
console.log(chalk.dim(` URL: ${repo.url}`));
|
|
650
|
-
|
|
705
|
+
if (!config.github_token) {
|
|
706
|
+
console.log(chalk.dim(" Tip: Re-run setup and provide a GitHub token if the repo is private."));
|
|
707
|
+
}
|
|
651
708
|
process.exit(1);
|
|
652
709
|
}
|
|
653
710
|
}
|
|
@@ -715,7 +772,7 @@ ${example("forge-registry")}
|
|
|
715
772
|
import { Command as Command2 } from "commander";
|
|
716
773
|
import chalk2 from "chalk";
|
|
717
774
|
import ora2 from "ora";
|
|
718
|
-
var upCommand = new Command2("up").description("Start the Horus stack").action(async () => {
|
|
775
|
+
var upCommand = new Command2("up").description("Start the Horus stack").option("--no-pull", "Skip pulling latest images before starting").action(async (opts) => {
|
|
719
776
|
if (!configExists() || !composeFileExists()) {
|
|
720
777
|
console.log(chalk2.red("Horus is not set up yet."));
|
|
721
778
|
console.log(chalk2.dim("Run `horus setup` first."));
|
|
@@ -732,6 +789,15 @@ var upCommand = new Command2("up").description("Start the Horus stack").action(a
|
|
|
732
789
|
console.log(error.message);
|
|
733
790
|
process.exit(1);
|
|
734
791
|
}
|
|
792
|
+
if (opts.pull) {
|
|
793
|
+
const pullSpinner = ora2("Pulling latest images...").start();
|
|
794
|
+
try {
|
|
795
|
+
await composeStreaming(runtime, ["pull", "--ignore-pull-failures"]);
|
|
796
|
+
pullSpinner.succeed("Images up to date");
|
|
797
|
+
} catch {
|
|
798
|
+
pullSpinner.warn("Could not pull images, using cached");
|
|
799
|
+
}
|
|
800
|
+
}
|
|
735
801
|
console.log("");
|
|
736
802
|
console.log(chalk2.bold("Starting Horus services..."));
|
|
737
803
|
try {
|
|
@@ -904,8 +970,10 @@ var configCommand = new Command5("config").description("View or modify Horus con
|
|
|
904
970
|
console.log(` ${chalk5.bold("version:")} ${config.version}`);
|
|
905
971
|
console.log(` ${chalk5.bold("data-dir:")} ${config.data_dir}`);
|
|
906
972
|
console.log(` ${chalk5.bold("runtime:")} ${config.runtime}`);
|
|
907
|
-
console.log(` ${chalk5.bold("host-repos-path:")}
|
|
908
|
-
|
|
973
|
+
console.log(` ${chalk5.bold("host-repos-path:")} ${config.host_repos_path || chalk5.dim("(not set)")}`);
|
|
974
|
+
const extraDirs = (config.host_repos_extra_scan_dirs ?? []).join(", ");
|
|
975
|
+
console.log(` ${chalk5.bold("host-repos-extra-scan-dirs:")} ${extraDirs || chalk5.dim("(not set)")}`);
|
|
976
|
+
console.log(` ${chalk5.bold("git-host:")} ${config.git_host || chalk5.dim("(not set)")}`);
|
|
909
977
|
console.log(` ${chalk5.bold("github-token:")} ${config.github_token ? maskApiKey(config.github_token) : chalk5.dim("(not set)")}`);
|
|
910
978
|
console.log("");
|
|
911
979
|
console.log(chalk5.bold(" Ports:"));
|
|
@@ -966,6 +1034,7 @@ configCommand.command("set <key> <value>").description("Set a configuration valu
|
|
|
966
1034
|
const needsRestart = [
|
|
967
1035
|
"data-dir",
|
|
968
1036
|
"host-repos-path",
|
|
1037
|
+
"host-repos-extra-scan-dirs",
|
|
969
1038
|
"runtime",
|
|
970
1039
|
"port.anvil",
|
|
971
1040
|
"port.vault-rest",
|
|
@@ -999,6 +1068,7 @@ import { checkbox } from "@inquirer/prompts";
|
|
|
999
1068
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
1000
1069
|
import { join as join4 } from "path";
|
|
1001
1070
|
import { homedir as homedir3 } from "os";
|
|
1071
|
+
import { execa as execa2 } from "execa";
|
|
1002
1072
|
function detectInstalledClients() {
|
|
1003
1073
|
const detected = [];
|
|
1004
1074
|
const home = homedir3();
|
|
@@ -1044,6 +1114,32 @@ function mergeAndWriteConfig(configPath, mcpServers) {
|
|
|
1044
1114
|
mkdirSync3(dir, { recursive: true });
|
|
1045
1115
|
writeFileSync3(configPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
1046
1116
|
}
|
|
1117
|
+
async function isClaudeCliAvailable() {
|
|
1118
|
+
try {
|
|
1119
|
+
const result = await execa2("claude", ["--version"], { reject: false });
|
|
1120
|
+
return result.exitCode === 0;
|
|
1121
|
+
} catch {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
async function registerWithClaudeCode(mcpServers) {
|
|
1126
|
+
const registered = [];
|
|
1127
|
+
const failed = [];
|
|
1128
|
+
for (const [name, entry] of Object.entries(mcpServers)) {
|
|
1129
|
+
const baseUrl = entry.url.replace(/\/sse$/, "");
|
|
1130
|
+
const result = await execa2(
|
|
1131
|
+
"claude",
|
|
1132
|
+
["mcp", "add", "--transport", "http", "--scope", "user", name, baseUrl],
|
|
1133
|
+
{ reject: false }
|
|
1134
|
+
);
|
|
1135
|
+
if (result.exitCode === 0) {
|
|
1136
|
+
registered.push(name);
|
|
1137
|
+
} else {
|
|
1138
|
+
failed.push(name);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return { registered, failed };
|
|
1142
|
+
}
|
|
1047
1143
|
async function syncSkills(runtime) {
|
|
1048
1144
|
const home = homedir3();
|
|
1049
1145
|
const skillsBase = join4(home, ".claude", "skills");
|
|
@@ -1164,14 +1260,41 @@ var connectCommand = new Command6("connect").description("Configure Claude/Curso
|
|
|
1164
1260
|
forge: { url: `http://${host}:${config.ports.forge}/sse` }
|
|
1165
1261
|
};
|
|
1166
1262
|
for (const target of targets) {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1263
|
+
if (target === "claude-code") {
|
|
1264
|
+
const cliSpinner = ora5("Registering MCP servers with Claude Code CLI...").start();
|
|
1265
|
+
const cliAvailable = await isClaudeCliAvailable();
|
|
1266
|
+
if (cliAvailable) {
|
|
1267
|
+
const { registered, failed } = await registerWithClaudeCode(mcpServers);
|
|
1268
|
+
if (failed.length === 0) {
|
|
1269
|
+
cliSpinner.succeed(
|
|
1270
|
+
`Registered with Claude Code: ${registered.map((n) => chalk6.cyan(n)).join(", ")}`
|
|
1271
|
+
);
|
|
1272
|
+
} else if (registered.length > 0) {
|
|
1273
|
+
cliSpinner.warn(
|
|
1274
|
+
`Partially registered \u2014 ok: ${registered.join(", ")}, failed: ${failed.join(", ")}`
|
|
1275
|
+
);
|
|
1276
|
+
} else {
|
|
1277
|
+
cliSpinner.fail("Failed to register MCP servers with Claude Code CLI");
|
|
1278
|
+
}
|
|
1279
|
+
} else {
|
|
1280
|
+
cliSpinner.warn("claude CLI not found on PATH \u2014 register manually:");
|
|
1281
|
+
for (const [name, entry] of Object.entries(mcpServers)) {
|
|
1282
|
+
const baseUrl = entry.url.replace(/\/sse$/, "");
|
|
1283
|
+
console.log(
|
|
1284
|
+
chalk6.dim(` claude mcp add --transport http --scope user ${name} ${baseUrl}`)
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
} else {
|
|
1289
|
+
const configPath = getConfigPath(target);
|
|
1290
|
+
const writeSpinner = ora5(`Configuring ${chalk6.cyan(target)}...`).start();
|
|
1291
|
+
try {
|
|
1292
|
+
mergeAndWriteConfig(configPath, mcpServers);
|
|
1293
|
+
writeSpinner.succeed(`Configured ${chalk6.cyan(target)} \u2014 ${chalk6.dim(configPath)}`);
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
writeSpinner.fail(`Failed to configure ${target}`);
|
|
1296
|
+
console.log(chalk6.dim(error.message));
|
|
1297
|
+
}
|
|
1175
1298
|
}
|
|
1176
1299
|
}
|
|
1177
1300
|
if (targets.includes("claude-code")) {
|