@arkhera30/cli 0.1.4 → 0.1.6

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.
@@ -26,7 +26,7 @@ services:
26
26
  #
27
27
  # start_period covers first-boot GGUF model download (~1-2 GB) + initial embed.
28
28
  qmd-daemon:
29
- image: ghcr.io/arkhera/horus/qmd-daemon:latest
29
+ image: ghcr.io/arjunkhera/horus/qmd-daemon:latest
30
30
  environment:
31
31
  - QMD_DAEMON_PORT=8181
32
32
  volumes:
@@ -51,7 +51,7 @@ services:
51
51
  # ── Anvil ──────────────────────────────────────────────────────────────────
52
52
  # Notes system and MCP server. Indexes markdown files from the Notes repo.
53
53
  anvil:
54
- image: ghcr.io/arkhera/horus/anvil:latest
54
+ image: ghcr.io/arjunkhera/horus/anvil:latest
55
55
  ports:
56
56
  - "${ANVIL_PORT:-8100}:8100"
57
57
  volumes:
@@ -94,7 +94,7 @@ services:
94
94
  # ── Vault ──────────────────────────────────────────────────────────────────
95
95
  # Knowledge service. Semantic search over the knowledge-base repo.
96
96
  vault:
97
- image: ghcr.io/arkhera/horus/vault:latest
97
+ image: ghcr.io/arjunkhera/horus/vault:latest
98
98
  ports:
99
99
  - "${VAULT_PORT:-8000}:8000"
100
100
  volumes:
@@ -140,7 +140,7 @@ services:
140
140
  # ── Vault MCP ──────────────────────────────────────────────────────────────
141
141
  # Thin MCP adapter that translates MCP tool calls to Vault REST API calls.
142
142
  vault-mcp:
143
- image: ghcr.io/arkhera/horus/vault-mcp:latest
143
+ image: ghcr.io/arjunkhera/horus/vault-mcp:latest
144
144
  ports:
145
145
  - "${VAULT_MCP_PORT:-8300}:8300"
146
146
  environment:
@@ -172,7 +172,7 @@ services:
172
172
  # ── Forge ──────────────────────────────────────────────────────────────────
173
173
  # Workspace manager and package registry MCP server.
174
174
  forge:
175
- image: ghcr.io/arkhera/horus/forge:latest
175
+ image: ghcr.io/arjunkhera/horus/forge:latest
176
176
  ports:
177
177
  - "${FORGE_PORT:-8200}:8200"
178
178
  volumes:
package/dist/index.js CHANGED
@@ -9,6 +9,9 @@ import { createRequire } from "module";
9
9
  import { Command } from "commander";
10
10
  import chalk from "chalk";
11
11
  import ora from "ora";
12
+ import { execSync } from "child_process";
13
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
14
+ import { join as join3 } from "path";
12
15
  import { input, confirm, number, select } from "@inquirer/prompts";
13
16
 
14
17
  // src/lib/config.ts
@@ -32,8 +35,8 @@ var DEFAULT_PORTS = {
32
35
  };
33
36
  var DEFAULT_REPOS = {
34
37
  anvil_notes: "",
35
- vault_knowledge: "https://github.com/arkhera/knowledge-base",
36
- forge_registry: "https://github.com/arkhera/Forge-Registry"
38
+ vault_knowledge: "",
39
+ forge_registry: ""
37
40
  };
38
41
  var DEFAULT_DATA_DIR = join(homedir(), ".horus", "data");
