@arkhera30/cli 0.1.5 → 0.1.7

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 +205 -86
  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,10 +123,7 @@ 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
- "# Repository URLs",
126
+ "# Repository URLs (must be HTTPS \u2014 container services do not have SSH keys)",
125
127
  `ANVIL_REPO_URL=${config.repos.anvil_notes}`,
126
128
  `VAULT_KNOWLEDGE_REPO_URL=${config.repos.vault_knowledge}`,
127
129
  `FORGE_REGISTRY_REPO_URL=${config.repos.forge_registry}`,
@@ -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,52 @@ 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
+ console.log(chalk.yellow(" Use HTTPS URLs \u2014 container services do not have SSH keys."));
549
+ 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
+ console.log("");
552
+ const git_host = await input({
553
+ message: "Git server hostname:",
554
+ default: "github.com"
555
+ });
556
+ const host = git_host.trim();
557
+ const example = (repo) => chalk.dim(` e.g., https://${host}/<owner>/${repo}`);
558
+ console.log("");
559
+ const anvil_notes = await input({
560
+ message: `Anvil notes repo URL:
561
+ ${example("horus-notes")}
562
+ `,
563
+ validate: (v) => v.trim().length > 0 || "Anvil needs a notes repo to store your data."
564
+ });
565
+ const vault_knowledge = await input({
566
+ message: `Vault knowledge-base repo URL:
567
+ ${example("knowledge-base")}
568
+ `,
569
+ validate: (v) => v.trim().length > 0 || "Vault needs a knowledge-base repo."
570
+ });
571
+ const forge_registry = await input({
572
+ message: `Forge registry repo URL:
573
+ ${example("forge-registry")}
574
+ `,
575
+ validate: (v) => v.trim().length > 0 || "Forge needs a registry repo."
576
+ });
517
577
  config = {
518
578
  ...defaultConfig(),
519
579
  data_dir,
520
580
  host_repos_path,
521
581
  runtime: runtime.name,
522
- ports
582
+ ports,
583
+ git_host: git_host.trim(),
584
+ repos: {
585
+ anvil_notes: anvil_notes.trim(),
586
+ vault_knowledge: vault_knowledge.trim(),
587
+ forge_registry: forge_registry.trim()
588
+ }
523
589
  };
524
590
  }
525
591
  const configSpinner = ora("Saving configuration...").start();
@@ -549,14 +615,41 @@ var setupCommand = new Command("setup").description("Interactive first-run setup
549
615
  console.error(error.message);
550
616
  process.exit(1);
551
617
  }
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");
618
+ const dataDir = config.data_dir.startsWith("~") ? join3(process.env.HOME || "", config.data_dir.slice(1)) : config.data_dir;
619
+ const reposToClone = [
620
+ { url: config.repos.anvil_notes, dest: join3(dataDir, "notes"), label: "Anvil notes" },
621
+ { url: config.repos.vault_knowledge, dest: join3(dataDir, "knowledge-base"), label: "Vault knowledge-base" },
622
+ { url: config.repos.forge_registry, dest: join3(dataDir, "registry"), label: "Forge registry" }
623
+ ].filter((r) => r.url);
624
+ if (reposToClone.length > 0) {
625
+ console.log("");
626
+ console.log(chalk.bold("Cloning repositories..."));
627
+ mkdirSync2(dataDir, { recursive: true });
628
+ for (const repo of reposToClone) {
629
+ const spinner = ora(`Cloning ${repo.label}...`).start();
630
+ if (existsSync3(join3(repo.dest, ".git"))) {
631
+ spinner.succeed(`${repo.label} already cloned`);
632
+ continue;
633
+ }
634
+ try {
635
+ mkdirSync2(repo.dest, { recursive: true });
636
+ execSync(`git clone "${repo.url}" "${repo.dest}"`, {
637
+ stdio: "pipe",
638
+ timeout: 6e4
639
+ });
640
+ spinner.succeed(`${repo.label} cloned`);
641
+ } catch (error) {
642
+ spinner.fail(`Failed to clone ${repo.label}`);
643
+ const msg = error.message || "";
644
+ if (msg.includes("already exists and is not an empty directory")) {
645
+ console.log(chalk.dim(" Directory exists but has no .git \u2014 check the path."));
646
+ } else {
647
+ console.log(chalk.dim(` ${msg.split("\n")[0]}`));
648
+ }
649
+ console.log(chalk.dim(` URL: ${repo.url}`));
650
+ console.log(chalk.dim(" Ensure you have git access (SSH key or credential helper)."));
651
+ process.exit(1);
652
+ }
560
653
  }
561
654
  }
