@arkhera30/cli 0.1.5 → 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.
Files changed (2) hide show
  1. package/dist/index.js +159 -84
  2. package/package.json +1 -1
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
  }
@@ -298,13 +324,6 @@ async function detectRuntime(preferred) {
298
324
  "No container runtime found.\n\nHorus requires Docker or Podman with the Compose plugin.\n\nInstall one of:\n - Docker Desktop: https://www.docker.com/products/docker-desktop/\n - Podman Desktop: https://podman-desktop.io/\n"
299
325
  );
300
326
  }
301
- async function registryLogin(runtime, registry, token, username = "horus") {
302
- const result = await execa(runtime.name, ["login", registry, "-u", username, "--password-stdin"], {
303
- input: token,
304
- reject: false
305
- });
306
- return result.exitCode === 0;
307
- }
308
327
  async function composeStreaming(runtime, args) {
309
328
  const bin = runtime.name;
310
329
  const result = await execa(bin, ["compose", ...args], {
@@ -409,7 +428,7 @@ function installComposeFile() {
409
428
  }
410
429
 
411
430
  // src/commands/setup.ts
412
- 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) => {
413
432
  console.log("");
414
433
  console.log(chalk.bold("Horus Setup"));
415
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"));
@@ -470,11 +489,18 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
470
489
  const runtime = await detectRuntime(selectedRuntime);
471
490
  let config;
472
491
  if (opts.yes) {
492
+ const defaults = defaultConfig();
473
493
  config = {
474
- ...defaultConfig(),
494
+ ...defaults,
475
495
  runtime: runtime.name,
476
496
  data_dir: opts.dataDir || DEFAULT_DATA_DIR,
477
- 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
+ }
478
504
  };
479
505
  } else {
480
506
  const data_dir = await input({
@@ -514,12 +540,43 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
514
540
  forge: forge ?? DEFAULT_PORTS.forge
515
541
  };
516
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
+ });
517
568
  config = {
518
569
  ...defaultConfig(),
519
570
  data_dir,
520
571
  host_repos_path,
521
572
  runtime: runtime.name,
522
- 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
+ }
523
580
  };
524
581
  }
525
582
  const configSpinner = ora("Saving configuration...").start();
@@ -549,14 +606,41 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
549
606
  console.error(error.message);
550
607
  process.exit(1);
551
608
  }
552
- const ghcrToken = config.github_token || process.env.GITHUB_TOKEN || "";
553
- if (ghcrToken) {
554
- const loginSpinner = ora("Authenticating with ghcr.io...").start();
555
- const ok = await registryLogin(runtime, "ghcr.io", ghcrToken);
556
- if (ok) {
557
- loginSpinner.succeed("Authenticated with ghcr.io");
558
- } else {
559
- loginSpinner.warn("GHCR login failed \u2014 private images may not pull");
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
+ }
560
644
  }
561
645
  }
562
646
  console.log("");
@@ -812,6 +896,7 @@ var configCommand = new Command5("config").description("View or modify Horus con
812
896
  console.log(` ${chalk5.bold("data-dir:")} ${config.data_dir}`);
813
897
  console.log(` ${chalk5.bold("runtime:")} ${config.runtime}`);
814
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)")}`);
815
900
  console.log(` ${chalk5.bold("github-token:")} ${config.github_token ? maskApiKey(config.github_token) : chalk5.dim("(not set)")}`);
816
901
  console.log("");
817
902
  console.log(chalk5.bold(" Ports:"));
@@ -902,23 +987,23 @@ import { Command as Command6 } from "commander";
902
987
  import chalk6 from "chalk";
903
988
  import ora5 from "ora";
904
989
  import { checkbox } from "@inquirer/prompts";
905
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
906
- 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";
907
992
  import { homedir as homedir3 } from "os";