39
42
  var SERVICES = [
@@ -52,6 +55,7 @@ function defaultConfig() {
52
55
  data_dir: DEFAULT_DATA_DIR,
53
56
  runtime: "docker",
54
57
  ports: { ...DEFAULT_PORTS },
58
+ git_host: "github.com",
55
59
  repos: { ...DEFAULT_REPOS },
56
60
  host_repos_path: "",
57
61
  github_token: ""
@@ -80,6 +84,7 @@ function loadConfig() {
80
84
  vault_mcp: parsed.ports?.vault_mcp ?? defaults.ports.vault_mcp,
81
85
  forge: parsed.ports?.forge ?? defaults.ports.forge
82
86
  },
87
+ git_host: parsed.git_host ?? defaults.git_host,
83
88
  repos: {
84
89
  anvil_notes: parsed.repos?.anvil_notes ?? defaults.repos.anvil_notes,
85
90
  vault_knowledge: parsed.repos?.vault_knowledge ?? defaults.repos.vault_knowledge,
@@ -118,9 +123,6 @@ function generateEnv(config) {
118
123
  `VAULT_MCP_PORT=${config.ports.vault_mcp}`,
119
124
  `FORGE_PORT=${config.ports.forge}`,
120
125
  "",
121
- "# Auth",
122
- `GITHUB_TOKEN=${config.github_token}`,
123
- "",
124
126
  "# Repository URLs",
125
127
  `ANVIL_REPO_URL=${config.repos.anvil_notes}`,
126
128
  `VAULT_KNOWLEDGE_REPO_URL=${config.repos.vault_knowledge}`,
@@ -142,7 +144,11 @@ var CONFIG_KEYS = [
142
144
  "port.vault-rest",
143
145
  "port.vault-mcp",
144
146
  "port.forge",
145
- "github-token"
147
+ "github-token",
148
+ "git-host",
149
+ "repo.anvil-notes",
150
+ "repo.vault-knowledge",
151
+ "repo.forge-registry"
146
152
  ];
147
153
  function getConfigValue(config, key) {
148
154
  switch (key) {
@@ -162,6 +168,14 @@ function getConfigValue(config, key) {
162
168
  return String(config.ports.forge);
163
169
  case "github-token":
164
170
  return config.github_token;
171
+ case "git-host":
172
+ return config.git_host;
173
+ case "repo.anvil-notes":
174
+ return config.repos.anvil_notes;
175
+ case "repo.vault-knowledge":
176
+ return config.repos.vault_knowledge;
177
+ case "repo.forge-registry":
178
+ return config.repos.forge_registry;
165
179
  }
166
180
  }
167
181
  function setConfigValue(config, key, value) {
@@ -194,6 +208,18 @@ function setConfigValue(config, key, value) {
194
208
  case "github-token":
195
209
  updated.github_token = value;
196
210
  break;
211
+ case "git-host":
212
+ updated.git_host = value;
213
+ break;
214
+ case "repo.anvil-notes":
215
+ updated.repos = { ...updated.repos, anvil_notes: value };
216
+ break;
217
+ case "repo.vault-knowledge":
218
+ updated.repos = { ...updated.repos, vault_knowledge: value };
219
+ break;
220
+ case "repo.forge-registry":
221
+ updated.repos = { ...updated.repos, forge_registry: value };
222
+ break;
197
223
  }
198
224
  return updated;
199
225
  }
@@ -402,7 +428,7 @@ function installComposeFile() {
402
428
  }
403
429
 
404
430
  // src/commands/setup.ts
405
- 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").action(async (opts) => {
431
+ 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").action(async (opts) => {
406
432
  console.log("");
407
433
  console.log(chalk.bold("Horus Setup"));
408
434
  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"));
@@ -463,11 +489,18 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
463
489
  const runtime = await detectRuntime(selectedRuntime);
464
490
  let config;
465
491
  if (opts.yes) {
492
+ const defaults = defaultConfig();
466
493
  config = {
467
- ...defaultConfig(),
494
+ ...defaults,
468
495
  runtime: runtime.name,
469
496
  data_dir: opts.dataDir || DEFAULT_DATA_DIR,
470
- host_repos_path: opts.reposPath || ""
497
+ host_repos_path: opts.reposPath || "",
498
+ git_host: opts.gitHost || defaults.git_host,
499
+ repos: {
500
+ anvil_notes: opts.anvilRepo || process.env.ANVIL_REPO_URL || defaults.repos.anvil_notes,
501
+ vault_knowledge: opts.vaultRepo || process.env.VAULT_KNOWLEDGE_REPO_URL || defaults.repos.vault_knowledge,
502
+ forge_registry: opts.forgeRepo || process.env.FORGE_REGISTRY_REPO_URL || defaults.repos.forge_registry
503
+ }
471
504
  };
472
505
  } else {
473
506
  const data_dir = await input({
@@ -507,12 +540,43 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
507
540
  forge: forge ?? DEFAULT_PORTS.forge
508
541
  };
509
542
  }
543
+ console.log("");
544
+ console.log(chalk.bold("Repository Configuration"));
545
+ console.log(chalk.dim("Horus stores notes and knowledge in Git repos you own."));
546
+ console.log(chalk.dim("Create empty repos on your Git server, then paste the URLs below."));
547
+ console.log("");
548
+ const git_host = await input({
549
+ message: "Git server hostname:",
550
+ default: "github.com"
551
+ });
552
+ const examplePrefix = `git@${git_host}:<owner>`;
553
+ console.log("");
554
+ console.log(chalk.dim(` Example: ${examplePrefix}/my-repo.git`));
555
+ console.log("");
556
+ const anvil_notes = await input({
557
+ message: "Anvil notes repo URL (required):",
558
+ validate: (v) => v.trim().length > 0 || "Anvil needs a notes repo to store your data."
559
+ });
560
+ const vault_knowledge = await input({
561
+ message: "Vault knowledge-base repo URL (required):",
562
+ validate: (v) => v.trim().length > 0 || "Vault needs a knowledge-base repo."
563
+ });
564
+ const forge_registry = await input({
565
+ message: "Forge registry repo URL (required):",
566
+ validate: (v) => v.trim().length > 0 || "Forge needs a registry repo."
567
+ });
510
568
  config = {
511
569
  ...defaultConfig(),
512
570
  data_dir,
513
571
  host_repos_path,
514
572
  runtime: runtime.name,
515
- ports
573
+ ports,
574
+ git_host: git_host.trim(),
575
+ repos: {
576
+ anvil_notes: anvil_notes.trim(),
577
+ vault_knowledge: vault_knowledge.trim(),
578
+ forge_registry: forge_registry.trim()
579
+ }
516
580
  };
517
581
  }
518
582
  const configSpinner = ora("Saving configuration...").start();
@@ -542,14 +606,50 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
542
606
  console.error(error.message);
543
607
  process.exit(1);
544
608
  }
609
+ const dataDir = config.data_dir.startsWith("~") ? join3(process.env.HOME || "", config.data_dir.slice(1)) : config.data_dir;
610
+ const reposToClone = [
611
+ { url: config.repos.anvil_notes, dest: join3(dataDir, "notes"), label: "Anvil notes" },
612
+ { url: config.repos.vault_knowledge, dest: join3(dataDir, "knowledge-base"), label: "Vault knowledge-base" },
613
+ { url: config.repos.forge_registry, dest: join3(dataDir, "registry"), label: "Forge registry" }
614
+ ].filter((r) => r.url);
615
+ if (reposToClone.length > 0) {
616
+ console.log("");
617
+ console.log(chalk.bold("Cloning repositories..."));
618
+ mkdirSync2(dataDir, { recursive: true });
619
+ for (const repo of reposToClone) {
620
+ const spinner = ora(`Cloning ${repo.label}...`).start();
621
+ if (existsSync3(join3(repo.dest, ".git"))) {
622
+ spinner.succeed(`${repo.label} already cloned`);
623
+ continue;
624
+ }
625
+ try {
626
+ mkdirSync2(repo.dest, { recursive: true });
627
+ execSync(`git clone "${repo.url}" "${repo.dest}"`, {
628
+ stdio: "pipe",
629
+ timeout: 6e4
630
+ });
631
+ spinner.succeed(`${repo.label} cloned`);
632
+ } catch (error) {
633
+ spinner.fail(`Failed to clone ${repo.label}`);
634
+ const msg = error.message || "";
635
+ if (msg.includes("already exists and is not an empty directory")) {
636
+ console.log(chalk.dim(" Directory exists but has no .git \u2014 check the path."));
637
+ } else {
638
+ console.log(chalk.dim(` ${msg.split("\n")[0]}`));
639
+ }
640
+ console.log(chalk.dim(` URL: ${repo.url}`));
641
+ console.log(chalk.dim(" Ensure you have git access (SSH key or credential helper)."));
642
+ process.exit(1);
643
+ }
644
+ }
645
+ }
545
646
  console.log("");
546
647
  console.log(chalk.bold("Pulling container images..."));
547
648
  try {
548
- await composeStreaming(runtime, ["pull"]);
549
- } catch (error) {
550
- console.log(chalk.red("Failed to pull images."));
551
- console.log(chalk.dim(error.message));
552
- process.exit(1);
649
+ await composeStreaming(runtime, ["pull", "--ignore-pull-failures"]);
650
+ } catch {
651
+ console.log(chalk.yellow("Some images could not be pulled."));
652
+ console.log(chalk.dim("Continuing \u2014 services will be built from source if build contexts are available."));
553
653
  }
554
654
  console.log("");
555
655
  console.log(chalk.bold("Starting Horus services..."));
@@ -796,6 +896,7 @@ var configCommand = new Command5("config").description("View or modify Horus con
796
896
  console.log(` ${chalk5.bold("data-dir:")} ${config.data_dir}`);
797
897
  console.log(` ${chalk5.bold("runtime:")} ${config.runtime}`);
798
898
  console.log(` ${chalk5.bold("host-repos-path:")} ${config.host_repos_path || chalk5.dim("(not set)")}`);
899
+ console.log(` ${chalk5.bold("git-host:")} ${config.git_host || chalk5.dim("(not set)")}`);
799
900
  console.log(` ${chalk5.bold("github-token:")} ${config.github_token ? maskApiKey(config.github_token) : chalk5.dim("(not set)")}`);
800
901
  console.log("");
801
902
  console.log(chalk5.bold(" Ports:"));
@@ -886,23 +987,23 @@ import { Command as Command6 } from "commander";
886
987
  import chalk6 from "chalk";
887
988
  import ora5 from "ora";
888
989
  import { checkbox } from "@inquirer/prompts";
889
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
890
- import { join as join3 } from "path";
990
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
991
+ import { join as join4 } from "path";
891
992
  import { homedir as homedir3 } from "os";
892
993
  function detectInstalledClients() {
893
994
  const detected = [];
894
995
  const home = homedir3();
895
- const claudeDesktopDir = join3(home, "Library", "Application Support", "Claude");
896
- if (existsSync3(claudeDesktopDir)) {
996
+ const claudeDesktopDir = join4(home, "Library", "Application Support", "Claude");
997
+ if (existsSync4(claudeDesktopDir)) {
897
998
  detected.push("claude-desktop");
898
999
  }
899
- const claudeCodeDir = join3(home, ".claude");
900
- if (existsSync3(claudeCodeDir)) {
1000
+ const claudeCodeDir = join4(home, ".claude");
1001
+ if (existsSync4(claudeCodeDir)) {
901
1002
  detected.push("claude-code");
902
1003
  }
903
- const cursorDir = join3(home, ".cursor");
904
- const cursorAppDir = join3(home, "Library", "Application Support", "Cursor");
905
- if (existsSync3(cursorDir) || existsSync3(cursorAppDir)) {
1004
+ const cursorDir = join4(home, ".cursor");
1005
+ const cursorAppDir = join4(home, "Library", "Application Support", "Cursor");
1006
+ if (existsSync4(cursorDir) || existsSync4(cursorAppDir)) {
906
1007
  detected.push("cursor");
907
1008
  }
908
1009
  return detected;
@@ -911,16 +1012,16 @@ function getConfigPath(target) {
911
1012
  const home = homedir3();
912
1013
  switch (target) {
913
1014
  case "claude-desktop":
914
- return join3(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1015
+ return join4(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
915
1016
  case "claude-code":
916
- return join3(home, ".claude", "settings.json");
1017
+ return join4(home, ".claude", "settings.json");
917
1018
  case "cursor":
918
- return join3(home, ".cursor", "mcp.json");
1019
+ return join4(home, ".cursor", "mcp.json");
919
1020
  }
920
1021
  }
921
1022
  function mergeAndWriteConfig(configPath, mcpServers) {
922
1023
  let existing = {};
923
- if (existsSync3(configPath)) {
1024
+ if (existsSync4(configPath)) {
924
1025
  try {
925
1026
  const raw = readFileSync3(configPath, "utf-8");
926
1027
  existing = JSON.parse(raw);
@@ -931,19 +1032,19 @@ function mergeAndWriteConfig(configPath, mcpServers) {
931
1032
  const existingServers = existing.mcpServers ?? {};
932
1033
  existing.mcpServers = { ...existingServers, ...mcpServers };
933
1034
  const dir = configPath.substring(0, configPath.lastIndexOf("/"));
934
- mkdirSync2(dir, { recursive: true });
1035
+ mkdirSync3(dir, { recursive: true });
935
1036
  writeFileSync3(configPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
936
1037
  }
937
1038
  async function syncSkills(runtime) {
938
1039
  const home = homedir3();
939
- const skillsBase = join3(home, ".claude", "skills");
1040
+ const skillsBase = join4(home, ".claude", "skills");
940
1041
  const skills = ["horus-anvil", "horus-vault", "horus-forge"];
941
1042
  const forgeContainer = "horus-forge-1";
942
1043
  for (const skill of skills) {
943
- const destDir = join3(skillsBase, skill);
944
- mkdirSync2(destDir, { recursive: true });
1044
+ const destDir = join4(skillsBase, skill);
1045
+ mkdirSync3(destDir, { recursive: true });
945
1046
  const src = `/home/forge/.claude/skills/${skill}/SKILL.md`;
946
- const dest = join3(destDir, "SKILL.md");
1047
+ const dest = join4(destDir, "SKILL.md");
947
1048
  const result = await runtime.exec(forgeContainer, "cat", src);
948
1049
  if (result.exitCode === 0 && result.stdout.trim()) {
949
1050
  writeFileSync3(dest, result.stdout, "utf-8");
@@ -1061,16 +1162,16 @@ import { Command as Command7 } from "commander";
1061
1162
  import chalk7 from "chalk";
1062
1163
  import ora6 from "ora";
1063
1164
  import { select as select2, confirm as confirm3 } from "@inquirer/prompts";
1064
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, readdirSync, existsSync as existsSync4 } from "fs";
1065
- import { join as join4 } from "path";
1165
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readdirSync, existsSync as existsSync5 } from "fs";
1166
+ import { join as join5 } from "path";
1066
1167
  import { createHash } from "crypto";
1067
1168
  import { stringify as stringifyYaml2, parse as parseYaml2 } from "yaml";
1068
- var SNAPSHOTS_DIR = join4(HORUS_DIR, "snapshots");
1169
+ var SNAPSHOTS_DIR = join5(HORUS_DIR, "snapshots");
1069
1170
  function ensureSnapshotsDir() {
1070
- mkdirSync3(SNAPSHOTS_DIR, { recursive: true });
1171
+ mkdirSync4(SNAPSHOTS_DIR, { recursive: true });
1071
1172
  }
1072
1173
  function composeFileHash() {
1073
- if (!existsSync4(COMPOSE_PATH)) return "";
1174
+ if (!existsSync5(COMPOSE_PATH)) return "";
1074
1175
  const content = readFileSync4(COMPOSE_PATH, "utf-8");
1075
1176
  return createHash("sha256").update(content).digest("hex").slice(0, 12);
1076
1177
  }
@@ -1100,14 +1201,14 @@ function saveSnapshot(images) {
1100
1201
  images,
1101
1202
  compose_hash: composeFileHash()
1102
1203
  };
1103
- const filePath = join4(SNAPSHOTS_DIR, `${timestamp}.yaml`);
1204
+ const filePath = join5(SNAPSHOTS_DIR, `${timestamp}.yaml`);
1104
1205
  writeFileSync4(filePath, stringifyYaml2(snapshot, { lineWidth: 0 }), "utf-8");
1105
1206
  return filePath;
1106
1207
  }
1107
1208
  function listSnapshots() {
1108
- if (!existsSync4(SNAPSHOTS_DIR)) return [];
1209
+ if (!existsSync5(SNAPSHOTS_DIR)) return [];
1109
1210
  return readdirSync(SNAPSHOTS_DIR).filter((f) => f.endsWith(".yaml")).sort().reverse().map((f) => {
1110
- const file = join4(SNAPSHOTS_DIR, f);
1211
+ const file = join5(SNAPSHOTS_DIR, f);
1111
1212
  const snapshot = parseYaml2(readFileSync4(file, "utf-8"));
1112
1213
  return { file, snapshot };
1113
1214
  });
@@ -1251,11 +1352,10 @@ var updateCommand = new Command7("update").description("Update Horus to the late
1251
1352
  console.log("");
1252
1353
  console.log(chalk7.bold("Pulling latest images..."));
1253
1354
  try {
1254
- await composeStreaming(runtime, ["pull"]);
1255
- } catch (error) {
1256
- console.log(chalk7.red("Failed to pull images."));
1257
- console.log(chalk7.dim(error.message));
1258
- process.exit(1);
1355
+ await composeStreaming(runtime, ["pull", "--ignore-pull-failures"]);
1356
+ } catch {
1357
+ console.log(chalk7.yellow("Some images could not be pulled."));
1358
+ console.log(chalk7.dim("Continuing \u2014 services will be built from source if build contexts are available."));
1259
1359
  }
1260
1360
  console.log("");
1261
1361
  console.log(chalk7.bold("Restarting services..."));
@@ -1313,9 +1413,9 @@ var updateCommand = new Command7("update").description("Update Horus to the late
1313
1413
  // src/commands/doctor.ts
1314
1414
  import { Command as Command8 } from "commander";
1315
1415
  import chalk8 from "chalk";
1316
- import { execSync } from "child_process";
1317
- import { existsSync as existsSync5, accessSync, statfsSync, constants } from "fs";
1318
- import { join as join5 } from "path";
1416
+ import { execSync as execSync2 } from "child_process";
1417
+ import { existsSync as existsSync6, accessSync, statfsSync, constants } from "fs";
1418
+ import { join as join6 } from "path";
1319
1419
  function symbol(status) {
1320
1420
  switch (status) {
1321
1421
  case "pass":
@@ -1338,11 +1438,11 @@ function colorMessage(status, msg) {
1338
1438
  }
1339
1439
  async function checkRuntime2() {
1340
1440
  try {
1341
- execSync("docker info", { stdio: "ignore" });
1441
+ execSync2("docker info", { stdio: "ignore" });
1342
1442
  return { status: "pass", label: "Runtime", message: "Docker is running" };
1343
1443
  } catch {
1344
1444
  try {
1345
- execSync("podman info", { stdio: "ignore" });
1445
+ execSync2("podman info", { stdio: "ignore" });
1346
1446
  return { status: "pass", label: "Runtime", message: "Podman is running" };
1347
1447
  } catch {
1348
1448
  return {
@@ -1356,11 +1456,11 @@ async function checkRuntime2() {
1356
1456
  }
1357
1457
  async function checkCompose() {
1358
1458
  try {
1359
- execSync("docker compose version", { stdio: "ignore" });
1459
+ execSync2("docker compose version", { stdio: "ignore" });
1360
1460
  return { status: "pass", label: "Compose", message: "Compose plugin available" };
1361
1461
  } catch {
1362
1462
  try {
1363
- execSync("podman compose version", { stdio: "ignore" });
1463
+ execSync2("podman compose version", { stdio: "ignore" });
1364
1464
  return { status: "pass", label: "Compose", message: "Compose plugin available (podman)" };
1365
1465
  } catch {
1366
1466
  return {
@@ -1384,7 +1484,7 @@ function checkConfig() {
1384
1484
  };
1385
1485
  }
1386
1486
  function checkComposeFile() {
1387
- if (existsSync5(COMPOSE_PATH)) {
1487
+ if (existsSync6(COMPOSE_PATH)) {
1388
1488
  return { status: "pass", label: "Compose file", message: "Compose file installed (~/.horus/docker-compose.yml)" };
1389
1489
  }
1390
1490
  return {
@@ -1396,7 +1496,7 @@ function checkComposeFile() {
1396
1496
  }
1397
1497
  function checkPort(port, serviceName) {
1398
1498
  try {
1399
- const output = execSync(`lsof -i :${port} -sTCP:LISTEN -t 2>/dev/null || true`, {
1499
+ const output = execSync2(`lsof -i :${port} -sTCP:LISTEN -t 2>/dev/null || true`, {
1400
1500
  encoding: "utf-8"
1401
1501
  }).trim();
1402
1502
  if (!output) {
@@ -1405,7 +1505,7 @@ function checkPort(port, serviceName) {
1405
1505
  const pids = output.split("\n").filter(Boolean);
1406
1506
  for (const pid of pids) {
1407
1507
  try {
1408
- const cmdline = execSync(`ps -p ${pid} -o comm= 2>/dev/null || true`, {
1508
+ const cmdline = execSync2(`ps -p ${pid} -o comm= 2>/dev/null || true`, {
1409
1509
  encoding: "utf-8"
1410
1510
  }).trim();
1411
1511
  if (cmdline.toLowerCase().includes("docker") || cmdline.toLowerCase().includes("podman")) {
@@ -1425,7 +1525,7 @@ function checkPort(port, serviceName) {
1425
1525
  }
1426
1526
  }
1427
1527
  function checkDataDir(dataDir) {
1428
- if (!existsSync5(dataDir)) {
1528
+ if (!existsSync6(dataDir)) {
1429
1529
  return {
1430
1530
  status: "warn",
1431
1531
  label: "Data directory",
@@ -1446,7 +1546,7 @@ function checkDataDir(dataDir) {
1446
1546
  }
1447
1547
  }
1448
1548
  function checkDiskSpace(dataDir) {
1449
- const checkDir = existsSync5(dataDir) ? dataDir : join5(dataDir, "..");
1549
+ const checkDir = existsSync6(dataDir) ? dataDir : join6(dataDir, "..");
1450
1550
  try {
1451
1551
  const stats = statfsSync(checkDir);
1452
1552
  const freeBytes = stats.bfree * stats.bsize;
@@ -1530,7 +1630,7 @@ var doctorCommand = new Command8("doctor").description("Diagnose common Horus is
1530
1630
  allResults.push(checkComposeFile());
1531
1631
  const config = configExists() ? loadConfig() : null;
1532
1632
  const ports = config?.ports ?? DEFAULT_PORTS;
1533
- const dataDir = config?.data_dir ?? join5(process.env.HOME ?? "~", ".horus", "data");
1633
+ const dataDir = config?.data_dir ?? join6(process.env.HOME ?? "~", ".horus", "data");
1534
1634
  allResults.push(checkPort(ports.anvil, "Anvil"));
1535
1635
  allResults.push(checkPort(ports.vault_rest, "Vault"));
1536
1636
  allResults.push(checkPort(ports.vault_mcp, "Vault MCP"));
@@ -1585,13 +1685,13 @@ import { Command as Command9 } from "commander";
1585
1685
  import chalk9 from "chalk";
1586
1686
  import ora7 from "ora";
1587
1687
  import { confirm as confirm4 } from "@inquirer/prompts";
1588
- import { mkdirSync as mkdirSync4, statSync, existsSync as existsSync6, writeFileSync as writeFileSync5 } from "fs";
1589
- import { join as join6, basename } from "path";
1590
- import { execSync as execSync2 } from "child_process";
1688
+ import { mkdirSync as mkdirSync5, statSync, existsSync as existsSync7, writeFileSync as writeFileSync5 } from "fs";
1689
+ import { join as join7, basename } from "path";
1690
+ import { execSync as execSync3 } from "child_process";
1591
1691
  import { stringify as stringifyYaml3 } from "yaml";
1592
- var BACKUPS_DIR = join6(HORUS_DIR, "backups");
1692
+ var BACKUPS_DIR = join7(HORUS_DIR, "backups");
1593
1693
  function ensureBackupsDir() {
1594
- mkdirSync4(BACKUPS_DIR, { recursive: true });
1694
+ mkdirSync5(BACKUPS_DIR, { recursive: true });
1595
1695
  }
1596
1696
  function formatBytes(bytes) {
1597
1697
  if (bytes < 1024) return `${bytes}B`;
@@ -1636,11 +1736,11 @@ async function createBackup(yes) {
1636
1736
  }
1637
1737
  ensureBackupsDir();
1638
1738
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1639
- const tarFile = join6(BACKUPS_DIR, `${timestamp}.tar.gz`);
1640
- const metaFile = join6(BACKUPS_DIR, `${timestamp}.meta.yaml`);
1739
+ const tarFile = join7(BACKUPS_DIR, `${timestamp}.tar.gz`);
1740
+ const metaFile = join7(BACKUPS_DIR, `${timestamp}.meta.yaml`);
1641
1741
  const backupSpinner = ora7("Creating backup archive...").start();
1642
1742
  try {
1643
- execSync2(`tar -czf "${tarFile}" -C "${HORUS_DIR}" data/`, {
1743
+ execSync3(`tar -czf "${tarFile}" -C "${HORUS_DIR}" data/`, {
1644
1744
  stdio: "pipe"
1645
1745
  });
1646
1746
  backupSpinner.succeed(`Archive created: ${chalk9.dim(tarFile)}`);
@@ -1686,7 +1786,7 @@ async function restoreBackup(file, yes) {
1686
1786
  console.log(chalk9.bold("Horus Restore"));
1687
1787
  console.log(chalk9.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"));
1688
1788
  console.log("");
1689
- if (!existsSync6(file)) {
1789
+ if (!existsSync7(file)) {
1690
1790
  console.log(chalk9.red(`Backup file not found: ${file}`));
1691
1791
  process.exit(1);
1692
1792
  }
@@ -1724,7 +1824,7 @@ async function restoreBackup(file, yes) {
1724
1824
  }
1725
1825
  const extractSpinner = ora7("Extracting backup...").start();
1726
1826
  try {
1727
- execSync2(`tar -xzf "${file}" -C "${HORUS_DIR}/"`, { stdio: "pipe" });
1827
+ execSync3(`tar -xzf "${file}" -C "${HORUS_DIR}/"`, { stdio: "pipe" });
1728
1828
  extractSpinner.succeed("Backup extracted");
1729
1829
  } catch (error) {
1730
1830
  extractSpinner.fail("Failed to extract backup");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkhera30/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "CLI for managing the Horus AI development stack",
5
5
  "type": "module",
6
6
  "bin": {