562
655
  console.log("");
@@ -812,6 +905,7 @@ var configCommand = new Command5("config").description("View or modify Horus con
812
905
  console.log(` ${chalk5.bold("data-dir:")} ${config.data_dir}`);
813
906
  console.log(` ${chalk5.bold("runtime:")} ${config.runtime}`);
814
907
  console.log(` ${chalk5.bold("host-repos-path:")} ${config.host_repos_path || chalk5.dim("(not set)")}`);
908
+ console.log(` ${chalk5.bold("git-host:")} ${config.git_host || chalk5.dim("(not set)")}`);
815
909
  console.log(` ${chalk5.bold("github-token:")} ${config.github_token ? maskApiKey(config.github_token) : chalk5.dim("(not set)")}`);
816
910
  console.log("");
817
911
  console.log(chalk5.bold(" Ports:"));
@@ -902,23 +996,23 @@ import { Command as Command6 } from "commander";
902
996
  import chalk6 from "chalk";
903
997
  import ora5 from "ora";
904
998
  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";
999
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
1000
+ import { join as join4 } from "path";
907
1001
  import { homedir as homedir3 } from "os";
908
1002
  function detectInstalledClients() {
909
1003
  const detected = [];
910
1004
  const home = homedir3();
911
- const claudeDesktopDir = join3(home, "Library", "Application Support", "Claude");
912
- if (existsSync3(claudeDesktopDir)) {
1005
+ const claudeDesktopDir = join4(home, "Library", "Application Support", "Claude");
1006
+ if (existsSync4(claudeDesktopDir)) {
913
1007
  detected.push("claude-desktop");
914
1008
  }
915
- const claudeCodeDir = join3(home, ".claude");
916
- if (existsSync3(claudeCodeDir)) {
1009
+ const claudeCodeDir = join4(home, ".claude");
1010
+ if (existsSync4(claudeCodeDir)) {
917
1011
  detected.push("claude-code");
918
1012
  }
919
- const cursorDir = join3(home, ".cursor");
920
- const cursorAppDir = join3(home, "Library", "Application Support", "Cursor");
921
- if (existsSync3(cursorDir) || existsSync3(cursorAppDir)) {
1013
+ const cursorDir = join4(home, ".cursor");
1014
+ const cursorAppDir = join4(home, "Library", "Application Support", "Cursor");
1015
+ if (existsSync4(cursorDir) || existsSync4(cursorAppDir)) {
922
1016
  detected.push("cursor");
923
1017
  }
924
1018
  return detected;
@@ -927,16 +1021,16 @@ function getConfigPath(target) {
927
1021
  const home = homedir3();
928
1022
  switch (target) {
929
1023
  case "claude-desktop":
930
- return join3(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1024
+ return join4(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
931
1025
  case "claude-code":
932
- return join3(home, ".claude", "settings.json");
1026
+ return join4(home, ".claude", "settings.json");
933
1027
  case "cursor":
934
- return join3(home, ".cursor", "mcp.json");
1028
+ return join4(home, ".cursor", "mcp.json");
935
1029
  }
936
1030
  }
937
1031
  function mergeAndWriteConfig(configPath, mcpServers) {
938
1032
  let existing = {};
939
- if (existsSync3(configPath)) {
1033
+ if (existsSync4(configPath)) {
940
1034
  try {
941
1035
  const raw = readFileSync3(configPath, "utf-8");
942
1036
  existing = JSON.parse(raw);
@@ -947,25 +1041,46 @@ function mergeAndWriteConfig(configPath, mcpServers) {
947
1041
  const existingServers = existing.mcpServers ?? {};
948
1042
  existing.mcpServers = { ...existingServers, ...mcpServers };
949
1043
  const dir = configPath.substring(0, configPath.lastIndexOf("/"));
950
- mkdirSync2(dir, { recursive: true });
1044
+ mkdirSync3(dir, { recursive: true });
951
1045
  writeFileSync3(configPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
952
1046
  }
953
1047
  async function syncSkills(runtime) {
954
1048
  const home = homedir3();
955
- const skillsBase = join3(home, ".claude", "skills");
1049
+ const skillsBase = join4(home, ".claude", "skills");
956
1050
  const skills = ["horus-anvil", "horus-vault", "horus-forge"];
957
1051
  const forgeContainer = "horus-forge-1";
958
1052
  for (const skill of skills) {
959
- const destDir = join3(skillsBase, skill);
960
- mkdirSync2(destDir, { recursive: true });
1053
+ const destDir = join4(skillsBase, skill);
1054
+ mkdirSync3(destDir, { recursive: true });
961
1055
  const src = `/home/forge/.claude/skills/${skill}/SKILL.md`;
962
- const dest = join3(destDir, "SKILL.md");
1056
+ const dest = join4(destDir, "SKILL.md");
963
1057
  const result = await runtime.exec(forgeContainer, "cat", src);
964
1058
  if (result.exitCode === 0 && result.stdout.trim()) {
965
1059
  writeFileSync3(dest, result.stdout, "utf-8");
966
1060
  }
967
1061
  }
968
1062
  }
1063
+ async function syncSkillsForCursor(runtime) {
1064
+ const home = homedir3();
1065
+ const rulesDir = join4(home, ".cursor", "rules");
1066
+ const skills = ["horus-anvil", "horus-vault", "horus-forge"];
1067
+ const forgeContainer = "horus-forge-1";
1068
+ mkdirSync3(rulesDir, { recursive: true });
1069
+ for (const skill of skills) {
1070
+ const src = `/home/forge/.claude/skills/${skill}/SKILL.md`;
1071
+ const dest = join4(rulesDir, `${skill}.mdc`);
1072
+ const result = await runtime.exec(forgeContainer, "cat", src);
1073
+ if (result.exitCode === 0 && result.stdout.trim()) {
1074
+ const frontmatter = `---
1075
+ description: Horus ${skill} reference
1076
+ alwaysApply: true
1077
+ ---
1078
+
1079
+ `;
1080
+ writeFileSync3(dest, frontmatter + result.stdout, "utf-8");
1081
+ }
1082
+ }
1083
+ }
969
1084
  function printNextSteps(targets) {
970
1085
  console.log("");
971
1086
  console.log(chalk6.bold("Next steps:"));
@@ -978,7 +1093,7 @@ function printNextSteps(targets) {
978
1093
  console.log(` ${chalk6.cyan("Claude Code")} Start a new Claude Code session`);
979
1094
  break;
980
1095
  case "cursor":
981
- console.log(` ${chalk6.cyan("Cursor")} Restart Cursor`);
1096
+ console.log(` ${chalk6.cyan("Cursor")} Restart Cursor to pick up the new MCP configuration and rules`);
982
1097
  break;
983
1098
  }
984
1099
  }
@@ -1069,6 +1184,16 @@ var connectCommand = new Command6("connect").description("Configure Claude/Curso
1069
1184
  console.log(chalk6.dim(error.message));
1070
1185
  }
1071
1186
  }
1187
+ if (targets.includes("cursor")) {
1188
+ const cursorRulesSpinner = ora5("Syncing horus-core rules for Cursor...").start();
1189
+ try {
1190
+ await syncSkillsForCursor(runtime);
1191
+ cursorRulesSpinner.succeed("horus-core rules synced to ~/.cursor/rules/");
1192
+ } catch (error) {
1193
+ cursorRulesSpinner.warn("Could not sync Cursor rules (Forge container may not be running)");
1194
+ console.log(chalk6.dim(error.message));
1195
+ }
1196
+ }
1072
1197
  printNextSteps(targets);
1073
1198
  });
1074
1199
 
@@ -1077,16 +1202,16 @@ import { Command as Command7 } from "commander";
1077
1202
  import chalk7 from "chalk";
1078
1203
  import ora6 from "ora";
1079
1204
  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";
1205
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readdirSync, existsSync as existsSync5 } from "fs";
1206
+ import { join as join5 } from "path";
1082
1207
  import { createHash } from "crypto";
1083
1208
  import { stringify as stringifyYaml2, parse as parseYaml2 } from "yaml";
1084
- var SNAPSHOTS_DIR = join4(HORUS_DIR, "snapshots");
1209
+ var SNAPSHOTS_DIR = join5(HORUS_DIR, "snapshots");
1085
1210
  function ensureSnapshotsDir() {
1086
- mkdirSync3(SNAPSHOTS_DIR, { recursive: true });
1211
+ mkdirSync4(SNAPSHOTS_DIR, { recursive: true });
1087
1212
  }
1088
1213
  function composeFileHash() {
1089
- if (!existsSync4(COMPOSE_PATH)) return "";
1214
+ if (!existsSync5(COMPOSE_PATH)) return "";
1090
1215
  const content = readFileSync4(COMPOSE_PATH, "utf-8");
1091
1216
  return createHash("sha256").update(content).digest("hex").slice(0, 12);
1092
1217
  }
@@ -1116,14 +1241,14 @@ function saveSnapshot(images) {
1116
1241
  images,
1117
1242
  compose_hash: composeFileHash()
1118
1243
  };
1119
- const filePath = join4(SNAPSHOTS_DIR, `${timestamp}.yaml`);
1244
+ const filePath = join5(SNAPSHOTS_DIR, `${timestamp}.yaml`);
1120
1245
  writeFileSync4(filePath, stringifyYaml2(snapshot, { lineWidth: 0 }), "utf-8");
1121
1246
  return filePath;
1122
1247
  }
1123
1248
  function listSnapshots() {
1124
- if (!existsSync4(SNAPSHOTS_DIR)) return [];
1249
+ if (!existsSync5(SNAPSHOTS_DIR)) return [];
1125
1250
  return readdirSync(SNAPSHOTS_DIR).filter((f) => f.endsWith(".yaml")).sort().reverse().map((f) => {
1126
- const file = join4(SNAPSHOTS_DIR, f);
1251
+ const file = join5(SNAPSHOTS_DIR, f);
1127
1252
  const snapshot = parseYaml2(readFileSync4(file, "utf-8"));
1128
1253
  return { file, snapshot };
1129
1254
  });
@@ -1245,6 +1370,10 @@ var updateCommand = new Command7("update").description("Update Horus to the late
1245
1370
  console.log(chalk7.dim(" Could not reach GitHub to check latest version."));
1246
1371
  }
1247
1372
  console.log("");
1373
+ console.log(chalk7.dim(" Note: this updates the Horus container services only."));
1374
+ console.log(chalk7.dim(" To update the Horus CLI itself, run:"));
1375
+ console.log(` ${chalk7.cyan("npm install -g @arkhera30/cli@latest")}`);
1376
+ console.log("");
1248
1377
  if (!opts.yes) {
1249
1378
  const confirmed = await confirm3({
1250
1379
  message: "Pull latest images and restart services?",
@@ -1264,16 +1393,6 @@ var updateCommand = new Command7("update").description("Update Horus to the late
1264
1393
  snapshotSpinner.warn("Could not save snapshot (update will proceed)");
1265
1394
  console.log(chalk7.dim(error.message));
1266
1395
  }
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
1396
  console.log("");
1278
1397
  console.log(chalk7.bold("Pulling latest images..."));
1279
1398
  try {
@@ -1338,9 +1457,9 @@ var updateCommand = new Command7("update").description("Update Horus to the late
1338
1457
  // src/commands/doctor.ts
1339
1458
  import { Command as Command8 } from "commander";
1340
1459
  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";
1460
+ import { execSync as execSync2 } from "child_process";
1461
+ import { existsSync as existsSync6, accessSync, statfsSync, constants } from "fs";
1462
+ import { join as join6 } from "path";
1344
1463
  function symbol(status) {
1345
1464
  switch (status) {
1346
1465
  case "pass":
@@ -1363,11 +1482,11 @@ function colorMessage(status, msg) {
1363
1482
  }
1364
1483
  async function checkRuntime2() {
1365
1484
  try {
1366
- execSync("docker info", { stdio: "ignore" });
1485
+ execSync2("docker info", { stdio: "ignore" });
1367
1486
  return { status: "pass", label: "Runtime", message: "Docker is running" };
1368
1487
  } catch {
1369
1488
  try {
1370
- execSync("podman info", { stdio: "ignore" });
1489
+ execSync2("podman info", { stdio: "ignore" });
1371
1490
  return { status: "pass", label: "Runtime", message: "Podman is running" };
1372
1491
  } catch {
1373
1492
  return {
@@ -1381,11 +1500,11 @@ async function checkRuntime2() {
1381
1500
  }
1382
1501
  async function checkCompose() {
1383
1502
  try {
1384
- execSync("docker compose version", { stdio: "ignore" });
1503
+ execSync2("docker compose version", { stdio: "ignore" });
1385
1504
  return { status: "pass", label: "Compose", message: "Compose plugin available" };
1386
1505
  } catch {
1387
1506
  try {
1388
- execSync("podman compose version", { stdio: "ignore" });
1507
+ execSync2("podman compose version", { stdio: "ignore" });
1389
1508
  return { status: "pass", label: "Compose", message: "Compose plugin available (podman)" };
1390
1509
  } catch {
1391
1510
  return {
@@ -1409,7 +1528,7 @@ function checkConfig() {
1409
1528
  };
1410
1529
  }
1411
1530
  function checkComposeFile() {
1412
- if (existsSync5(COMPOSE_PATH)) {
1531
+ if (existsSync6(COMPOSE_PATH)) {
1413
1532
  return { status: "pass", label: "Compose file", message: "Compose file installed (~/.horus/docker-compose.yml)" };
1414
1533
  }
1415
1534
  return {
@@ -1421,7 +1540,7 @@ function checkComposeFile() {
1421
1540
  }
1422
1541
  function checkPort(port, serviceName) {
1423
1542
  try {
1424
- const output = execSync(`lsof -i :${port} -sTCP:LISTEN -t 2>/dev/null || true`, {
1543
+ const output = execSync2(`lsof -i :${port} -sTCP:LISTEN -t 2>/dev/null || true`, {
1425
1544
  encoding: "utf-8"
1426
1545
  }).trim();
1427
1546
  if (!output) {
@@ -1430,7 +1549,7 @@ function checkPort(port, serviceName) {
1430
1549
  const pids = output.split("\n").filter(Boolean);
1431
1550
  for (const pid of pids) {
1432
1551
  try {
1433
- const cmdline = execSync(`ps -p ${pid} -o comm= 2>/dev/null || true`, {
1552
+ const cmdline = execSync2(`ps -p ${pid} -o comm= 2>/dev/null || true`, {
1434
1553
  encoding: "utf-8"
1435
1554
  }).trim();
1436
1555
  if (cmdline.toLowerCase().includes("docker") || cmdline.toLowerCase().includes("podman")) {
@@ -1450,7 +1569,7 @@ function checkPort(port, serviceName) {
1450
1569
  }
1451
1570
  }
1452
1571
  function checkDataDir(dataDir) {
1453
- if (!existsSync5(dataDir)) {
1572
+ if (!existsSync6(dataDir)) {
1454
1573
  return {
1455
1574
  status: "warn",
1456
1575
  label: "Data directory",
@@ -1471,7 +1590,7 @@ function checkDataDir(dataDir) {
1471
1590
  }
1472
1591
  }
1473
1592
  function checkDiskSpace(dataDir) {
1474
- const checkDir = existsSync5(dataDir) ? dataDir : join5(dataDir, "..");
1593
+ const checkDir = existsSync6(dataDir) ? dataDir : join6(dataDir, "..");
1475
1594
  try {
1476
1595
  const stats = statfsSync(checkDir);
1477
1596
  const freeBytes = stats.bfree * stats.bsize;
@@ -1555,7 +1674,7 @@ var doctorCommand = new Command8("doctor").description("Diagnose common Horus is
1555
1674
  allResults.push(checkComposeFile());
1556
1675
  const config = configExists() ? loadConfig() : null;
1557
1676
  const ports = config?.ports ?? DEFAULT_PORTS;
1558
- const dataDir = config?.data_dir ?? join5(process.env.HOME ?? "~", ".horus", "data");
1677
+ const dataDir = config?.data_dir ?? join6(process.env.HOME ?? "~", ".horus", "data");
1559
1678
  allResults.push(checkPort(ports.anvil, "Anvil"));
1560
1679
  allResults.push(checkPort(ports.vault_rest, "Vault"));
1561
1680
  allResults.push(checkPort(ports.vault_mcp, "Vault MCP"));
@@ -1610,13 +1729,13 @@ import { Command as Command9 } from "commander";
1610
1729
  import chalk9 from "chalk";
1611
1730
  import ora7 from "ora";
1612
1731
  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";
1732
+ import { mkdirSync as mkdirSync5, statSync, existsSync as existsSync7, writeFileSync as writeFileSync5 } from "fs";
1733
+ import { join as join7, basename } from "path";
1734
+ import { execSync as execSync3 } from "child_process";
1616
1735
  import { stringify as stringifyYaml3 } from "yaml";
1617
- var BACKUPS_DIR = join6(HORUS_DIR, "backups");
1736
+ var BACKUPS_DIR = join7(HORUS_DIR, "backups");
1618
1737
  function ensureBackupsDir() {
1619
- mkdirSync4(BACKUPS_DIR, { recursive: true });
1738
+ mkdirSync5(BACKUPS_DIR, { recursive: true });
1620
1739
  }
1621
1740
  function formatBytes(bytes) {
1622
1741
  if (bytes < 1024) return `${bytes}B`;
@@ -1661,11 +1780,11 @@ async function createBackup(yes) {
1661
1780
  }
1662
1781
  ensureBackupsDir();
1663
1782
  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`);
1783
+ const tarFile = join7(BACKUPS_DIR, `${timestamp}.tar.gz`);
1784
+ const metaFile = join7(BACKUPS_DIR, `${timestamp}.meta.yaml`);
1666
1785
  const backupSpinner = ora7("Creating backup archive...").start();
1667
1786
  try {
1668
- execSync2(`tar -czf "${tarFile}" -C "${HORUS_DIR}" data/`, {
1787
+ execSync3(`tar -czf "${tarFile}" -C "${HORUS_DIR}" data/`, {
1669
1788
  stdio: "pipe"
1670
1789
  });
1671
1790
  backupSpinner.succeed(`Archive created: ${chalk9.dim(tarFile)}`);
@@ -1711,7 +1830,7 @@ async function restoreBackup(file, yes) {
1711
1830
  console.log(chalk9.bold("Horus Restore"));
1712
1831
  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
1832
  console.log("");
1714
- if (!existsSync6(file)) {
1833
+ if (!existsSync7(file)) {
1715
1834
  console.log(chalk9.red(`Backup file not found: ${file}`));
1716
1835
  process.exit(1);
1717
1836
  }
@@ -1749,7 +1868,7 @@ async function restoreBackup(file, yes) {
1749
1868
  }
1750
1869
  const extractSpinner = ora7("Extracting backup...").start();
1751
1870
  try {
1752
- execSync2(`tar -xzf "${file}" -C "${HORUS_DIR}/"`, { stdio: "pipe" });
1871
+ execSync3(`tar -xzf "${file}" -C "${HORUS_DIR}/"`, { stdio: "pipe" });
1753
1872
  extractSpinner.succeed("Backup extracted");
1754
1873
  } catch (error) {
1755
1874
  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.7",
4
4
  "description": "CLI for managing the Horus AI development stack",
5
5
  "type": "module",
6
6
  "bin": {