@arkhera30/cli 0.1.15 → 0.1.17
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/dist/index.js +91 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -38,7 +38,8 @@ function findPackageJson() {
|
|
|
38
38
|
}
|
|
39
39
|
var pkg = JSON.parse(readFileSync(findPackageJson(), "utf-8"));
|
|
40
40
|
var CLI_VERSION = pkg.version;
|
|
41
|
-
var HORUS_DIR = join(homedir(), "
|
|
41
|
+
var HORUS_DIR = join(homedir(), "Horus");
|
|
42
|
+
var LEGACY_HORUS_DIR = join(homedir(), ".horus");
|
|
42
43
|
var CONFIG_PATH = join(HORUS_DIR, "config.yaml");
|
|
43
44
|
var ENV_PATH = join(HORUS_DIR, ".env");
|
|
44
45
|
var COMPOSE_PATH = join(HORUS_DIR, "docker-compose.yml");
|
|
@@ -53,7 +54,7 @@ var DEFAULT_REPOS = {
|
|
|
53
54
|
vault_knowledge: "",
|
|
54
55
|
forge_registry: ""
|
|
55
56
|
};
|
|
56
|
-
var DEFAULT_DATA_DIR = join(homedir(), "
|
|
57
|
+
var DEFAULT_DATA_DIR = join(homedir(), "Horus", "data");
|
|
57
58
|
var SERVICES = [
|
|
58
59
|
"qmd-daemon",
|
|
59
60
|
"anvil",
|
|
@@ -85,6 +86,38 @@ function configExists() {
|
|
|
85
86
|
}
|
|
86
87
|
function loadConfig() {
|
|
87
88
|
if (!existsSync2(CONFIG_PATH)) {
|
|
89
|
+
const legacyConfigPath = pathJoin(LEGACY_HORUS_DIR, "config.yaml");
|
|
90
|
+
if (existsSync2(legacyConfigPath)) {
|
|
91
|
+
console.warn(
|
|
92
|
+
`
|
|
93
|
+
Warning: Horus config found at ~/.horus/config.yaml (legacy location).
|
|
94
|
+
The new default is ~/Horus. Run \`horus setup\` to migrate.
|
|
95
|
+
`
|
|
96
|
+
);
|
|
97
|
+
const raw2 = readFileSync2(legacyConfigPath, "utf-8");
|
|
98
|
+
const parsed2 = parseYaml(raw2);
|
|
99
|
+
const defaults2 = defaultConfig();
|
|
100
|
+
return {
|
|
101
|
+
version: parsed2.version ?? defaults2.version,
|
|
102
|
+
data_dir: parsed2.data_dir ?? defaults2.data_dir,
|
|
103
|
+
runtime: parsed2.runtime ?? defaults2.runtime,
|
|
104
|
+
ports: {
|
|
105
|
+
anvil: parsed2.ports?.anvil ?? defaults2.ports.anvil,
|
|
106
|
+
vault_rest: parsed2.ports?.vault_rest ?? defaults2.ports.vault_rest,
|
|
107
|
+
vault_mcp: parsed2.ports?.vault_mcp ?? defaults2.ports.vault_mcp,
|
|
108
|
+
forge: parsed2.ports?.forge ?? defaults2.ports.forge
|
|
109
|
+
},
|
|
110
|
+
git_host: parsed2.git_host ?? defaults2.git_host,
|
|
111
|
+
repos: {
|
|
112
|
+
anvil_notes: parsed2.repos?.anvil_notes ?? defaults2.repos.anvil_notes,
|
|
113
|
+
vault_knowledge: parsed2.repos?.vault_knowledge ?? defaults2.repos.vault_knowledge,
|
|
114
|
+
forge_registry: parsed2.repos?.forge_registry ?? defaults2.repos.forge_registry
|
|
115
|
+
},
|
|
116
|
+
host_repos_path: parsed2.host_repos_path ?? defaults2.host_repos_path,
|
|
117
|
+
host_repos_extra_scan_dirs: parsed2.host_repos_extra_scan_dirs ?? defaults2.host_repos_extra_scan_dirs,
|
|
118
|
+
github_token: parsed2.github_token ?? defaults2.github_token
|
|
119
|
+
};
|
|
120
|
+
}
|
|
88
121
|
return defaultConfig();
|
|
89
122
|
}
|
|
90
123
|
const raw = readFileSync2(CONFIG_PATH, "utf-8");
|
|
@@ -502,7 +535,7 @@ async function pollUntilHealthy(runtime, onUpdate, timeoutMs = 3e5, intervalMs =
|
|
|
502
535
|
const unhealthyServices = states.filter((s) => s.status === "unhealthy").map((s) => s.name).join(", ");
|
|
503
536
|
throw new Error(
|
|
504
537
|
`Services failed health check: ${unhealthyServices}
|
|
505
|
-
Run '${runtime.name} compose logs <service>' from
|
|
538
|
+
Run '${runtime.name} compose logs <service>' from ~/Horus/ to investigate.`
|
|
506
539
|
);
|
|
507
540
|
}
|
|
508
541
|
const elapsed = Date.now() - startTime;
|
|
@@ -510,7 +543,7 @@ Run '${runtime.name} compose logs <service>' from ~/.horus/ to investigate.`
|
|
|
510
543
|
const notReady = states.filter((s) => s.status !== "healthy").map((s) => `${s.name} (${s.status})`).join(", ");
|
|
511
544
|
throw new Error(
|
|
512
545
|
`Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for services: ${notReady}
|
|
513
|
-
Run '${runtime.name} compose logs' from
|
|
546
|
+
Run '${runtime.name} compose logs' from ~/Horus/ to investigate.`
|
|
514
547
|
);
|
|
515
548
|
}
|
|
516
549
|
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
@@ -611,6 +644,16 @@ function mergeAndWriteConfig(configPath, mcpServers) {
|
|
|
611
644
|
mkdirSync2(dir, { recursive: true });
|
|
612
645
|
writeFileSync3(configPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
613
646
|
}
|
|
647
|
+
function getMcpRemoteWrapperPath() {
|
|
648
|
+
return join3(homedir3(), ".forge", "bin", "mcp-remote-wrapper");
|
|
649
|
+
}
|
|
650
|
+
function buildStdioServers(config, wrapperPath, host) {
|
|
651
|
+
return {
|
|
652
|
+
anvil: { command: wrapperPath, args: [`http://${host}:${config.ports.anvil}/mcp`, "--transport", "http-only"] },
|
|
653
|
+
vault: { command: wrapperPath, args: [`http://${host}:${config.ports.vault_mcp}/mcp`, "--transport", "http-only"] },
|
|
654
|
+
forge: { command: wrapperPath, args: [`http://${host}:${config.ports.forge}/mcp`, "--transport", "http-only"] }
|
|
655
|
+
};
|
|
656
|
+
}
|
|
614
657
|
async function isClaudeCliAvailable() {
|
|
615
658
|
try {
|
|
616
659
|
const result = await execa2("claude", ["--version"], { reject: false });
|
|
@@ -657,21 +700,25 @@ async function syncSkills(runtime) {
|
|
|
657
700
|
async function syncSkillsForCursor(runtime) {
|
|
658
701
|
const home = homedir3();
|
|
659
702
|
const rulesDir = join3(home, ".cursor", "rules");
|
|
703
|
+
const skillsBase = join3(home, ".cursor", "skills-cursor");
|
|
660
704
|
const skills = ["horus-anvil", "horus-vault", "horus-forge"];
|
|
661
705
|
const forgeContainer = "horus-forge-1";
|
|
662
706
|
mkdirSync2(rulesDir, { recursive: true });
|
|
663
707
|
for (const skill of skills) {
|
|
664
708
|
const src = `/home/forge/.claude/skills/${skill}/SKILL.md`;
|
|
665
|
-
const dest = join3(rulesDir, `${skill}.mdc`);
|
|
666
709
|
const result = await runtime.exec(forgeContainer, "cat", src);
|
|
667
710
|
if (result.exitCode === 0 && result.stdout.trim()) {
|
|
711
|
+
const ruleDest = join3(rulesDir, `${skill}.mdc`);
|
|
668
712
|
const frontmatter = `---
|
|
669
713
|
description: Horus ${skill} reference
|
|
670
714
|
alwaysApply: true
|
|
671
715
|
---
|
|
672
716
|
|
|
673
717
|
`;
|
|
674
|
-
writeFileSync3(
|
|
718
|
+
writeFileSync3(ruleDest, frontmatter + result.stdout, "utf-8");
|
|
719
|
+
const skillDir = join3(skillsBase, skill);
|
|
720
|
+
mkdirSync2(skillDir, { recursive: true });
|
|
721
|
+
writeFileSync3(join3(skillDir, "SKILL.md"), result.stdout, "utf-8");
|
|
675
722
|
}
|
|
676
723
|
}
|
|
677
724
|
}
|
|
@@ -694,18 +741,38 @@ function printNextSteps(targets) {
|
|
|
694
741
|
console.log("");
|
|
695
742
|
}
|
|
696
743
|
async function runConnect(config, runtime, targets, host = "localhost") {
|
|
697
|
-
const
|
|
744
|
+
const httpServers = {
|
|
698
745
|
anvil: { url: `http://${host}:${config.ports.anvil}/sse` },
|
|
699
746
|
vault: { url: `http://${host}:${config.ports.vault_mcp}/sse` },
|
|
700
747
|
forge: { url: `http://${host}:${config.ports.forge}/sse` }
|
|
701
748
|
};
|
|
702
749
|
const configured = [];
|
|
703
750
|
for (const target of targets) {
|
|
704
|
-
if (target === "claude-
|
|
751
|
+
if (target === "claude-desktop") {
|
|
752
|
+
const desktopSpinner = ora(`Configuring ${chalk.cyan("claude-desktop")}...`).start();
|
|
753
|
+
const wrapperPath = getMcpRemoteWrapperPath();
|
|
754
|
+
if (!existsSync4(wrapperPath)) {
|
|
755
|
+
desktopSpinner.fail("mcp-remote-wrapper not found");
|
|
756
|
+
console.log(chalk.dim(`Expected at: ${wrapperPath}`));
|
|
757
|
+
console.log(chalk.dim("Install it with: npx --yes mcp-remote --help"));
|
|
758
|
+
console.log(chalk.dim("Then place the wrapper script at the path above."));
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
const stdioServers = buildStdioServers(config, wrapperPath, host);
|
|
763
|
+
const configPath = getConfigPath(target);
|
|
764
|
+
mergeAndWriteConfig(configPath, stdioServers);
|
|
765
|
+
desktopSpinner.succeed(`Configured ${chalk.cyan("claude-desktop")} \u2014 ${chalk.dim(configPath)}`);
|
|
766
|
+
configured.push(target);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
desktopSpinner.fail("Failed to configure claude-desktop");
|
|
769
|
+
console.log(chalk.dim(error.message));
|
|
770
|
+
}
|
|
771
|
+
} else if (target === "claude-code") {
|
|
705
772
|
const cliSpinner = ora("Registering MCP servers with Claude Code CLI...").start();
|
|
706
773
|
const cliAvailable = await isClaudeCliAvailable();
|
|
707
774
|
if (cliAvailable) {
|
|
708
|
-
const { registered, failed } = await registerWithClaudeCode(
|
|
775
|
+
const { registered, failed } = await registerWithClaudeCode(httpServers);
|
|
709
776
|
if (failed.length === 0) {
|
|
710
777
|
cliSpinner.succeed(
|
|
711
778
|
`Registered with Claude Code: ${registered.map((n) => chalk.cyan(n)).join(", ")}`
|
|
@@ -721,7 +788,7 @@ async function runConnect(config, runtime, targets, host = "localhost") {
|
|
|
721
788
|
}
|
|
722
789
|
} else {
|
|
723
790
|
cliSpinner.warn("claude CLI not found on PATH \u2014 register manually:");
|
|
724
|
-
for (const [name, entry] of Object.entries(
|
|
791
|
+
for (const [name, entry] of Object.entries(httpServers)) {
|
|
725
792
|
const baseUrl = entry.url.replace(/\/sse$/, "");
|
|
726
793
|
console.log(
|
|
727
794
|
chalk.dim(` claude mcp add --transport http --scope user ${name} ${baseUrl}`)
|
|
@@ -732,7 +799,7 @@ async function runConnect(config, runtime, targets, host = "localhost") {
|
|
|
732
799
|
const configPath = getConfigPath(target);
|
|
733
800
|
const writeSpinner = ora(`Configuring ${chalk.cyan(target)}...`).start();
|
|
734
801
|
try {
|
|
735
|
-
mergeAndWriteConfig(configPath,
|
|
802
|
+
mergeAndWriteConfig(configPath, httpServers);
|
|
736
803
|
writeSpinner.succeed(`Configured ${chalk.cyan(target)} \u2014 ${chalk.dim(configPath)}`);
|
|
737
804
|
configured.push(target);
|
|
738
805
|
} catch (error) {
|
|
@@ -755,7 +822,7 @@ async function runConnect(config, runtime, targets, host = "localhost") {
|
|
|
755
822
|
const cursorRulesSpinner = ora("Syncing horus-core rules for Cursor...").start();
|
|
756
823
|
try {
|
|
757
824
|
await syncSkillsForCursor(runtime);
|
|
758
|
-
cursorRulesSpinner.succeed("horus-core rules synced to ~/.cursor/rules/");
|
|
825
|
+
cursorRulesSpinner.succeed("horus-core rules synced to ~/.cursor/rules/ and skills to ~/.cursor/skills-cursor/");
|
|
759
826
|
} catch (error) {
|
|
760
827
|
cursorRulesSpinner.warn("Could not sync Cursor rules (Forge container may not be running)");
|
|
761
828
|
console.log(chalk.dim(error.message));
|
|
@@ -1013,7 +1080,7 @@ ${example("forge-registry")}
|
|
|
1013
1080
|
const configSpinner = ora2("Saving configuration...").start();
|
|
1014
1081
|
try {
|
|
1015
1082
|
saveConfig(config);
|
|
1016
|
-
configSpinner.succeed("Configuration saved to
|
|
1083
|
+
configSpinner.succeed("Configuration saved to ~/Horus/config.yaml");
|
|
1017
1084
|
} catch (error) {
|
|
1018
1085
|
configSpinner.fail("Failed to save configuration");
|
|
1019
1086
|
console.error(error.message);
|
|
@@ -1022,7 +1089,7 @@ ${example("forge-registry")}
|
|
|
1022
1089
|
const envSpinner = ora2("Generating .env file...").start();
|
|
1023
1090
|
try {
|
|
1024
1091
|
writeEnvFile(config);
|
|
1025
|
-
envSpinner.succeed("Environment file written to
|
|
1092
|
+
envSpinner.succeed("Environment file written to ~/Horus/.env");
|
|
1026
1093
|
} catch (error) {
|
|
1027
1094
|
envSpinner.fail("Failed to generate .env");
|
|
1028
1095
|
console.error(error.message);
|
|
@@ -1031,7 +1098,7 @@ ${example("forge-registry")}
|
|
|
1031
1098
|
const composeSpinner = ora2("Installing docker-compose.yml...").start();
|
|
1032
1099
|
try {
|
|
1033
1100
|
installComposeFile(runtime.name);
|
|
1034
|
-
composeSpinner.succeed("Compose file installed to
|
|
1101
|
+
composeSpinner.succeed("Compose file installed to ~/Horus/docker-compose.yml");
|
|
1035
1102
|
} catch (error) {
|
|
1036
1103
|
composeSpinner.fail("Failed to install compose file");
|
|
1037
1104
|
console.error(error.message);
|
|
@@ -1117,7 +1184,7 @@ ${example("forge-registry")}
|
|
|
1117
1184
|
healthSpinner.fail("Some services did not become healthy");
|
|
1118
1185
|
console.log(chalk2.dim(error.message));
|
|
1119
1186
|
console.log("");
|
|
1120
|
-
console.log(chalk2.dim("Tip: Check logs with `docker compose logs` from
|
|
1187
|
+
console.log(chalk2.dim("Tip: Check logs with `docker compose logs` from ~/Horus/"));
|
|
1121
1188
|
process.exit(1);
|
|
1122
1189
|
}
|
|
1123
1190
|
console.log("");
|
|
@@ -1138,7 +1205,7 @@ ${example("forge-registry")}
|
|
|
1138
1205
|
console.log(chalk2.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"));
|
|
1139
1206
|
console.log("");
|
|
1140
1207
|
console.log(` ${chalk2.bold("Runtime:")} ${runtime.name}`);
|
|
1141
|
-
console.log(` ${chalk2.bold("Config:")}
|
|
1208
|
+
console.log(` ${chalk2.bold("Config:")} ~/Horus/config.yaml`);
|
|
1142
1209
|
console.log(` ${chalk2.bold("Data:")} ${config.data_dir}`);
|
|
1143
1210
|
console.log("");
|
|
1144
1211
|
console.log(chalk2.bold(" Service URLs:"));
|
|
@@ -1280,7 +1347,7 @@ var statusCommand = new Command5("status").description("Show status of Horus ser
|
|
|
1280
1347
|
console.log(chalk5.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"));
|
|
1281
1348
|
console.log(` ${chalk5.bold("Version:")} ${CLI_VERSION}`);
|
|
1282
1349
|
console.log(` ${chalk5.bold("Runtime:")} ${runtime.name}`);
|
|
1283
|
-
console.log(` ${chalk5.bold("Config:")}
|
|
1350
|
+
console.log(` ${chalk5.bold("Config:")} ~/Horus/config.yaml`);
|
|
1284
1351
|
console.log("");
|
|
1285
1352
|
if (containers.length === 0) {
|
|
1286
1353
|
console.log(chalk5.yellow(" No services are running."));
|
|
@@ -1365,7 +1432,7 @@ var configCommand = new Command6("config").description("View or modify Horus con
|
|
|
1365
1432
|
console.log(` ${chalk6.bold("vault-knowledge:")} ${config.repos.vault_knowledge || chalk6.dim("(not set)")}`);
|
|
1366
1433
|
console.log(` ${chalk6.bold("forge-registry:")} ${config.repos.forge_registry || chalk6.dim("(not set)")}`);
|
|
1367
1434
|
console.log("");
|
|
1368
|
-
console.log(chalk6.dim(` Config file:
|
|
1435
|
+
console.log(chalk6.dim(` Config file: ~/Horus/config.yaml`));
|
|
1369
1436
|
console.log(chalk6.dim(` Use 'horus config get <key>' or 'horus config set <key> <value>'`));
|
|
1370
1437
|
console.log("");
|
|
1371
1438
|
});
|
|
@@ -1756,23 +1823,23 @@ async function checkCompose(preferred) {
|
|
|
1756
1823
|
}
|
|
1757
1824
|
function checkConfig() {
|
|
1758
1825
|
if (configExists()) {
|
|
1759
|
-
return { status: "pass", label: "Config", message: "Configuration file exists (
|
|
1826
|
+
return { status: "pass", label: "Config", message: "Configuration file exists (~/Horus/config.yaml)" };
|
|
1760
1827
|
}
|
|
1761
1828
|
return {
|
|
1762
1829
|
status: "fail",
|
|
1763
1830
|
label: "Config",
|
|
1764
|
-
message: "Configuration file missing (
|
|
1831
|
+
message: "Configuration file missing (~/Horus/config.yaml)",
|
|
1765
1832
|
hint: "Run `horus setup` to create the configuration"
|
|
1766
1833
|
};
|
|
1767
1834
|
}
|
|
1768
1835
|
function checkComposeFile() {
|
|
1769
1836
|
if (existsSync7(COMPOSE_PATH)) {
|
|
1770
|
-
return { status: "pass", label: "Compose file", message: "Compose file installed (
|
|
1837
|
+
return { status: "pass", label: "Compose file", message: "Compose file installed (~/Horus/docker-compose.yml)" };
|
|
1771
1838
|
}
|
|
1772
1839
|
return {
|
|
1773
1840
|
status: "fail",
|
|
1774
1841
|
label: "Compose file",
|
|
1775
|
-
message: "Compose file missing (
|
|
1842
|
+
message: "Compose file missing (~/Horus/docker-compose.yml)",
|
|
1776
1843
|
hint: "Run `horus setup` to install the compose file"
|
|
1777
1844
|
};
|
|
1778
1845
|
}
|
|
@@ -1905,7 +1972,7 @@ var doctorCommand = new Command8("doctor").description("Diagnose common Horus is
|
|
|
1905
1972
|
allResults.push(checkConfig());
|
|
1906
1973
|
allResults.push(checkComposeFile());
|
|
1907
1974
|
const ports = config?.ports ?? DEFAULT_PORTS;
|
|
1908
|
-
const dataDir = config?.data_dir ??
|
|
1975
|
+
const dataDir = config?.data_dir ?? DEFAULT_DATA_DIR;
|
|
1909
1976
|
allResults.push(checkPort(ports.anvil, "Anvil"));
|
|
1910
1977
|
allResults.push(checkPort(ports.vault_rest, "Vault"));
|
|
1911
1978
|
allResults.push(checkPort(ports.vault_mcp, "Vault MCP"));
|