908
993
  function detectInstalledClients() {
909
994
  const detected = [];
910
995
  const home = homedir3();
911
- const claudeDesktopDir = join3(home, "Library", "Application Support", "Claude");
912
- if (existsSync3(claudeDesktopDir)) {
996
+ const claudeDesktopDir = join4(home, "Library", "Application Support", "Claude");
997
+ if (existsSync4(claudeDesktopDir)) {
913
998
  detected.push("claude-desktop");
914
999
  }
915
- const claudeCodeDir = join3(home, ".claude");
916
- if (existsSync3(claudeCodeDir)) {
1000
+ const claudeCodeDir = join4(home, ".claude");
1001
+ if (existsSync4(claudeCodeDir)) {
917
1002
  detected.push("claude-code");
918
1003
  }
919
- const cursorDir = join3(home, ".cursor");
920
- const cursorAppDir = join3(home, "Library", "Application Support", "Cursor");
921
- 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)) {
922
1007
  detected.push("cursor");
923
1008
  }
924
1009
  return detected;
@@ -927,16 +1012,16 @@ function getConfigPath(target) {
927
1012
  const home = homedir3();
928
1013
  switch (target) {
929
1014
  case "claude-desktop":
930
- return join3(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1015
+ return join4(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
931
1016
  case "claude-code":
932
- return join3(home, ".claude", "settings.json");
1017
+ return join4(home, ".claude", "settings.json");
933
1018
  case "cursor":
934
- return join3(home, ".cursor", "mcp.json");
1019
+ return join4(home, ".cursor", "mcp.json");
935
1020
  }
936
1021
  }
937
1022
  function mergeAndWriteConfig(configPath, mcpServers) {
938
1023
  let existing = {};
939
- if (existsSync3(configPath)) {
1024
+ if (existsSync4(configPath)) {
940
1025
  try {
941
1026
  const raw = readFileSync3(configPath, "utf-8");
942
1027
  existing = JSON.parse(raw);
@@ -947,19 +1032,19 @@ function mergeAndWriteConfig(configPath, mcpServers) {
947
1032
  const existingServers = existing.mcpServers ?? {};
948
1033
  existing.mcpServers = { ...existingServers, ...mcpServers };
949
1034
  const dir = configPath.substring(0, configPath.lastIndexOf("/"));
950
- mkdirSync2(dir, { recursive: true });
1035
+ mkdirSync3(dir, { recursive: true });
951
1036
  writeFileSync3(configPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
952
1037
  }
953
1038
  async function syncSkills(runtime) {
954
1039
  const home = homedir3();
955
- const skillsBase = join3(home, ".claude", "skills");
1040
+ const skillsBase = join4(home, ".claude", "skills");
956
1041
  const skills = ["horus-anvil", "horus-vault", "horus-forge"];
957
1042
  const forgeContainer = "horus-forge-1";
958
1043
  for (const skill of skills) {
959
- const destDir = join3(skillsBase, skill);
960
- mkdirSync2(destDir, { recursive: true });
1044
+ const destDir = join4(skillsBase, skill);
1045
+ mkdirSync3(destDir, { recursive: true });
961
1046
  const src = `/home/forge/.claude/skills/${skill}/SKILL.md`;
962
- const dest = join3(destDir, "SKILL.md");
1047
+ const dest = join4(destDir, "SKILL.md");
963
1048
  const result = await runtime.exec(forgeContainer, "cat", src);
964
1049
  if (result.exitCode === 0 && result.stdout.trim()) {
965
1050
  writeFileSync3(dest, result.stdout, "utf-8");
@@ -1077,16 +1162,16 @@ import { Command as Command7 } from "commander";
1077
1162
  import chalk7 from "chalk";
1078
1163
  import ora6 from "ora";
1079
1164
  import { select as select2, confirm as confirm3 } from "@inquirer/prompts";
1080
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, readdirSync, existsSync as existsSync4 } from "fs";
1081
- 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";
1082
1167
  import { createHash } from "crypto";
1083
1168
  import { stringify as stringifyYaml2, parse as parseYaml2 } from "yaml";
1084
- var SNAPSHOTS_DIR = join4(HORUS_DIR, "snapshots");
1169
+ var SNAPSHOTS_DIR = join5(HORUS_DIR, "snapshots");
1085
1170
  function ensureSnapshotsDir() {
1086
- mkdirSync3(SNAPSHOTS_DIR, { recursive: true });
1171
+ mkdirSync4(SNAPSHOTS_DIR, { recursive: true });
1087
1172
  }
1088
1173
  function composeFileHash() {
1089
- if (!existsSync4(COMPOSE_PATH)) return "";
1174
+ if (!existsSync5(COMPOSE_PATH)) return "";
1090
1175
  const content = readFileSync4(COMPOSE_PATH, "utf-8");
1091
1176
  return createHash("sha256").update(content).digest("hex").slice(0, 12);
1092
1177
  }
@@ -1116,14 +1201,14 @@ function saveSnapshot(images) {
1116
1201
  images,
1117
1202
  compose_hash: composeFileHash()
1118
1203
  };
1119
- const filePath = join4(SNAPSHOTS_DIR, `${timestamp}.yaml`);
1204
+ const filePath = join5(SNAPSHOTS_DIR, `${timestamp}.yaml`);
1120
1205
  writeFileSync4(filePath, stringifyYaml2(snapshot, { lineWidth: 0 }), "utf-8");
1121
1206
  return filePath;
1122
1207
  }
1123
1208
  function listSnapshots() {
1124
- if (!existsSync4(SNAPSHOTS_DIR)) return [];
1209
+ if (!existsSync5(SNAPSHOTS_DIR)) return [];
1125
1210
  return readdirSync(SNAPSHOTS_DIR).filter((f) => f.endsWith(".yaml")).sort().reverse().map((f) => {
1126
- const file = join4(SNAPSHOTS_DIR, f);
1211
+ const file = join5(SNAPSHOTS_DIR, f);
1127
1212
  const snapshot = parseYaml2(readFileSync4(file, "utf-8"));
1128
1213
  return { file, snapshot };
1129
1214
  });
@@ -1264,16 +1349,6 @@ var updateCommand = new Command7("update").description("Update Horus to the late
1264
1349
  snapshotSpinner.warn("Could not save snapshot (update will proceed)");
1265
1350
  console.log(chalk7.dim(error.message));
1266
1351
  }
1267
- const ghcrToken = config.github_token || process.env.GITHUB_TOKEN || "";
1268
- if (ghcrToken) {
1269
- const loginSpinner = ora6("Authenticating with ghcr.io...").start();
1270
- const ok = await registryLogin(runtime, "ghcr.io", ghcrToken);
1271
- if (ok) {
1272
- loginSpinner.succeed("Authenticated with ghcr.io");
1273
- } else {
1274
- loginSpinner.warn("GHCR login failed \u2014 private images may not pull");
1275
- }
1276
- }
1277
1352
  console.log("");
1278
1353
  console.log(chalk7.bold("Pulling latest images..."));
1279
1354
  try {
@@ -1338,9 +1413,9 @@ var updateCommand = new Command7("update").description("Update Horus to the late
1338
1413
  // src/commands/doctor.ts
1339
1414
  import { Command as Command8 } from "commander";
1340
1415
  import chalk8 from "chalk";
1341
- import { execSync } from "child_process";
1342
- import { existsSync as existsSync5, accessSync, statfsSync, constants } from "fs";
1343
- 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";
1344
1419
  function symbol(status) {
1345
1420
  switch (status) {
1346
1421
  case "pass":
@@ -1363,11 +1438,11 @@ function colorMessage(status, msg) {
1363
1438
  }
1364
1439
  async function checkRuntime2() {
1365
1440
  try {
1366
- execSync("docker info", { stdio: "ignore" });
1441
+ execSync2("docker info", { stdio: "ignore" });
1367
1442
  return { status: "pass", label: "Runtime", message: "Docker is running" };
1368
1443
  } catch {
1369
1444
  try {
1370
- execSync("podman info", { stdio: "ignore" });
1445
+ execSync2("podman info", { stdio: "ignore" });
1371
1446
  return { status: "pass", label: "Runtime", message: "Podman is running" };
1372
1447
  } catch {
1373
1448
  return {
@@ -1381,11 +1456,11 @@ async function checkRuntime2() {
1381
1456
  }
1382
1457
  async function checkCompose() {
1383
1458
  try {
1384
- execSync("docker compose version", { stdio: "ignore" });
1459
+ execSync2("docker compose version", { stdio: "ignore" });
1385
1460
  return { status: "pass", label: "Compose", message: "Compose plugin available" };
1386
1461
  } catch {
1387
1462
  try {
1388
- execSync("podman compose version", { stdio: "ignore" });
1463
+ execSync2("podman compose version", { stdio: "ignore" });
1389
1464
  return { status: "pass", label: "Compose", message: "Compose plugin available (podman)" };
1390
1465
  } catch {
1391
1466
  return {
@@ -1409,7 +1484,7 @@ function checkConfig() {
1409
1484
  };
1410
1485
  }
1411
1486
  function checkComposeFile() {
1412
- if (existsSync5(COMPOSE_PATH)) {
1487
+ if (existsSync6(COMPOSE_PATH)) {
1413
1488
  return { status: "pass", label: "Compose file", message: "Compose file installed (~/.horus/docker-compose.yml)" };
1414
1489
  }
1415
1490
  return {
@@ -1421,7 +1496,7 @@ function checkComposeFile() {
1421
1496
  }
1422
1497
  function checkPort(port, serviceName) {
1423
1498
  try {
1424
- 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`, {
1425
1500
  encoding: "utf-8"
1426
1501
  }).trim();
1427
1502
  if (!output) {
@@ -1430,7 +1505,7 @@ function checkPort(port, serviceName) {
1430
1505
  const pids = output.split("\n").filter(Boolean);
1431
1506
  for (const pid of pids) {
1432
1507
  try {
1433
- 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`, {
1434
1509
  encoding: "utf-8"
1435
1510
  }).trim();
1436
1511
  if (cmdline.toLowerCase().includes("docker") || cmdline.toLowerCase().includes("podman")) {
@@ -1450,7 +1525,7 @@ function checkPort(port, serviceName) {
1450
1525
  }
1451
1526
  }
1452
1527
  function checkDataDir(dataDir) {
1453
- if (!existsSync5(dataDir)) {
1528
+ if (!existsSync6(dataDir)) {
1454
1529
  return {
1455
1530
  status: "warn",
1456
1531
  label: "Data directory",
@@ -1471,7 +1546,7 @@ function checkDataDir(dataDir) {
1471
1546
  }
1472
1547
  }
1473
1548
  function checkDiskSpace(dataDir) {
1474
- const checkDir = existsSync5(dataDir) ? dataDir : join5(dataDir, "..");
1549
+ const checkDir = existsSync6(dataDir) ? dataDir : join6(dataDir, "..");
1475
1550
  try {
1476
1551
  const stats = statfsSync(checkDir);
1477
1552
  const freeBytes = stats.bfree * stats.bsize;
@@ -1555,7 +1630,7 @@ var doctorCommand = new Command8("doctor").description("Diagnose common Horus is
1555
1630
  allResults.push(checkComposeFile());
1556
1631
  const config = configExists() ? loadConfig() : null;
1557
1632
  const ports = config?.ports ?? DEFAULT_PORTS;
1558
- const dataDir = config?.data_dir ?? join5(process.env.HOME ?? "~", ".horus", "data");
1633
+ const dataDir = config?.data_dir ?? join6(process.env.HOME ?? "~", ".horus", "data");
1559
1634
  allResults.push(checkPort(ports.anvil, "Anvil"));
1560
1635
  allResults.push(checkPort(ports.vault_rest, "Vault"));
1561
1636
  allResults.push(checkPort(ports.vault_mcp, "Vault MCP"));
@@ -1610,13 +1685,13 @@ import { Command as Command9 } from "commander";
1610
1685
  import chalk9 from "chalk";
1611
1686
  import ora7 from "ora";
1612
1687
  import { confirm as confirm4 } from "@inquirer/prompts";
1613
- import { mkdirSync as mkdirSync4, statSync, existsSync as existsSync6, writeFileSync as writeFileSync5 } from "fs";
1614
- import { join as join6, basename } from "path";
1615
- 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";
1616
1691
  import { stringify as stringifyYaml3 } from "yaml";
1617
- var BACKUPS_DIR = join6(HORUS_DIR, "backups");
1692
+ var BACKUPS_DIR = join7(HORUS_DIR, "backups");
1618
1693
  function ensureBackupsDir() {
1619
- mkdirSync4(BACKUPS_DIR, { recursive: true });
1694
+ mkdirSync5(BACKUPS_DIR, { recursive: true });
1620
1695
  }
1621
1696
  function formatBytes(bytes) {
1622
1697
  if (bytes < 1024) return `${bytes}B`;
@@ -1661,11 +1736,11 @@ async function createBackup(yes) {
1661
1736
  }
1662
1737
  ensureBackupsDir();
1663
1738
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1664
- const tarFile = join6(BACKUPS_DIR, `${timestamp}.tar.gz`);
1665
- 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`);
1666
1741
  const backupSpinner = ora7("Creating backup archive...").start();
1667
1742
  try {
1668
- execSync2(`tar -czf "${tarFile}" -C "${HORUS_DIR}" data/`, {
1743
+ execSync3(`tar -czf "${tarFile}" -C "${HORUS_DIR}" data/`, {
1669
1744
  stdio: "pipe"
1670
1745
  });
1671
1746
  backupSpinner.succeed(`Archive created: ${chalk9.dim(tarFile)}`);
@@ -1711,7 +1786,7 @@ async function restoreBackup(file, yes) {
1711
1786
  console.log(chalk9.bold("Horus Restore"));
1712
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"));
1713
1788
  console.log("");
1714
- if (!existsSync6(file)) {
1789
+ if (!existsSync7(file)) {
1715
1790
  console.log(chalk9.red(`Backup file not found: ${file}`));
1716
1791
  process.exit(1);
1717
1792
  }
@@ -1749,7 +1824,7 @@ async function restoreBackup(file, yes) {
1749
1824
  }
1750
1825
  const extractSpinner = ora7("Extracting backup...").start();
1751
1826
  try {
1752
- execSync2(`tar -xzf "${file}" -C "${HORUS_DIR}/"`, { stdio: "pipe" });
1827
+ execSync3(`tar -xzf "${file}" -C "${HORUS_DIR}/"`, { stdio: "pipe" });
1753
1828
  extractSpinner.succeed("Backup extracted");
1754
1829
  } catch (error) {
1755
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.5",
3
+ "version": "0.1.6",
4
4
  "description": "CLI for managing the Horus AI development stack",
5
5
  "type": "module",
6
6
  "bin": {