@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.
Files changed (2) hide show
  1. package/dist/index.js +91 -24
  2. 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(), ".horus");
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(), ".horus", "data");
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 ~/.horus/ to investigate.`
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 ~/.horus/ to investigate.`
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(dest, frontmatter + result.stdout, "utf-8");
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 mcpServers = {
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-code") {
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(mcpServers);
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(mcpServers)) {
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, mcpServers);
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 ~/.horus/config.yaml");
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 ~/.horus/.env");
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 ~/.horus/docker-compose.yml");
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 ~/.horus/"));
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:")} ~/.horus/config.yaml`);
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:")} ~/.horus/config.yaml`);
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: ~/.horus/config.yaml`));
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 (~/.horus/config.yaml)" };
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 (~/.horus/config.yaml)",
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 (~/.horus/docker-compose.yml)" };
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 (~/.horus/docker-compose.yml)",
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 ?? join6(process.env.HOME ?? "~", ".horus", "data");
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"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkhera30/cli",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "CLI for managing the Horus AI development stack",
5
5
  "type": "module",
6
6
  "bin": {