@arkhera30/cli 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +156 -81
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,14 +15,20 @@ import { join as join4 } from "path";
|
|
|
15
15
|
import { input, confirm, number, select, password } from "@inquirer/prompts";
|
|
16
16
|
|
|
17
17
|
// src/lib/config.ts
|
|
18
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
19
|
-
import { resolve } from "path";
|
|
18
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "fs";
|
|
19
|
+
import { resolve, join as pathJoin, relative } from "path";
|
|
20
20
|
import { homedir as homedir2 } from "os";
|
|
21
21
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
22
22
|
|
|
23
23
|
// src/lib/constants.ts
|
|
24
24
|
import { homedir } from "os";
|
|
25
|
-
import { join } from "path";
|
|
25
|
+
import { join, dirname } from "path";
|
|
26
|
+
import { readFileSync } from "fs";
|
|
27
|
+
import { fileURLToPath } from "url";
|
|
28
|
+
var __pkg_dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
var pkgPath = join(__pkg_dirname, "..", "..", "package.json");
|
|
30
|
+
var pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
31
|
+
var CLI_VERSION = pkg.version;
|
|
26
32
|
var HORUS_DIR = join(homedir(), ".horus");
|
|
27
33
|
var CONFIG_PATH = join(HORUS_DIR, "config.yaml");
|
|
28
34
|
var ENV_PATH = join(HORUS_DIR, ".env");
|
|
@@ -72,7 +78,7 @@ function loadConfig() {
|
|
|
72
78
|
if (!existsSync(CONFIG_PATH)) {
|
|
73
79
|
return defaultConfig();
|
|
74
80
|
}
|
|
75
|
-
const raw =
|
|
81
|
+
const raw = readFileSync2(CONFIG_PATH, "utf-8");
|
|
76
82
|
const parsed = parseYaml(raw);
|
|
77
83
|
const defaults = defaultConfig();
|
|
78
84
|
return {
|
|
@@ -107,12 +113,54 @@ function resolvePath(p) {
|
|
|
107
113
|
}
|
|
108
114
|
return resolve(p);
|
|
109
115
|
}
|
|
116
|
+
function discoverRepoDirs(rootDir, maxDepth = 4) {
|
|
117
|
+
const repoDirs = /* @__PURE__ */ new Set();
|
|
118
|
+
function walk(dir, depth) {
|
|
119
|
+
if (depth > maxDepth) return;
|
|
120
|
+
let entries;
|
|
121
|
+
try {
|
|
122
|
+
entries = readdirSync(dir);
|
|
123
|
+
} catch {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (entry === "node_modules" || entry === ".git") continue;
|
|
128
|
+
const full = pathJoin(dir, entry);
|
|
129
|
+
try {
|
|
130
|
+
if (!statSync(full).isDirectory()) continue;
|
|
131
|
+
} catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (existsSync(pathJoin(full, ".git"))) {
|
|
135
|
+
repoDirs.add(dir);
|
|
136
|
+
}
|
|
137
|
+
walk(full, depth + 1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (existsSync(rootDir)) {
|
|
141
|
+
walk(rootDir, 0);
|
|
142
|
+
}
|
|
143
|
+
return [...repoDirs];
|
|
144
|
+
}
|
|
110
145
|
function generateEnv(config) {
|
|
111
146
|
const dataDir = resolvePath(config.data_dir);
|
|
112
147
|
const hostReposPath = config.host_repos_path ? resolvePath(config.host_repos_path) : "";
|
|
113
148
|
const baseScanPath = "/data/repos";
|
|
114
|
-
|
|
115
|
-
|
|
149
|
+
let forgeScanPaths;
|
|
150
|
+
if (hostReposPath) {
|
|
151
|
+
const discoveredDirs = discoverRepoDirs(hostReposPath);
|
|
152
|
+
const containerPaths = discoveredDirs.map((dir) => {
|
|
153
|
+
const rel = relative(hostReposPath, dir);
|
|
154
|
+
return rel ? `${baseScanPath}/${rel}` : baseScanPath;
|
|
155
|
+
});
|
|
156
|
+
const allPaths = [baseScanPath, ...containerPaths];
|
|
157
|
+
const extraScanPaths = (config.host_repos_extra_scan_dirs ?? []).map((d) => d.trim()).filter(Boolean).map((d) => `${baseScanPath}/${d}`);
|
|
158
|
+
const uniquePaths = [.../* @__PURE__ */ new Set([...allPaths, ...extraScanPaths])];
|
|
159
|
+
forgeScanPaths = uniquePaths.join(":");
|
|
160
|
+
} else {
|
|
161
|
+
const extraScanPaths = (config.host_repos_extra_scan_dirs ?? []).map((d) => d.trim()).filter(Boolean).map((d) => `${baseScanPath}/${d}`);
|
|
162
|
+
forgeScanPaths = [baseScanPath, ...extraScanPaths].join(":");
|
|
163
|
+
}
|
|
116
164
|
const lines = [
|
|
117
165
|
"# \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\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\u2500",
|
|
118
166
|
"# Horus \u2014 Generated .env file",
|
|
@@ -299,6 +347,9 @@ function createRuntime(name) {
|
|
|
299
347
|
const result = await execa(bin, ["inspect", "--format", format, container], {
|
|
300
348
|
reject: false
|
|
301
349
|
});
|
|
350
|
+
if (result.exitCode !== 0) {
|
|
351
|
+
throw new Error(`inspect failed: ${result.stderr}`);
|
|
352
|
+
}
|
|
302
353
|
return result.stdout?.toString().trim() ?? "";
|
|
303
354
|
},
|
|
304
355
|
async isRunning() {
|
|
@@ -315,6 +366,24 @@ function createRuntime(name) {
|
|
|
315
366
|
}
|
|
316
367
|
};
|
|
317
368
|
}
|
|
369
|
+
function parseComposeJson(output) {
|
|
370
|
+
const trimmed = output.trim();
|
|
371
|
+
if (!trimmed) return [];
|
|
372
|
+
if (trimmed.startsWith("[")) {
|
|
373
|
+
try {
|
|
374
|
+
const parsed = JSON.parse(trimmed);
|
|
375
|
+
if (Array.isArray(parsed)) return parsed;
|
|
376
|
+
} catch {
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return trimmed.split("\n").filter((line) => line.trim()).map((line) => {
|
|
380
|
+
try {
|
|
381
|
+
return JSON.parse(line);
|
|
382
|
+
} catch {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}).filter((item) => item !== null);
|
|
386
|
+
}
|
|
318
387
|
async function checkRuntime(name) {
|
|
319
388
|
return tryCommand(name, ["compose", "version"]);
|
|
320
389
|
}
|
|
@@ -358,14 +427,22 @@ async function composeStreaming(runtime, args) {
|
|
|
358
427
|
|
|
359
428
|
// src/lib/health.ts
|
|
360
429
|
async function checkContainerHealth(runtime, service) {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
430
|
+
const candidates = [`horus-${service}-1`, `horus_${service}_1`];
|
|
431
|
+
for (const containerName of candidates) {
|
|
432
|
+
try {
|
|
433
|
+
const healthStatus = await runtime.inspect(containerName, "{{.State.Health.Status}}");
|
|
434
|
+
if (healthStatus && !healthStatus.includes("<nil>") && healthStatus.trim() !== "") {
|
|
435
|
+
return { name: service, status: mapStatus(healthStatus) };
|
|
436
|
+
}
|
|
437
|
+
const stateStatus = await runtime.inspect(containerName, "{{.State.Status}}");
|
|
438
|
+
if (stateStatus && stateStatus.trim() !== "") {
|
|
439
|
+
return { name: service, status: mapStateStatus(stateStatus) };
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
368
444
|
}
|
|
445
|
+
return { name: service, status: "stopped" };
|
|
369
446
|
}
|
|
370
447
|
function mapStatus(raw) {
|
|
371
448
|
switch (raw.trim().toLowerCase()) {
|
|
@@ -379,13 +456,28 @@ function mapStatus(raw) {
|
|
|
379
456
|
return "unknown";
|
|
380
457
|
}
|
|
381
458
|
}
|
|
459
|
+
function mapStateStatus(raw) {
|
|
460
|
+
switch (raw.trim().toLowerCase()) {
|
|
461
|
+
case "running":
|
|
462
|
+
return "healthy";
|
|
463
|
+
case "created":
|
|
464
|
+
case "restarting":
|
|
465
|
+
return "starting";
|
|
466
|
+
case "exited":
|
|
467
|
+
case "dead":
|
|
468
|
+
case "removing":
|
|
469
|
+
return "unhealthy";
|
|
470
|
+
default:
|
|
471
|
+
return "unknown";
|
|
472
|
+
}
|
|
473
|
+
}
|
|
382
474
|
async function checkAllHealth(runtime) {
|
|
383
475
|
const results = await Promise.all(
|
|
384
476
|
SERVICES.map((service) => checkContainerHealth(runtime, service))
|
|
385
477
|
);
|
|
386
478
|
return results;
|
|
387
479
|
}
|
|
388
|
-
async function pollUntilHealthy(runtime, onUpdate, timeoutMs =
|
|
480
|
+
async function pollUntilHealthy(runtime, onUpdate, timeoutMs = 3e5, intervalMs = 5e3) {
|
|
389
481
|
const startTime = Date.now();
|
|
390
482
|
while (true) {
|
|
391
483
|
const states = await checkAllHealth(runtime);
|
|
@@ -401,7 +493,7 @@ async function pollUntilHealthy(runtime, onUpdate, timeoutMs = 6e5, intervalMs =
|
|
|
401
493
|
const unhealthyServices = states.filter((s) => s.status === "unhealthy").map((s) => s.name).join(", ");
|
|
402
494
|
throw new Error(
|
|
403
495
|
`Services failed health check: ${unhealthyServices}
|
|
404
|
-
Run '
|
|
496
|
+
Run '${runtime.name} compose logs <service>' from ~/.horus/ to investigate.`
|
|
405
497
|
);
|
|
406
498
|
}
|
|
407
499
|
const elapsed = Date.now() - startTime;
|
|
@@ -409,7 +501,7 @@ Run 'docker compose logs <service>' from ~/.horus/ to investigate.`
|
|
|
409
501
|
const notReady = states.filter((s) => s.status !== "healthy").map((s) => `${s.name} (${s.status})`).join(", ");
|
|
410
502
|
throw new Error(
|
|
411
503
|
`Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for services: ${notReady}
|
|
412
|
-
Run '
|
|
504
|
+
Run '${runtime.name} compose logs' from ~/.horus/ to investigate.`
|
|
413
505
|
);
|
|
414
506
|
}
|
|
415
507
|
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
@@ -417,11 +509,11 @@ Run 'docker compose logs' from ~/.horus/ to investigate.`
|
|
|
417
509
|
}
|
|
418
510
|
|
|
419
511
|
// src/lib/compose.ts
|
|
420
|
-
import { readFileSync as
|
|
421
|
-
import { join as join2, dirname } from "path";
|
|
422
|
-
import { fileURLToPath } from "url";
|
|
423
|
-
var __filename =
|
|
424
|
-
var __dirname =
|
|
512
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
513
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
514
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
515
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
516
|
+
var __dirname = dirname2(__filename);
|
|
425
517
|
function getBundledComposePath() {
|
|
426
518
|
const candidates = [
|
|
427
519
|
join2(__dirname, "..", "..", "compose", "docker-compose.yml"),
|
|
@@ -449,7 +541,7 @@ function composeFileExists() {
|
|
|
449
541
|
function installComposeFile(runtime) {
|
|
450
542
|
ensureHorusDir();
|
|
451
543
|
const bundledPath = getBundledComposePath();
|
|
452
|
-
let content =
|
|
544
|
+
let content = readFileSync3(bundledPath, "utf-8");
|
|
453
545
|
if (runtime === "podman") {
|
|
454
546
|
content = applyPodmanUserOverride(content);
|
|
455
547
|
}
|
|
@@ -461,7 +553,7 @@ import { Command } from "commander";
|
|
|
461
553
|
import chalk from "chalk";
|
|
462
554
|
import ora from "ora";
|
|
463
555
|
import { checkbox } from "@inquirer/prompts";
|
|
464
|
-
import { readFileSync as
|
|
556
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
465
557
|
import { join as join3 } from "path";
|
|
466
558
|
import { homedir as homedir3 } from "os";
|
|
467
559
|
import { execa as execa2 } from "execa";
|
|
@@ -498,7 +590,7 @@ function mergeAndWriteConfig(configPath, mcpServers) {
|
|
|
498
590
|
let existing = {};
|
|
499
591
|
if (existsSync3(configPath)) {
|
|
500
592
|
try {
|
|
501
|
-
const raw =
|
|
593
|
+
const raw = readFileSync4(configPath, "utf-8");
|
|
502
594
|
existing = JSON.parse(raw);
|
|
503
595
|
} catch {
|
|
504
596
|
existing = {};
|
|
@@ -821,11 +913,7 @@ var setupCommand = new Command2("setup").description("Interactive first-run setu
|
|
|
821
913
|
message: "Host repos path (for Forge repo scanning, leave empty to skip):",
|
|
822
914
|
default: ""
|
|
823
915
|
});
|
|
824
|
-
const
|
|
825
|
-
message: "Extra subdirectories to scan within repos path (comma-separated, e.g. ArjunKhera \u2014 leave empty to skip):",
|
|
826
|
-
default: ""
|
|
827
|
-
});
|
|
828
|
-
const host_repos_extra_scan_dirs = extra_scan_dirs_raw.split(",").map((d) => d.trim()).filter(Boolean);
|
|
916
|
+
const host_repos_extra_scan_dirs = [];
|
|
829
917
|
const customize_ports = await confirm({
|
|
830
918
|
message: "Customize port assignments?",
|
|
831
919
|
default: false
|
|
@@ -939,7 +1027,7 @@ ${example("forge-registry")}
|
|
|
939
1027
|
console.error(error.message);
|
|
940
1028
|
process.exit(1);
|
|
941
1029
|
}
|
|
942
|
-
const dataDir =
|
|
1030
|
+
const dataDir = resolvePath(config.data_dir);
|
|
943
1031
|
const reposToClone = [
|
|
944
1032
|
{ url: config.repos.anvil_notes, dest: join4(dataDir, "notes"), label: "Anvil notes" },
|
|
945
1033
|
{ url: config.repos.vault_knowledge, dest: join4(dataDir, "knowledge-base"), label: "Vault knowledge-base" },
|
|
@@ -1173,17 +1261,14 @@ var statusCommand = new Command5("status").description("Show status of Horus ser
|
|
|
1173
1261
|
let containers = [];
|
|
1174
1262
|
try {
|
|
1175
1263
|
const result = await runtime.compose("ps", "--format", "json");
|
|
1176
|
-
|
|
1177
|
-
if (output) {
|
|
1178
|
-
containers = output.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
1179
|
-
}
|
|
1264
|
+
containers = parseComposeJson(result.stdout);
|
|
1180
1265
|
} catch {
|
|
1181
1266
|
}
|
|
1182
1267
|
spinner.stop();
|
|
1183
1268
|
console.log("");
|
|
1184
1269
|
console.log(chalk5.bold("Horus Status"));
|
|
1185
1270
|
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"));
|
|
1186
|
-
console.log(` ${chalk5.bold("Version:")} ${
|
|
1271
|
+
console.log(` ${chalk5.bold("Version:")} ${CLI_VERSION}`);
|
|
1187
1272
|
console.log(` ${chalk5.bold("Runtime:")} ${runtime.name}`);
|
|
1188
1273
|
console.log(` ${chalk5.bold("Config:")} ~/.horus/config.yaml`);
|
|
1189
1274
|
console.log("");
|
|
@@ -1348,7 +1433,7 @@ import { Command as Command7 } from "commander";
|
|
|
1348
1433
|
import chalk7 from "chalk";
|
|
1349
1434
|
import ora6 from "ora";
|
|
1350
1435
|
import { select as select2, confirm as confirm3 } from "@inquirer/prompts";
|
|
1351
|
-
import { readFileSync as
|
|
1436
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, existsSync as existsSync5 } from "fs";
|
|
1352
1437
|
import { join as join5 } from "path";
|
|
1353
1438
|
import { createHash } from "crypto";
|
|
1354
1439
|
import { stringify as stringifyYaml2, parse as parseYaml2 } from "yaml";
|
|
@@ -1358,7 +1443,7 @@ function ensureSnapshotsDir() {
|
|
|
1358
1443
|
}
|
|
1359
1444
|
function composeFileHash() {
|
|
1360
1445
|
if (!existsSync5(COMPOSE_PATH)) return "";
|
|
1361
|
-
const content =
|
|
1446
|
+
const content = readFileSync5(COMPOSE_PATH, "utf-8");
|
|
1362
1447
|
return createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
1363
1448
|
}
|
|
1364
1449
|
async function captureCurrentImages(runtime) {
|
|
@@ -1393,9 +1478,9 @@ function saveSnapshot(images) {
|
|
|
1393
1478
|
}
|
|
1394
1479
|
function listSnapshots() {
|
|
1395
1480
|
if (!existsSync5(SNAPSHOTS_DIR)) return [];
|
|
1396
|
-
return
|
|
1481
|
+
return readdirSync2(SNAPSHOTS_DIR).filter((f) => f.endsWith(".yaml")).sort().reverse().map((f) => {
|
|
1397
1482
|
const file = join5(SNAPSHOTS_DIR, f);
|
|
1398
|
-
const snapshot = parseYaml2(
|
|
1483
|
+
const snapshot = parseYaml2(readFileSync5(file, "utf-8"));
|
|
1399
1484
|
return { file, snapshot };
|
|
1400
1485
|
});
|
|
1401
1486
|
}
|
|
@@ -1626,41 +1711,38 @@ function colorMessage(status, msg) {
|
|
|
1626
1711
|
return chalk8.red(msg);
|
|
1627
1712
|
}
|
|
1628
1713
|
}
|
|
1629
|
-
async function
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
return { status: "pass", label: "Runtime", message: "Docker is running" };
|
|
1633
|
-
} catch {
|
|
1714
|
+
async function checkRuntimeAvailability(preferred) {
|
|
1715
|
+
const order = preferred === "podman" ? ["podman", "docker"] : ["docker", "podman"];
|
|
1716
|
+
for (const rt of order) {
|
|
1634
1717
|
try {
|
|
1635
|
-
execSync2(
|
|
1636
|
-
return { status: "pass", label: "Runtime", message: "Podman is running
|
|
1718
|
+
execSync2(`${rt} info`, { stdio: "ignore" });
|
|
1719
|
+
return { status: "pass", label: "Runtime", message: `${rt === "docker" ? "Docker" : "Podman"} is running` };
|
|
1637
1720
|
} catch {
|
|
1638
|
-
return {
|
|
1639
|
-
status: "fail",
|
|
1640
|
-
label: "Runtime",
|
|
1641
|
-
message: "Docker/Podman is not running",
|
|
1642
|
-
hint: "Start Docker Desktop or Podman Desktop"
|
|
1643
|
-
};
|
|
1644
1721
|
}
|
|
1645
1722
|
}
|
|
1723
|
+
return {
|
|
1724
|
+
status: "fail",
|
|
1725
|
+
label: "Runtime",
|
|
1726
|
+
message: "Docker/Podman is not running",
|
|
1727
|
+
hint: "Start Docker Desktop or Podman Desktop"
|
|
1728
|
+
};
|
|
1646
1729
|
}
|
|
1647
|
-
async function checkCompose() {
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
return { status: "pass", label: "Compose", message: "Compose plugin available" };
|
|
1651
|
-
} catch {
|
|
1730
|
+
async function checkCompose(preferred) {
|
|
1731
|
+
const order = preferred === "podman" ? ["podman", "docker"] : ["docker", "podman"];
|
|
1732
|
+
for (const rt of order) {
|
|
1652
1733
|
try {
|
|
1653
|
-
execSync2(
|
|
1654
|
-
|
|
1734
|
+
execSync2(`${rt} compose version`, { stdio: "ignore" });
|
|
1735
|
+
const label = rt === "podman" ? "Compose plugin available (podman)" : "Compose plugin available";
|
|
1736
|
+
return { status: "pass", label: "Compose", message: label };
|
|
1655
1737
|
} catch {
|
|
1656
|
-
return {
|
|
1657
|
-
status: "fail",
|
|
1658
|
-
label: "Compose",
|
|
1659
|
-
message: "Compose plugin not found",
|
|
1660
|
-
hint: "Install Docker Compose plugin: https://docs.docker.com/compose/install/"
|
|
1661
|
-
};
|
|
1662
1738
|
}
|
|
1663
1739
|
}
|
|
1740
|
+
return {
|
|
1741
|
+
status: "fail",
|
|
1742
|
+
label: "Compose",
|
|
1743
|
+
message: "Compose plugin not found",
|
|
1744
|
+
hint: "Install Docker Compose plugin or podman-compose"
|
|
1745
|
+
};
|
|
1664
1746
|
}
|
|
1665
1747
|
function checkConfig() {
|
|
1666
1748
|
if (configExists()) {
|
|
@@ -1760,8 +1842,8 @@ async function checkServices(runtime) {
|
|
|
1760
1842
|
const results = [];
|
|
1761
1843
|
try {
|
|
1762
1844
|
const psResult = await runtime.compose("ps", "--format", "json");
|
|
1763
|
-
const
|
|
1764
|
-
if (
|
|
1845
|
+
const containers = parseComposeJson(psResult.stdout);
|
|
1846
|
+
if (containers.length === 0) {
|
|
1765
1847
|
return [
|
|
1766
1848
|
{
|
|
1767
1849
|
status: "warn",
|
|
@@ -1771,17 +1853,10 @@ async function checkServices(runtime) {
|
|
|
1771
1853
|
}
|
|
1772
1854
|
];
|
|
1773
1855
|
}
|
|
1774
|
-
const containers = lines.map((l) => {
|
|
1775
|
-
try {
|
|
1776
|
-
return JSON.parse(l);
|
|
1777
|
-
} catch {
|
|
1778
|
-
return null;
|
|
1779
|
-
}
|
|
1780
|
-
}).filter((c) => c !== null);
|
|
1781
1856
|
for (const c of containers) {
|
|
1782
|
-
const name = c.Service ?? "unknown";
|
|
1857
|
+
const name = c.Service ?? c.Name ?? "unknown";
|
|
1783
1858
|
const health = (c.Health || c.State || "unknown").toLowerCase();
|
|
1784
|
-
if (health === "healthy" || health === "running") {
|
|
1859
|
+
if (health === "healthy" || health === "running" || health === "up") {
|
|
1785
1860
|
results.push({ status: "pass", label: `Service: ${name}`, message: `${name} is ${health}` });
|
|
1786
1861
|
} else if (health === "starting") {
|
|
1787
1862
|
results.push({
|
|
@@ -1814,11 +1889,11 @@ var doctorCommand = new Command8("doctor").description("Diagnose common Horus is
|
|
|
1814
1889
|
console.log(chalk8.bold("Horus Doctor"));
|
|
1815
1890
|
console.log(chalk8.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"));
|
|
1816
1891
|
const allResults = [];
|
|
1817
|
-
|
|
1818
|
-
allResults.push(await
|
|
1892
|
+
const config = configExists() ? loadConfig() : null;
|
|
1893
|
+
allResults.push(await checkRuntimeAvailability(config?.runtime));
|
|
1894
|
+
allResults.push(await checkCompose(config?.runtime));
|
|
1819
1895
|
allResults.push(checkConfig());
|
|
1820
1896
|
allResults.push(checkComposeFile());
|
|
1821
|
-
const config = configExists() ? loadConfig() : null;
|
|
1822
1897
|
const ports = config?.ports ?? DEFAULT_PORTS;
|
|
1823
1898
|
const dataDir = config?.data_dir ?? join6(process.env.HOME ?? "~", ".horus", "data");
|
|
1824
1899
|
allResults.push(checkPort(ports.anvil, "Anvil"));
|
|
@@ -1875,7 +1950,7 @@ import { Command as Command9 } from "commander";
|
|
|
1875
1950
|
import chalk9 from "chalk";
|
|
1876
1951
|
import ora7 from "ora";
|
|
1877
1952
|
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
1878
|
-
import { mkdirSync as mkdirSync5, statSync, existsSync as existsSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
1953
|
+
import { mkdirSync as mkdirSync5, statSync as statSync2, existsSync as existsSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
1879
1954
|
import { join as join7, basename } from "path";
|
|
1880
1955
|
import { execSync as execSync3 } from "child_process";
|
|
1881
1956
|
import { stringify as stringifyYaml3 } from "yaml";
|
|
@@ -1943,7 +2018,7 @@ async function createBackup(yes) {
|
|
|
1943
2018
|
}
|
|
1944
2019
|
let sizeBytes = 0;
|
|
1945
2020
|
try {
|
|
1946
|
-
sizeBytes =
|
|
2021
|
+
sizeBytes = statSync2(tarFile).size;
|
|
1947
2022
|
} catch {
|
|
1948
2023
|
}
|
|
1949
2024
|
const meta = {
|