@merittdev/horus 0.1.12 → 0.1.13
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.cjs +554 -143
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50314,7 +50314,7 @@ init_cjs_shims();
|
|
|
50314
50314
|
|
|
50315
50315
|
// ../../packages/core/src/version.ts
|
|
50316
50316
|
init_cjs_shims();
|
|
50317
|
-
var HORUS_VERSION = true ? "0.1.
|
|
50317
|
+
var HORUS_VERSION = true ? "0.1.13" : "dev";
|
|
50318
50318
|
var PINNED_AXON_VERSION = "1.0.7";
|
|
50319
50319
|
var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
|
|
50320
50320
|
|
|
@@ -54699,6 +54699,19 @@ var modelsSchema = external_exports.object({
|
|
|
54699
54699
|
reasoning: external_exports.string().default("claude-opus-4-8"),
|
|
54700
54700
|
extraction: external_exports.string().default("claude-haiku-4-5")
|
|
54701
54701
|
});
|
|
54702
|
+
var AI_PROVIDERS = ["anthropic", "claude", "codex", "gemini", "kimi", "cursor"];
|
|
54703
|
+
var aiSchema = external_exports.object({
|
|
54704
|
+
/** Preferred provider for AI narrative. */
|
|
54705
|
+
provider: external_exports.enum(AI_PROVIDERS).optional(),
|
|
54706
|
+
anthropic: external_exports.object({
|
|
54707
|
+
/** Direct API key (takes priority over apiKeyEnv). Stored redacted in display. */
|
|
54708
|
+
apiKey: external_exports.string().optional(),
|
|
54709
|
+
/** Env var holding the key. Defaults to ANTHROPIC_API_KEY. */
|
|
54710
|
+
apiKeyEnv: external_exports.string().optional(),
|
|
54711
|
+
/** Default model for narrative generation. */
|
|
54712
|
+
model: external_exports.string().optional()
|
|
54713
|
+
}).optional()
|
|
54714
|
+
}).optional();
|
|
54702
54715
|
var horusConfigSchema = external_exports.object({
|
|
54703
54716
|
projects: external_exports.array(projectSchema).default([]),
|
|
54704
54717
|
axon: external_exports.object({
|
|
@@ -54706,8 +54719,19 @@ var horusConfigSchema = external_exports.object({
|
|
|
54706
54719
|
pinnedVersion: external_exports.string().default("1.0.7")
|
|
54707
54720
|
}).default({}),
|
|
54708
54721
|
database: databaseSchema,
|
|
54709
|
-
models: modelsSchema.default({})
|
|
54722
|
+
models: modelsSchema.default({}),
|
|
54723
|
+
ai: aiSchema
|
|
54710
54724
|
});
|
|
54725
|
+
function resolveAiSettings(config) {
|
|
54726
|
+
const ai = config.ai;
|
|
54727
|
+
const fromConfig = ai?.anthropic?.apiKey;
|
|
54728
|
+
const key = fromConfig ?? process.env[ai?.anthropic?.apiKeyEnv ?? "ANTHROPIC_API_KEY"];
|
|
54729
|
+
const out = { anthropicKeyFromConfig: fromConfig !== void 0 };
|
|
54730
|
+
if (ai?.provider !== void 0) out.provider = ai.provider;
|
|
54731
|
+
if (key !== void 0) out.anthropicApiKey = key;
|
|
54732
|
+
if (ai?.anthropic?.model !== void 0) out.model = ai.anthropic.model;
|
|
54733
|
+
return out;
|
|
54734
|
+
}
|
|
54711
54735
|
function listEnvironments(config) {
|
|
54712
54736
|
const out = [];
|
|
54713
54737
|
for (const p of config.projects) {
|
|
@@ -54892,7 +54916,8 @@ async function loadConfigFile(target) {
|
|
|
54892
54916
|
projects: file.project ? [file.project] : [],
|
|
54893
54917
|
database: file.database ?? {
|
|
54894
54918
|
url: process.env["DATABASE_URL"] ?? DEFAULT_DB_URL
|
|
54895
|
-
}
|
|
54919
|
+
},
|
|
54920
|
+
...file.ai !== void 0 ? { ai: file.ai } : {}
|
|
54896
54921
|
};
|
|
54897
54922
|
return parseConfig(raw, target);
|
|
54898
54923
|
}
|
|
@@ -55307,7 +55332,7 @@ var AxonCodeProvider = class {
|
|
|
55307
55332
|
}
|
|
55308
55333
|
async searchSymbols(query, limit = 10) {
|
|
55309
55334
|
const E = this.escapeId(query);
|
|
55310
|
-
const exactQuery = `MATCH (n) WHERE toLower(n.name) = toLower("${E}") AND
|
|
55335
|
+
const exactQuery = `MATCH (n) WHERE toLower(n.name) = toLower("${E}") AND label(n) <> "File" RETURN n.id, n.name, n.file_path LIMIT ${limit}`;
|
|
55311
55336
|
const [exactRows, semanticRes] = await Promise.all([
|
|
55312
55337
|
this.rows(exactQuery).catch(() => []),
|
|
55313
55338
|
this.client.search(query, limit)
|
|
@@ -67396,9 +67421,14 @@ async function runExplain(query, opts) {
|
|
|
67396
67421
|
}
|
|
67397
67422
|
async function isQueueBoundary(config, query) {
|
|
67398
67423
|
try {
|
|
67424
|
+
let project;
|
|
67425
|
+
try {
|
|
67426
|
+
project = resolveEnvironment(config).project;
|
|
67427
|
+
} catch {
|
|
67428
|
+
}
|
|
67399
67429
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
67400
67430
|
try {
|
|
67401
|
-
const edges = await listQueueEdges(db, { queueName: query });
|
|
67431
|
+
const edges = await listQueueEdges(db, { queueName: query, project });
|
|
67402
67432
|
return edges.length > 0;
|
|
67403
67433
|
} finally {
|
|
67404
67434
|
await sql2.end().catch(() => {
|
|
@@ -67508,22 +67538,22 @@ function extractQueueGraph(input) {
|
|
|
67508
67538
|
}
|
|
67509
67539
|
}
|
|
67510
67540
|
const producers = [];
|
|
67511
|
-
for (const
|
|
67512
|
-
for (const m of
|
|
67541
|
+
for (const pc35 of input.producerClasses) {
|
|
67542
|
+
for (const m of pc35.content.matchAll(INJECT_QUEUE_RE)) {
|
|
67513
67543
|
const queue = m[1] ?? "";
|
|
67514
|
-
if (queue) producers.push({ queue, symbol:
|
|
67544
|
+
if (queue) producers.push({ queue, symbol: pc35.name, file: pc35.filePath });
|
|
67515
67545
|
}
|
|
67516
|
-
for (const m of
|
|
67546
|
+
for (const m of pc35.content.matchAll(NEW_BULL_RE)) {
|
|
67517
67547
|
if (m[2] !== "Queue") continue;
|
|
67518
67548
|
const queue = resolveArg(m[3] ?? "", constMap);
|
|
67519
67549
|
if (queue) {
|
|
67520
|
-
producers.push({ queue, symbol: (m[1] ??
|
|
67550
|
+
producers.push({ queue, symbol: (m[1] ?? pc35.name) || baseName(pc35.filePath), file: pc35.filePath });
|
|
67521
67551
|
}
|
|
67522
67552
|
}
|
|
67523
|
-
for (const m of
|
|
67553
|
+
for (const m of pc35.content.matchAll(QUEUE_NAME_CONST_RE)) {
|
|
67524
67554
|
const ident = m[1] ?? "";
|
|
67525
67555
|
const queue = m[2] ?? "";
|
|
67526
|
-
if (queue) producers.push({ queue, symbol: ident || baseName(
|
|
67556
|
+
if (queue) producers.push({ queue, symbol: ident || baseName(pc35.filePath), file: pc35.filePath });
|
|
67527
67557
|
}
|
|
67528
67558
|
}
|
|
67529
67559
|
const workers = [];
|
|
@@ -67796,6 +67826,13 @@ async function runQueues(name, opts) {
|
|
|
67796
67826
|
}
|
|
67797
67827
|
}
|
|
67798
67828
|
const rows = await listQueueEdges(db, { project, queueName: name });
|
|
67829
|
+
if (opts.json) {
|
|
67830
|
+
const topology = topologyToJson(buildQueueMap(rows));
|
|
67831
|
+
const out = { project: project ?? null, topology };
|
|
67832
|
+
if (opts.live) out["live"] = await gatherLiveState(config, rows, name);
|
|
67833
|
+
console.log(JSON.stringify(out, null, 2));
|
|
67834
|
+
return 0;
|
|
67835
|
+
}
|
|
67799
67836
|
console.log(
|
|
67800
67837
|
import_picocolors4.default.bold("Queue topology") + import_picocolors4.default.dim(" \xB7 source: code / source intelligence \xB7 static (run horus index to refresh)")
|
|
67801
67838
|
);
|
|
@@ -67837,6 +67874,55 @@ function buildQueueMap(rows) {
|
|
|
67837
67874
|
}
|
|
67838
67875
|
return byQueue;
|
|
67839
67876
|
}
|
|
67877
|
+
function endpoints(edges, symKey, fileKey) {
|
|
67878
|
+
const seen = /* @__PURE__ */ new Map();
|
|
67879
|
+
for (const e of edges) {
|
|
67880
|
+
const symbol = e[symKey];
|
|
67881
|
+
if (!symbol) continue;
|
|
67882
|
+
if (!seen.has(symbol)) seen.set(symbol, { symbol, file: e[fileKey] ?? null });
|
|
67883
|
+
}
|
|
67884
|
+
return [...seen.values()];
|
|
67885
|
+
}
|
|
67886
|
+
function topologyToJson(byQueue) {
|
|
67887
|
+
return [...byQueue.entries()].map(([queueName, edges]) => ({
|
|
67888
|
+
queueName,
|
|
67889
|
+
producers: endpoints(edges, "producerSymbol", "producerFile"),
|
|
67890
|
+
workers: endpoints(edges, "workerSymbol", "workerFile")
|
|
67891
|
+
}));
|
|
67892
|
+
}
|
|
67893
|
+
async function gatherLiveState(config, rows, nameFilter) {
|
|
67894
|
+
let renv;
|
|
67895
|
+
try {
|
|
67896
|
+
renv = resolveEnvironment(config);
|
|
67897
|
+
} catch (err) {
|
|
67898
|
+
return { ok: false, error: err.message };
|
|
67899
|
+
}
|
|
67900
|
+
const queueProvider = queueForEnv(renv);
|
|
67901
|
+
if (!queueProvider) return { ok: false, error: "Redis not configured" };
|
|
67902
|
+
try {
|
|
67903
|
+
const health = await queueProvider.health();
|
|
67904
|
+
if (!health.ok) return { ok: false, error: health.detail };
|
|
67905
|
+
const staticNames = new Set(buildQueueMap(rows).keys());
|
|
67906
|
+
let queueNames;
|
|
67907
|
+
if (nameFilter !== void 0) {
|
|
67908
|
+
queueNames = [nameFilter];
|
|
67909
|
+
} else {
|
|
67910
|
+
const discovered = await queueProvider.discoverQueues().catch(() => []);
|
|
67911
|
+
const union2 = /* @__PURE__ */ new Set([...staticNames, ...discovered]);
|
|
67912
|
+
queueNames = union2.size > 0 ? [...union2] : void 0;
|
|
67913
|
+
}
|
|
67914
|
+
const state = await queueProvider.analyzeQueues({ queueNames });
|
|
67915
|
+
return {
|
|
67916
|
+
ok: true,
|
|
67917
|
+
prefix: state.prefix,
|
|
67918
|
+
collectedAt: state.collectedAt,
|
|
67919
|
+
queues: state.queues.map((q) => ({ ...q, runtimeOnly: !staticNames.has(q.queueName) }))
|
|
67920
|
+
};
|
|
67921
|
+
} finally {
|
|
67922
|
+
await queueProvider.close().catch(() => {
|
|
67923
|
+
});
|
|
67924
|
+
}
|
|
67925
|
+
}
|
|
67840
67926
|
function printTopology(byQueue) {
|
|
67841
67927
|
for (const [queueName, edges] of byQueue) {
|
|
67842
67928
|
console.log(import_picocolors4.default.bold(queueName));
|
|
@@ -71568,7 +71654,7 @@ async function discoverArchitecture(deps) {
|
|
|
71568
71654
|
})();
|
|
71569
71655
|
const asyncBoundaries = await (async () => {
|
|
71570
71656
|
try {
|
|
71571
|
-
const edges = await listQueueEdges(deps.db);
|
|
71657
|
+
const edges = await listQueueEdges(deps.db, { project: deps.project });
|
|
71572
71658
|
const byQueue = /* @__PURE__ */ new Map();
|
|
71573
71659
|
for (const edge of edges) {
|
|
71574
71660
|
const key = edge.queueName;
|
|
@@ -71732,7 +71818,7 @@ async function analyzeBlastRadius(query, deps, depth = 3) {
|
|
|
71732
71818
|
]);
|
|
71733
71819
|
const upstream = ctx.callees;
|
|
71734
71820
|
const downstream = impact.byDepth;
|
|
71735
|
-
const edges = await listQueueEdges(deps.db);
|
|
71821
|
+
const edges = await listQueueEdges(deps.db, { project: deps.project });
|
|
71736
71822
|
const asyncDownstreamMap = /* @__PURE__ */ new Map();
|
|
71737
71823
|
for (const edge of edges) {
|
|
71738
71824
|
if (edge.producerFile === top.filePath || edge.producerSymbol === top.name) {
|
|
@@ -72850,7 +72936,11 @@ function filterArchitecture(architecture, tokens) {
|
|
|
72850
72936
|
};
|
|
72851
72937
|
}
|
|
72852
72938
|
async function buildOnboarding(input, deps) {
|
|
72853
|
-
const architecture = await discoverArchitecture({
|
|
72939
|
+
const architecture = await discoverArchitecture({
|
|
72940
|
+
code: deps.code,
|
|
72941
|
+
db: deps.db,
|
|
72942
|
+
project: deps.project
|
|
72943
|
+
});
|
|
72854
72944
|
const area = input.area?.trim();
|
|
72855
72945
|
let filteredArchitecture = architecture;
|
|
72856
72946
|
let pastIncidents = [];
|
|
@@ -73577,18 +73667,76 @@ Hard rules:
|
|
|
73577
73667
|
- only include services from: ${input.knownServices.join(", ")}
|
|
73578
73668
|
- hypothesisJudgments must only reference hypothesis IDs from the hypotheses list above
|
|
73579
73669
|
- verdict must be exactly one of: supported, weakened, eliminated, unconfirmed
|
|
73580
|
-
- uncertainty must be exactly one of: low, medium, high
|
|
73670
|
+
- uncertainty must be exactly one of: low, medium, high
|
|
73671
|
+
- output raw JSON only: no markdown code fences, no text before or after the JSON`;
|
|
73581
73672
|
}
|
|
73582
|
-
function
|
|
73583
|
-
|
|
73584
|
-
|
|
73585
|
-
|
|
73586
|
-
|
|
73587
|
-
|
|
73588
|
-
|
|
73589
|
-
|
|
73673
|
+
function sliceBalancedObjects(s) {
|
|
73674
|
+
const out = [];
|
|
73675
|
+
let depth = 0;
|
|
73676
|
+
let start = -1;
|
|
73677
|
+
let inStr = false;
|
|
73678
|
+
let escaped = false;
|
|
73679
|
+
for (let i = 0; i < s.length; i++) {
|
|
73680
|
+
const ch = s[i];
|
|
73681
|
+
if (inStr) {
|
|
73682
|
+
if (escaped) escaped = false;
|
|
73683
|
+
else if (ch === "\\") escaped = true;
|
|
73684
|
+
else if (ch === '"') inStr = false;
|
|
73685
|
+
continue;
|
|
73686
|
+
}
|
|
73687
|
+
if (ch === '"') inStr = true;
|
|
73688
|
+
else if (ch === "{") {
|
|
73689
|
+
if (depth === 0) start = i;
|
|
73690
|
+
depth++;
|
|
73691
|
+
} else if (ch === "}") {
|
|
73692
|
+
if (depth > 0) {
|
|
73693
|
+
depth--;
|
|
73694
|
+
if (depth === 0 && start !== -1) out.push(s.slice(start, i + 1));
|
|
73695
|
+
}
|
|
73590
73696
|
}
|
|
73591
|
-
|
|
73697
|
+
}
|
|
73698
|
+
return out.sort((a, b2) => b2.length - a.length);
|
|
73699
|
+
}
|
|
73700
|
+
function stripTrailingCommas(s) {
|
|
73701
|
+
return s.replace(/,(\s*[}\]])/g, "$1");
|
|
73702
|
+
}
|
|
73703
|
+
function extractJson(raw) {
|
|
73704
|
+
const candidates = [];
|
|
73705
|
+
const trimmed = raw.trim();
|
|
73706
|
+
candidates.push(trimmed);
|
|
73707
|
+
const fence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
73708
|
+
if (fence?.[1]) candidates.push(fence[1].trim());
|
|
73709
|
+
candidates.push(...sliceBalancedObjects(trimmed));
|
|
73710
|
+
for (const c of candidates) {
|
|
73711
|
+
for (const variant of [c, stripTrailingCommas(c)]) {
|
|
73712
|
+
try {
|
|
73713
|
+
const parsed = JSON.parse(variant);
|
|
73714
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
73715
|
+
return parsed;
|
|
73716
|
+
}
|
|
73717
|
+
} catch {
|
|
73718
|
+
}
|
|
73719
|
+
}
|
|
73720
|
+
}
|
|
73721
|
+
return null;
|
|
73722
|
+
}
|
|
73723
|
+
function rawNarrativeFallback(raw, input, ceiling) {
|
|
73724
|
+
const cleaned = raw.replace(/```(?:json)?/gi, "").trim();
|
|
73725
|
+
return {
|
|
73726
|
+
what: input.deterministicSummary || "AI returned an unstructured response.",
|
|
73727
|
+
why: cleaned.slice(0, 4e3),
|
|
73728
|
+
whereNext: [],
|
|
73729
|
+
citations: [],
|
|
73730
|
+
confidence: Math.min(input.reportConfidence, ceiling)
|
|
73731
|
+
};
|
|
73732
|
+
}
|
|
73733
|
+
function parseOutput(raw, input, ceiling) {
|
|
73734
|
+
const parsed = extractJson(raw);
|
|
73735
|
+
if (parsed === null) {
|
|
73736
|
+
console.error(
|
|
73737
|
+
`[ai] structured JSON parse failed (response length ${raw.length}); preserving raw narrative fallback`
|
|
73738
|
+
);
|
|
73739
|
+
return rawNarrativeFallback(raw, input, ceiling);
|
|
73592
73740
|
}
|
|
73593
73741
|
const confidence = Math.min(
|
|
73594
73742
|
typeof parsed["confidence"] === "number" ? parsed["confidence"] : input.reportConfidence,
|
|
@@ -73643,6 +73791,13 @@ init_cjs_shims();
|
|
|
73643
73791
|
|
|
73644
73792
|
// ../../packages/ai/src/local-providers.ts
|
|
73645
73793
|
init_cjs_shims();
|
|
73794
|
+
var LOCAL_PROVIDER_IDS = [
|
|
73795
|
+
"codex",
|
|
73796
|
+
"claude",
|
|
73797
|
+
"kimi",
|
|
73798
|
+
"gemini",
|
|
73799
|
+
"cursor"
|
|
73800
|
+
];
|
|
73646
73801
|
var DEFAULT_DESCRIPTORS = [
|
|
73647
73802
|
{ id: "codex", displayName: "OpenAI Codex CLI" },
|
|
73648
73803
|
{ id: "claude", displayName: "Anthropic Claude" },
|
|
@@ -73841,8 +73996,12 @@ async function runInvestigate(hint, opts) {
|
|
|
73841
73996
|
const rendered = format === "json" ? reportToJSON(report) : format === "markdown" || format === "md" ? reportToMarkdown(report) : renderReport2(report);
|
|
73842
73997
|
console.log(rendered);
|
|
73843
73998
|
if (opts.ai && format !== "json") {
|
|
73844
|
-
const
|
|
73845
|
-
const
|
|
73999
|
+
const ai = resolveAiSettings(config);
|
|
74000
|
+
const model = opts.aiModel ?? ai.model ?? "claude-opus-4-8";
|
|
74001
|
+
const provider = opts._aiProvider ?? new AnthropicNarrativeProvider({
|
|
74002
|
+
model,
|
|
74003
|
+
...ai.anthropicApiKey !== void 0 ? { apiKey: ai.anthropicApiKey } : {}
|
|
74004
|
+
});
|
|
73846
74005
|
console.log(import_picocolors5.default.dim(`[ai] model: ${model}`));
|
|
73847
74006
|
const narrativeInput = buildNarrativeInput(report);
|
|
73848
74007
|
const { output, fromProvider, validationErrors } = await renderNarrative(narrativeInput, { provider });
|
|
@@ -73981,9 +74140,14 @@ async function runArchitecture(opts) {
|
|
|
73981
74140
|
);
|
|
73982
74141
|
return 1;
|
|
73983
74142
|
}
|
|
74143
|
+
let project;
|
|
74144
|
+
try {
|
|
74145
|
+
project = resolveEnvironment(config, { project: opts.repo }).project;
|
|
74146
|
+
} catch {
|
|
74147
|
+
}
|
|
73984
74148
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
73985
74149
|
try {
|
|
73986
|
-
const m = await discoverArchitecture({ code, db });
|
|
74150
|
+
const m = await discoverArchitecture({ code, db, project });
|
|
73987
74151
|
console.log(opts.json ? architectureToJSON(m) : renderArchitecture(m));
|
|
73988
74152
|
} finally {
|
|
73989
74153
|
await sql2.end();
|
|
@@ -74007,9 +74171,14 @@ async function runBlastRadius(query, opts) {
|
|
|
74007
74171
|
console.error(import_picocolors10.default.red("Source-intelligence host unreachable \u2014 run: horus index"));
|
|
74008
74172
|
return 1;
|
|
74009
74173
|
}
|
|
74174
|
+
let project;
|
|
74175
|
+
try {
|
|
74176
|
+
project = resolveEnvironment(config, { project: opts.repo }).project;
|
|
74177
|
+
} catch {
|
|
74178
|
+
}
|
|
74010
74179
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
74011
74180
|
try {
|
|
74012
|
-
const r = await analyzeBlastRadius(query, { code, db }, opts.depth ?? 3);
|
|
74181
|
+
const r = await analyzeBlastRadius(query, { code, db, project }, opts.depth ?? 3);
|
|
74013
74182
|
if (!r) {
|
|
74014
74183
|
console.log(`No symbol found for: ${query}`);
|
|
74015
74184
|
console.log(import_picocolors10.default.dim(` Tip: use an exact class or function name, e.g. "MyService"`));
|
|
@@ -74472,7 +74641,10 @@ async function runOnboard(area, opts) {
|
|
|
74472
74641
|
}
|
|
74473
74642
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
74474
74643
|
try {
|
|
74475
|
-
const g = await buildOnboarding(
|
|
74644
|
+
const g = await buildOnboarding(
|
|
74645
|
+
{ area },
|
|
74646
|
+
{ code, db, repoPath: repo.path, project: renv.project }
|
|
74647
|
+
);
|
|
74476
74648
|
console.log(opts.json ? onboardingToJSON(g) : renderOnboarding(g));
|
|
74477
74649
|
} finally {
|
|
74478
74650
|
await sql2.end();
|
|
@@ -74548,6 +74720,10 @@ function sinceToIso(since) {
|
|
|
74548
74720
|
const msMap = { m: 6e4, h: 36e5, d: 864e5 };
|
|
74549
74721
|
return new Date(Date.now() - (msMap[unit] ?? 6e4) * amount).toISOString();
|
|
74550
74722
|
}
|
|
74723
|
+
function resolveRawLevel(opts) {
|
|
74724
|
+
if (opts.allLevels) return void 0;
|
|
74725
|
+
return opts.level ?? "error";
|
|
74726
|
+
}
|
|
74551
74727
|
function levelColor(level, text2) {
|
|
74552
74728
|
if (level === "error" || level === "fatal") return import_picocolors20.default.red(text2);
|
|
74553
74729
|
if (level === "warn") return import_picocolors20.default.yellow(text2);
|
|
@@ -74607,13 +74783,45 @@ async function runLogs(service, opts) {
|
|
|
74607
74783
|
const from = sinceToIso(opts.since) ?? new Date(Date.now() - 7 * 864e5).toISOString();
|
|
74608
74784
|
const fromDisplay = from.slice(0, 16).replace("T", " ");
|
|
74609
74785
|
if (opts.raw === true) {
|
|
74786
|
+
const rawLevel = resolveRawLevel(opts);
|
|
74787
|
+
const limit = opts.limit !== void 0 ? Math.min(Number(opts.limit), 1e3) : 20;
|
|
74610
74788
|
const records = await logs.searchLogs({
|
|
74611
74789
|
service: resolvedService,
|
|
74612
74790
|
from,
|
|
74613
|
-
level:
|
|
74791
|
+
level: rawLevel,
|
|
74614
74792
|
text: opts.grep,
|
|
74615
|
-
limit
|
|
74793
|
+
limit
|
|
74616
74794
|
});
|
|
74795
|
+
if (opts.json) {
|
|
74796
|
+
console.log(
|
|
74797
|
+
JSON.stringify(
|
|
74798
|
+
{
|
|
74799
|
+
scope: {
|
|
74800
|
+
project: renv.project,
|
|
74801
|
+
env: renv.env,
|
|
74802
|
+
service: resolvedService ?? null,
|
|
74803
|
+
index: indexPattern,
|
|
74804
|
+
from,
|
|
74805
|
+
level: rawLevel ?? "all",
|
|
74806
|
+
grep: opts.grep ?? null
|
|
74807
|
+
},
|
|
74808
|
+
records: records.map((r) => ({
|
|
74809
|
+
timestamp: r.timestamp,
|
|
74810
|
+
level: r.level,
|
|
74811
|
+
service: r.service ?? null,
|
|
74812
|
+
component: r.component ?? null,
|
|
74813
|
+
eventCode: r.eventCode ?? null,
|
|
74814
|
+
message: r.message,
|
|
74815
|
+
traceId: r.traceId ?? null,
|
|
74816
|
+
requestId: r.requestId ?? null
|
|
74817
|
+
}))
|
|
74818
|
+
},
|
|
74819
|
+
null,
|
|
74820
|
+
2
|
|
74821
|
+
)
|
|
74822
|
+
);
|
|
74823
|
+
return 0;
|
|
74824
|
+
}
|
|
74617
74825
|
if (records.length === 0) {
|
|
74618
74826
|
console.log(import_picocolors20.default.dim("No logs matched."));
|
|
74619
74827
|
return 0;
|
|
@@ -74642,6 +74850,30 @@ async function runLogs(service, opts) {
|
|
|
74642
74850
|
scopeService = void 0;
|
|
74643
74851
|
}
|
|
74644
74852
|
}
|
|
74853
|
+
if (opts.json) {
|
|
74854
|
+
console.log(
|
|
74855
|
+
JSON.stringify(
|
|
74856
|
+
{
|
|
74857
|
+
scope: {
|
|
74858
|
+
project: renv.project,
|
|
74859
|
+
env: renv.env,
|
|
74860
|
+
service: scopeService ?? null,
|
|
74861
|
+
index: indexPattern,
|
|
74862
|
+
from,
|
|
74863
|
+
severity: "error+",
|
|
74864
|
+
grep: opts.grep ?? null
|
|
74865
|
+
},
|
|
74866
|
+
totalErrors: analysis.totalErrors,
|
|
74867
|
+
signatures: analysis.signatures,
|
|
74868
|
+
newSignatures: analysis.newSignatures,
|
|
74869
|
+
affectedServices: analysis.affectedServices
|
|
74870
|
+
},
|
|
74871
|
+
null,
|
|
74872
|
+
2
|
|
74873
|
+
)
|
|
74874
|
+
);
|
|
74875
|
+
return 0;
|
|
74876
|
+
}
|
|
74645
74877
|
console.log(
|
|
74646
74878
|
import_picocolors20.default.bold(`Error analysis`) + import_picocolors20.default.dim(
|
|
74647
74879
|
` \u2014 ${renv.project}/${renv.env}` + (scopeService ? ` \xB7 service ${scopeService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
|
|
@@ -74766,6 +74998,25 @@ async function runMetrics(hint, opts) {
|
|
|
74766
74998
|
const from = to - dur;
|
|
74767
74999
|
if (opts.query !== void 0 && opts.query !== "") {
|
|
74768
75000
|
const series = await metrics.rawRange(opts.query, from, to, stepNum);
|
|
75001
|
+
if (opts.json === true) {
|
|
75002
|
+
console.log(
|
|
75003
|
+
JSON.stringify(
|
|
75004
|
+
{
|
|
75005
|
+
query: opts.query,
|
|
75006
|
+
from,
|
|
75007
|
+
to,
|
|
75008
|
+
step: stepNum,
|
|
75009
|
+
series: series.map((s) => {
|
|
75010
|
+
const summary = summarize(s);
|
|
75011
|
+
return { labels: s.labels, last: summary.last, avg: summary.avg };
|
|
75012
|
+
})
|
|
75013
|
+
},
|
|
75014
|
+
null,
|
|
75015
|
+
2
|
|
75016
|
+
)
|
|
75017
|
+
);
|
|
75018
|
+
return 0;
|
|
75019
|
+
}
|
|
74769
75020
|
if (series.length === 0) {
|
|
74770
75021
|
console.log(import_picocolors21.default.dim("No series returned."));
|
|
74771
75022
|
return 0;
|
|
@@ -74876,6 +75127,23 @@ async function runState(opts) {
|
|
|
74876
75127
|
const analysis = await mongo.analyzeState(
|
|
74877
75128
|
staleHours !== void 0 ? { staleHours } : {}
|
|
74878
75129
|
);
|
|
75130
|
+
if (opts.json) {
|
|
75131
|
+
const staleSignals = analysis.collections.filter((c) => c.isStale === true).length;
|
|
75132
|
+
const anomalousSignals = analysis.collections.reduce((n, c) => n + c.anomalies.length, 0);
|
|
75133
|
+
console.log(
|
|
75134
|
+
JSON.stringify(
|
|
75135
|
+
{
|
|
75136
|
+
scope: { project: renv.project, env: renv.env, database: analysis.database },
|
|
75137
|
+
autoDiscovered: analysis.autoDiscovered,
|
|
75138
|
+
signals: { stale: staleSignals, anomalous: anomalousSignals },
|
|
75139
|
+
collections: analysis.collections
|
|
75140
|
+
},
|
|
75141
|
+
null,
|
|
75142
|
+
2
|
|
75143
|
+
)
|
|
75144
|
+
);
|
|
75145
|
+
return 0;
|
|
75146
|
+
}
|
|
74879
75147
|
const discoveryNote = analysis.autoDiscovered ? import_picocolors22.default.dim(` (${analysis.collections.length} collections, auto-discovered)`) : "";
|
|
74880
75148
|
console.log(
|
|
74881
75149
|
import_picocolors22.default.bold("State analysis") + import_picocolors22.default.dim(` \u2014 ${renv.project}/${renv.env} \xB7 db ${analysis.database}`) + discoveryNote
|
|
@@ -75121,8 +75389,8 @@ async function runSetup(opts) {
|
|
|
75121
75389
|
// ../../packages/cli/src/commands/connect.ts
|
|
75122
75390
|
init_cjs_shims();
|
|
75123
75391
|
var import_node_readline = require("readline");
|
|
75124
|
-
var
|
|
75125
|
-
var
|
|
75392
|
+
var import_node_child_process6 = require("child_process");
|
|
75393
|
+
var import_picocolors28 = __toESM(require_picocolors(), 1);
|
|
75126
75394
|
|
|
75127
75395
|
// ../../packages/cli/src/lib/tty-selector.ts
|
|
75128
75396
|
init_cjs_shims();
|
|
@@ -75302,6 +75570,122 @@ async function checkboxSearch(opts) {
|
|
|
75302
75570
|
});
|
|
75303
75571
|
}
|
|
75304
75572
|
|
|
75573
|
+
// ../../packages/cli/src/commands/connect-ai.ts
|
|
75574
|
+
init_cjs_shims();
|
|
75575
|
+
var import_node_child_process5 = require("child_process");
|
|
75576
|
+
var import_picocolors27 = __toESM(require_picocolors(), 1);
|
|
75577
|
+
function cliInstalled(id) {
|
|
75578
|
+
const r = (0, import_node_child_process5.spawnSync)(id, ["--version"], { stdio: "ignore", timeout: 2e3 });
|
|
75579
|
+
return !r.error;
|
|
75580
|
+
}
|
|
75581
|
+
async function probeAnthropic(apiKey) {
|
|
75582
|
+
try {
|
|
75583
|
+
const res = await fetch("https://api.anthropic.com/v1/models?limit=1", {
|
|
75584
|
+
headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
|
|
75585
|
+
signal: AbortSignal.timeout(8e3)
|
|
75586
|
+
});
|
|
75587
|
+
if (res.ok) return { ok: true, detail: "API key valid" };
|
|
75588
|
+
if (res.status === 401) return { ok: false, detail: "invalid API key (401)" };
|
|
75589
|
+
return { ok: false, detail: `HTTP ${res.status}` };
|
|
75590
|
+
} catch (err) {
|
|
75591
|
+
return { ok: false, detail: err.message };
|
|
75592
|
+
}
|
|
75593
|
+
}
|
|
75594
|
+
function redactKey(key) {
|
|
75595
|
+
if (key.length <= 12) return "***";
|
|
75596
|
+
return `${key.slice(0, 7)}\u2026${key.slice(-4)}`;
|
|
75597
|
+
}
|
|
75598
|
+
async function runConnectAi(opts) {
|
|
75599
|
+
try {
|
|
75600
|
+
const cwd = process.cwd();
|
|
75601
|
+
const root = findRepoRoot(cwd) ?? cwd;
|
|
75602
|
+
const configPath = discoverLocalConfig(cwd) ?? localConfigPath(root);
|
|
75603
|
+
const installed = LOCAL_PROVIDER_IDS.filter((id) => cliInstalled(id));
|
|
75604
|
+
console.log(import_picocolors27.default.bold("\nAI providers"));
|
|
75605
|
+
console.log(
|
|
75606
|
+
import_picocolors27.default.dim(
|
|
75607
|
+
` Local CLIs on PATH: ${installed.length > 0 ? installed.join(", ") : "none detected"}`
|
|
75608
|
+
)
|
|
75609
|
+
);
|
|
75610
|
+
console.log(import_picocolors27.default.dim(" Cloud: anthropic (Anthropic Claude API \u2014 used by `investigate --ai`)\n"));
|
|
75611
|
+
let provider = opts.provider;
|
|
75612
|
+
if (provider === void 0) {
|
|
75613
|
+
if (isInteractive()) {
|
|
75614
|
+
const answer = (await ask("Provider", "anthropic", false)).trim().toLowerCase();
|
|
75615
|
+
provider = answer || "anthropic";
|
|
75616
|
+
} else {
|
|
75617
|
+
provider = "anthropic";
|
|
75618
|
+
}
|
|
75619
|
+
}
|
|
75620
|
+
if (!AI_PROVIDERS.includes(provider)) {
|
|
75621
|
+
console.error(import_picocolors27.default.red(`Unknown AI provider: ${provider}`) + import_picocolors27.default.dim(`
|
|
75622
|
+
supported: ${AI_PROVIDERS.join(", ")}`));
|
|
75623
|
+
return 1;
|
|
75624
|
+
}
|
|
75625
|
+
const aiBlock = { provider };
|
|
75626
|
+
let storedKey;
|
|
75627
|
+
if (provider === "anthropic") {
|
|
75628
|
+
let apiKey = opts.apiKey;
|
|
75629
|
+
if (!apiKey && isInteractive()) {
|
|
75630
|
+
apiKey = await askPassword("Anthropic API key") || void 0;
|
|
75631
|
+
}
|
|
75632
|
+
if (!apiKey) {
|
|
75633
|
+
console.error(
|
|
75634
|
+
import_picocolors27.default.red("No API key provided.") + import_picocolors27.default.dim("\n Pass --api-key sk-ant-\u2026 or set ANTHROPIC_API_KEY and re-run.")
|
|
75635
|
+
);
|
|
75636
|
+
return 1;
|
|
75637
|
+
}
|
|
75638
|
+
if (!opts.noTest) {
|
|
75639
|
+
const probe2 = await probeAnthropic(apiKey);
|
|
75640
|
+
if (!probe2.ok) {
|
|
75641
|
+
console.error(`
|
|
75642
|
+
${import_picocolors27.default.red("\u2717 Anthropic key check failed:")} ${probe2.detail}`);
|
|
75643
|
+
console.error(import_picocolors27.default.dim(" Fix the key and retry, or pass --no-test to skip."));
|
|
75644
|
+
return 1;
|
|
75645
|
+
}
|
|
75646
|
+
console.log(`
|
|
75647
|
+
${import_picocolors27.default.green("\u2713")} anthropic ${import_picocolors27.default.dim(`(${probe2.detail})`)}`);
|
|
75648
|
+
}
|
|
75649
|
+
const anthropic = { apiKey };
|
|
75650
|
+
if (opts.model) anthropic["model"] = opts.model;
|
|
75651
|
+
aiBlock["anthropic"] = anthropic;
|
|
75652
|
+
storedKey = apiKey;
|
|
75653
|
+
} else {
|
|
75654
|
+
if (!installed.includes(provider)) {
|
|
75655
|
+
console.error(
|
|
75656
|
+
import_picocolors27.default.red(`${provider} CLI not found on PATH.`) + import_picocolors27.default.dim(`
|
|
75657
|
+
Install it first, or choose anthropic.`)
|
|
75658
|
+
);
|
|
75659
|
+
return 1;
|
|
75660
|
+
}
|
|
75661
|
+
const desc2 = DEFAULT_LOCAL_PROVIDER_REGISTRY.get(provider);
|
|
75662
|
+
console.log(`
|
|
75663
|
+
${import_picocolors27.default.green("\u2713")} ${desc2?.displayName ?? provider} ${import_picocolors27.default.dim("detected on PATH")}`);
|
|
75664
|
+
console.log(
|
|
75665
|
+
import_picocolors27.default.dim(
|
|
75666
|
+
" Note: `investigate --ai` currently uses the Anthropic API for narrative.\n Local-CLI narrative generation is recorded as your preference for future use."
|
|
75667
|
+
)
|
|
75668
|
+
);
|
|
75669
|
+
}
|
|
75670
|
+
const file = discoverLocalConfig(cwd) ? readLocalConfig(configPath) : { version: 1, project: { name: root.split("/").pop() ?? "project", repositories: [{ name: root.split("/").pop() ?? "project", path: root }], environments: [{ name: "production", readOnly: true, connectors: {} }] } };
|
|
75671
|
+
file.ai = aiBlock;
|
|
75672
|
+
writeLocalConfig(root, file);
|
|
75673
|
+
if (storedKey !== void 0) {
|
|
75674
|
+
ensureCredentialGitignore(root);
|
|
75675
|
+
ensureProjectGitignore(root);
|
|
75676
|
+
}
|
|
75677
|
+
console.log(`
|
|
75678
|
+
${import_picocolors27.default.green("\u2713")} ${import_picocolors27.default.bold("ai")} provider saved \u2192 ${import_picocolors27.default.dim(configPath)}`);
|
|
75679
|
+
console.log(import_picocolors27.default.dim(` provider: ${provider}`));
|
|
75680
|
+
if (storedKey) console.log(import_picocolors27.default.dim(` anthropic key: ${redactKey(storedKey)}`));
|
|
75681
|
+
console.log(import_picocolors27.default.dim(' run: horus investigate "<hint>" --ai'));
|
|
75682
|
+
return 0;
|
|
75683
|
+
} catch (err) {
|
|
75684
|
+
console.error(import_picocolors27.default.red(err instanceof Error ? err.message : String(err)));
|
|
75685
|
+
return 1;
|
|
75686
|
+
}
|
|
75687
|
+
}
|
|
75688
|
+
|
|
75305
75689
|
// ../../packages/cli/src/commands/connect.ts
|
|
75306
75690
|
function parseDbSpec(spec) {
|
|
75307
75691
|
const [dbPart, rolesPart] = spec.split(":");
|
|
@@ -75319,10 +75703,18 @@ function parseDbSpec(spec) {
|
|
|
75319
75703
|
}
|
|
75320
75704
|
var SUPPORTED = ["elasticsearch", "mongodb", "grafana", "redis"];
|
|
75321
75705
|
async function runConnect(type, opts) {
|
|
75706
|
+
if (type === "ai") {
|
|
75707
|
+
return runConnectAi({
|
|
75708
|
+
provider: opts.provider,
|
|
75709
|
+
apiKey: opts.apiKey,
|
|
75710
|
+
model: opts.aiModel,
|
|
75711
|
+
noTest: opts.noTest
|
|
75712
|
+
});
|
|
75713
|
+
}
|
|
75322
75714
|
if (!SUPPORTED.includes(type)) {
|
|
75323
75715
|
console.error(
|
|
75324
|
-
|
|
75325
|
-
supported: ${SUPPORTED.join(", ")}`)
|
|
75716
|
+
import_picocolors28.default.red(`Unknown connector type: ${type}`) + import_picocolors28.default.dim(`
|
|
75717
|
+
supported: ${SUPPORTED.join(", ")}, ai`)
|
|
75326
75718
|
);
|
|
75327
75719
|
return 1;
|
|
75328
75720
|
}
|
|
@@ -75352,20 +75744,20 @@ async function runConnect(type, opts) {
|
|
|
75352
75744
|
if (!probeResult.ok) {
|
|
75353
75745
|
console.error(
|
|
75354
75746
|
`
|
|
75355
|
-
${
|
|
75747
|
+
${import_picocolors28.default.red(`\u2717 Could not reach ${connectorType}:`)} ${probeResult.detail}` + import_picocolors28.default.dim("\n Fix the connection and retry, or pass --no-test to skip.")
|
|
75356
75748
|
);
|
|
75357
75749
|
return 1;
|
|
75358
75750
|
}
|
|
75359
75751
|
console.log(
|
|
75360
75752
|
`
|
|
75361
|
-
${
|
|
75753
|
+
${import_picocolors28.default.green("\u2713")} ${connectorType} reachable ${import_picocolors28.default.dim(`(${probeResult.detail})`)}`
|
|
75362
75754
|
);
|
|
75363
75755
|
}
|
|
75364
75756
|
const hasLiteralCredentials = filled.url !== void 0 || filled.password !== void 0 || filled.username !== void 0;
|
|
75365
75757
|
if (hasLiteralCredentials) {
|
|
75366
75758
|
if (isGitTracked(configPath, root)) {
|
|
75367
75759
|
console.error(
|
|
75368
|
-
|
|
75760
|
+
import_picocolors28.default.red(".horus/config.json is already tracked by Git.") + "\nStoring credentials here would expose them in the repository.\n" + import_picocolors28.default.dim(
|
|
75369
75761
|
" Option A \u2014 remove from Git index then re-run:\n git rm --cached .horus/config.json\n horus connect " + type + " ...\n\n Option B \u2014 keep credentials in the environment and reference them:\n export ES_URL=https://...\n export ES_USERNAME=...\n export ES_PASSWORD=...\n Then edit .horus/config.json manually to use urlEnv/usernameEnv/passwordEnv."
|
|
75370
75762
|
)
|
|
75371
75763
|
);
|
|
@@ -75375,18 +75767,18 @@ ${import_picocolors27.default.green("\u2713")} ${connectorType} reachable ${impo
|
|
|
75375
75767
|
}
|
|
75376
75768
|
patchLocalConnector(configPath, connectorType, patch, filled.env);
|
|
75377
75769
|
console.log(
|
|
75378
|
-
`${
|
|
75770
|
+
`${import_picocolors28.default.green("\u2713")} ${import_picocolors28.default.bold(connectorType)} connector saved \u2192 ${import_picocolors28.default.dim(configPath)}`
|
|
75379
75771
|
);
|
|
75380
75772
|
printSummary(connectorType, filled);
|
|
75381
|
-
console.log(
|
|
75773
|
+
console.log(import_picocolors28.default.dim(`
|
|
75382
75774
|
run: horus investigate "<hint>"`));
|
|
75383
75775
|
return 0;
|
|
75384
75776
|
} catch (err) {
|
|
75385
75777
|
if (err instanceof ExitPromptError) {
|
|
75386
|
-
console.error(
|
|
75778
|
+
console.error(import_picocolors28.default.red("Cancelled."));
|
|
75387
75779
|
return 1;
|
|
75388
75780
|
}
|
|
75389
|
-
console.error(
|
|
75781
|
+
console.error(import_picocolors28.default.red(err.message));
|
|
75390
75782
|
return 1;
|
|
75391
75783
|
}
|
|
75392
75784
|
}
|
|
@@ -75395,7 +75787,7 @@ async function fillInteractive(type, opts) {
|
|
|
75395
75787
|
if (!needsInteraction) return opts;
|
|
75396
75788
|
console.log(
|
|
75397
75789
|
`
|
|
75398
|
-
${
|
|
75790
|
+
${import_picocolors28.default.bold(`Connect ${type}`)} ${import_picocolors28.default.dim("(press Enter to skip optional fields)")}
|
|
75399
75791
|
`
|
|
75400
75792
|
);
|
|
75401
75793
|
const filled = { ...opts };
|
|
@@ -75450,7 +75842,7 @@ ${import_picocolors27.default.bold(`Connect ${type}`)} ${import_picocolors27.def
|
|
|
75450
75842
|
break;
|
|
75451
75843
|
case "redis": {
|
|
75452
75844
|
console.log(
|
|
75453
|
-
|
|
75845
|
+
import_picocolors28.default.dim(
|
|
75454
75846
|
" Tip: embed credentials directly in the URL \u2014 redis://:password@host:6379\n or enter the URL and password separately below."
|
|
75455
75847
|
)
|
|
75456
75848
|
);
|
|
@@ -75488,8 +75880,8 @@ function missingRequired(type, opts) {
|
|
|
75488
75880
|
}
|
|
75489
75881
|
function ask(label, placeholder = "", required = true) {
|
|
75490
75882
|
return new Promise((resolve8) => {
|
|
75491
|
-
const hint = placeholder ?
|
|
75492
|
-
const suffix = required ? "" :
|
|
75883
|
+
const hint = placeholder ? import_picocolors28.default.dim(` (${placeholder})`) : "";
|
|
75884
|
+
const suffix = required ? "" : import_picocolors28.default.dim(" [optional]");
|
|
75493
75885
|
process.stdout.write(` ${label}${suffix}${hint}: `);
|
|
75494
75886
|
const rl = (0, import_node_readline.createInterface)({
|
|
75495
75887
|
input: process.stdin,
|
|
@@ -75506,7 +75898,7 @@ function askPassword(label) {
|
|
|
75506
75898
|
return new Promise((resolve8) => {
|
|
75507
75899
|
const stdin = process.stdin;
|
|
75508
75900
|
if (typeof stdin.setRawMode === "function") {
|
|
75509
|
-
process.stdout.write(` ${label}${
|
|
75901
|
+
process.stdout.write(` ${label}${import_picocolors28.default.dim(" [optional]")}: `);
|
|
75510
75902
|
stdin.setRawMode(true);
|
|
75511
75903
|
stdin.resume();
|
|
75512
75904
|
let value = "";
|
|
@@ -75606,15 +75998,15 @@ function describeProbe(p) {
|
|
|
75606
75998
|
return `${p.keyCount} keys \xB7 ${p.suggestedRoles.join("/")}${examples ? ` \xB7 ${examples}` : ""}`;
|
|
75607
75999
|
}
|
|
75608
76000
|
async function discoverAndSelectDbs(url, bullmqPrefix) {
|
|
75609
|
-
console.log(
|
|
76001
|
+
console.log(import_picocolors28.default.dim("\n Scanning DBs 0-15 (read-only, sampled)\u2026"));
|
|
75610
76002
|
const probes = await probeRedisDatabases(url, { bullmqPrefix });
|
|
75611
76003
|
const nonEmpty = probes.filter((p) => p.reachable && p.keyCount > 0);
|
|
75612
76004
|
if (nonEmpty.length === 0) {
|
|
75613
|
-
console.log(
|
|
76005
|
+
console.log(import_picocolors28.default.dim(" No populated DBs found."));
|
|
75614
76006
|
return [];
|
|
75615
76007
|
}
|
|
75616
76008
|
for (const p of nonEmpty) {
|
|
75617
|
-
console.log(` ${
|
|
76009
|
+
console.log(` ${import_picocolors28.default.bold(`DB ${p.db}`)} ${import_picocolors28.default.dim("\xB7 " + describeProbe(p))}`);
|
|
75618
76010
|
}
|
|
75619
76011
|
const byLabel = /* @__PURE__ */ new Map();
|
|
75620
76012
|
const choices = nonEmpty.map((p) => {
|
|
@@ -75725,11 +76117,11 @@ function printSummary(type, opts) {
|
|
|
75725
76117
|
} else if (opts.dashboard) {
|
|
75726
76118
|
lines.push(` dashboard: ${opts.dashboard}`);
|
|
75727
76119
|
}
|
|
75728
|
-
if (lines.length > 0) console.log(
|
|
76120
|
+
if (lines.length > 0) console.log(import_picocolors28.default.dim(lines.join("\n")));
|
|
75729
76121
|
}
|
|
75730
76122
|
function isGitTracked(filePath, cwd) {
|
|
75731
76123
|
try {
|
|
75732
|
-
(0,
|
|
76124
|
+
(0, import_node_child_process6.execFileSync)("git", ["ls-files", "--error-unmatch", filePath], {
|
|
75733
76125
|
cwd,
|
|
75734
76126
|
stdio: "pipe"
|
|
75735
76127
|
});
|
|
@@ -75764,11 +76156,11 @@ async function askIndexSelection(indices) {
|
|
|
75764
76156
|
const shown = indices.slice(0, MAX_DISPLAY);
|
|
75765
76157
|
console.log("\n Available Elasticsearch indexes/data streams:");
|
|
75766
76158
|
shown.forEach((name, i) => {
|
|
75767
|
-
console.log(` ${
|
|
76159
|
+
console.log(` ${import_picocolors28.default.dim(`[${i + 1}]`)} ${name}`);
|
|
75768
76160
|
});
|
|
75769
76161
|
if (indices.length > MAX_DISPLAY) {
|
|
75770
76162
|
console.log(
|
|
75771
|
-
|
|
76163
|
+
import_picocolors28.default.dim(
|
|
75772
76164
|
` \u2026 and ${indices.length - MAX_DISPLAY} more (type a pattern manually to match all)`
|
|
75773
76165
|
)
|
|
75774
76166
|
);
|
|
@@ -75812,11 +76204,11 @@ async function askDashboardSelection(dashboards) {
|
|
|
75812
76204
|
const shown = dashboards.slice(0, MAX_DISPLAY);
|
|
75813
76205
|
console.log("\n Available Grafana dashboards:");
|
|
75814
76206
|
shown.forEach((d, i) => {
|
|
75815
|
-
const folder = d.folderTitle ?
|
|
75816
|
-
console.log(` ${
|
|
76207
|
+
const folder = d.folderTitle ? import_picocolors28.default.dim(` (${d.folderTitle})`) : "";
|
|
76208
|
+
console.log(` ${import_picocolors28.default.dim(`[${i + 1}]`)} ${d.title}${folder}`);
|
|
75817
76209
|
});
|
|
75818
76210
|
if (dashboards.length > MAX_DISPLAY) {
|
|
75819
|
-
console.log(
|
|
76211
|
+
console.log(import_picocolors28.default.dim(` \u2026 and ${dashboards.length - MAX_DISPLAY} more`));
|
|
75820
76212
|
}
|
|
75821
76213
|
const input = (await ask(
|
|
75822
76214
|
` Select dashboards to use (e.g. 1,2 or Enter to type uid manually)`,
|
|
@@ -75866,12 +76258,12 @@ function redactUrl(raw) {
|
|
|
75866
76258
|
|
|
75867
76259
|
// ../../packages/cli/src/commands/stop.ts
|
|
75868
76260
|
init_cjs_shims();
|
|
75869
|
-
var
|
|
76261
|
+
var import_node_child_process7 = require("child_process");
|
|
75870
76262
|
var import_node_fs7 = require("fs");
|
|
75871
76263
|
var import_node_util4 = require("util");
|
|
75872
76264
|
var import_node_path9 = require("path");
|
|
75873
|
-
var
|
|
75874
|
-
var execFileAsync = (0, import_node_util4.promisify)(
|
|
76265
|
+
var import_picocolors29 = __toESM(require_picocolors(), 1);
|
|
76266
|
+
var execFileAsync = (0, import_node_util4.promisify)(import_node_child_process7.execFile);
|
|
75875
76267
|
var unlinkAsync = (0, import_node_util4.promisify)(import_node_fs7.unlink);
|
|
75876
76268
|
var SPAWNED_HOST_FILE2 = "spawned-host.json";
|
|
75877
76269
|
var START_TIME_TOLERANCE_S = 60;
|
|
@@ -75887,30 +76279,30 @@ async function runStop(opts) {
|
|
|
75887
76279
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
75888
76280
|
const hostUrl = readSourceHostUrl(root);
|
|
75889
76281
|
if (!hostUrl) {
|
|
75890
|
-
console.log(
|
|
76282
|
+
console.log(import_picocolors29.default.dim("No source-intelligence host found for this repo (.horus/source/host.json absent)."));
|
|
75891
76283
|
return 0;
|
|
75892
76284
|
}
|
|
75893
76285
|
return await stopHost(root, hostUrl);
|
|
75894
76286
|
} catch (err) {
|
|
75895
|
-
console.error(
|
|
76287
|
+
console.error(import_picocolors29.default.red(err.message));
|
|
75896
76288
|
return 1;
|
|
75897
76289
|
}
|
|
75898
76290
|
}
|
|
75899
76291
|
async function stopHost(root, hostUrl) {
|
|
75900
76292
|
const alive = await isHostHealthy(hostUrl);
|
|
75901
76293
|
if (!alive) {
|
|
75902
|
-
console.log(
|
|
76294
|
+
console.log(import_picocolors29.default.dim(`Host ${hostUrl} is already stopped.`));
|
|
75903
76295
|
return 0;
|
|
75904
76296
|
}
|
|
75905
76297
|
const port = extractPort(hostUrl);
|
|
75906
76298
|
if (port === null) {
|
|
75907
|
-
console.error(
|
|
76299
|
+
console.error(import_picocolors29.default.red(`Cannot determine port from host URL: ${hostUrl}`));
|
|
75908
76300
|
return 1;
|
|
75909
76301
|
}
|
|
75910
76302
|
const spawned = readSpawnedHost(root);
|
|
75911
76303
|
if (spawned === null) {
|
|
75912
76304
|
console.error(
|
|
75913
|
-
|
|
76305
|
+
import_picocolors29.default.red(
|
|
75914
76306
|
`No ownership record found (.horus/${SPAWNED_HOST_FILE2} absent). Horus will not stop a host it did not spawn.`
|
|
75915
76307
|
)
|
|
75916
76308
|
);
|
|
@@ -75918,12 +76310,12 @@ async function stopHost(root, hostUrl) {
|
|
|
75918
76310
|
}
|
|
75919
76311
|
const recordError = validateSpawnedRecord(spawned);
|
|
75920
76312
|
if (recordError !== null) {
|
|
75921
|
-
console.error(
|
|
76313
|
+
console.error(import_picocolors29.default.red(`Ownership record is malformed: ${recordError}. Aborting for safety.`));
|
|
75922
76314
|
return 1;
|
|
75923
76315
|
}
|
|
75924
76316
|
if (spawned.port !== port) {
|
|
75925
76317
|
console.error(
|
|
75926
|
-
|
|
76318
|
+
import_picocolors29.default.red(
|
|
75927
76319
|
`Ownership record port (${spawned.port}) does not match host URL port (${port}). Record may be stale.`
|
|
75928
76320
|
)
|
|
75929
76321
|
);
|
|
@@ -75931,7 +76323,7 @@ async function stopHost(root, hostUrl) {
|
|
|
75931
76323
|
}
|
|
75932
76324
|
if (spawned.root !== root) {
|
|
75933
76325
|
console.error(
|
|
75934
|
-
|
|
76326
|
+
import_picocolors29.default.red(
|
|
75935
76327
|
`Ownership record root (${spawned.root}) does not match resolved root (${root}). Record may be stale.`
|
|
75936
76328
|
)
|
|
75937
76329
|
);
|
|
@@ -75939,7 +76331,7 @@ async function stopHost(root, hostUrl) {
|
|
|
75939
76331
|
}
|
|
75940
76332
|
const info = await getProcessInfo(spawned.pid);
|
|
75941
76333
|
if (info === null) {
|
|
75942
|
-
console.log(
|
|
76334
|
+
console.log(import_picocolors29.default.dim(`Process pid ${spawned.pid} is no longer running \u2014 already stopped.`));
|
|
75943
76335
|
try {
|
|
75944
76336
|
await unlinkAsync((0, import_node_path9.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
|
|
75945
76337
|
} catch {
|
|
@@ -75952,7 +76344,7 @@ async function stopHost(root, hostUrl) {
|
|
|
75952
76344
|
);
|
|
75953
76345
|
if (!hostPortRe.test(info.args)) {
|
|
75954
76346
|
console.error(
|
|
75955
|
-
|
|
76347
|
+
import_picocolors29.default.red(
|
|
75956
76348
|
`Pid ${spawned.pid} args do not match "horus-source host --port ${portStr}". Got: "${info.args.slice(0, 120)}". Aborting for safety.`
|
|
75957
76349
|
)
|
|
75958
76350
|
);
|
|
@@ -75961,12 +76353,12 @@ async function stopHost(root, hostUrl) {
|
|
|
75961
76353
|
const startTs = new Date(spawned.startedAt).getTime();
|
|
75962
76354
|
const recordedAgeS = Math.round((Date.now() - startTs) / 1e3);
|
|
75963
76355
|
if (!Number.isFinite(info.etimeSeconds)) {
|
|
75964
|
-
console.error(
|
|
76356
|
+
console.error(import_picocolors29.default.red(`Could not read elapsed time for pid ${spawned.pid}. Aborting for safety.`));
|
|
75965
76357
|
return 1;
|
|
75966
76358
|
}
|
|
75967
76359
|
if (Math.abs(info.etimeSeconds - recordedAgeS) > START_TIME_TOLERANCE_S) {
|
|
75968
76360
|
console.error(
|
|
75969
|
-
|
|
76361
|
+
import_picocolors29.default.red(
|
|
75970
76362
|
`Pid ${spawned.pid} age mismatch: record says ~${recordedAgeS}s, process reports ${info.etimeSeconds}s elapsed. Possible PID reuse \u2014 aborting for safety.`
|
|
75971
76363
|
)
|
|
75972
76364
|
);
|
|
@@ -75978,9 +76370,9 @@ async function stopHost(root, hostUrl) {
|
|
|
75978
76370
|
signaled = true;
|
|
75979
76371
|
} catch (err) {
|
|
75980
76372
|
if (err.code === "ESRCH") {
|
|
75981
|
-
console.log(
|
|
76373
|
+
console.log(import_picocolors29.default.dim(`Process pid ${spawned.pid} exited before signal \u2014 already stopped.`));
|
|
75982
76374
|
} else {
|
|
75983
|
-
console.error(
|
|
76375
|
+
console.error(import_picocolors29.default.red(`Failed to signal pid ${spawned.pid}: ${err.message}`));
|
|
75984
76376
|
return 1;
|
|
75985
76377
|
}
|
|
75986
76378
|
}
|
|
@@ -75996,14 +76388,14 @@ async function stopHost(root, hostUrl) {
|
|
|
75996
76388
|
}
|
|
75997
76389
|
if (!exited) {
|
|
75998
76390
|
console.error(
|
|
75999
|
-
|
|
76391
|
+
import_picocolors29.default.red(
|
|
76000
76392
|
`Host pid ${spawned.pid} did not exit within ${STOP_WAIT_MS / 1e3}s after SIGTERM.`
|
|
76001
76393
|
)
|
|
76002
76394
|
);
|
|
76003
76395
|
return 1;
|
|
76004
76396
|
}
|
|
76005
76397
|
console.log(
|
|
76006
|
-
`${
|
|
76398
|
+
`${import_picocolors29.default.green("\u2713")} Stopped source-intelligence host ` + import_picocolors29.default.dim(`(pid ${spawned.pid}, port ${port})`) + ` for ${root}`
|
|
76007
76399
|
);
|
|
76008
76400
|
}
|
|
76009
76401
|
try {
|
|
@@ -76016,7 +76408,7 @@ async function stopAll() {
|
|
|
76016
76408
|
const registry = readRegistry();
|
|
76017
76409
|
const projects2 = Object.entries(registry.projects);
|
|
76018
76410
|
if (projects2.length === 0) {
|
|
76019
|
-
console.log(
|
|
76411
|
+
console.log(import_picocolors29.default.dim("No registered projects."));
|
|
76020
76412
|
return 0;
|
|
76021
76413
|
}
|
|
76022
76414
|
let stopped = 0;
|
|
@@ -76026,17 +76418,17 @@ async function stopAll() {
|
|
|
76026
76418
|
if (!hostUrl) continue;
|
|
76027
76419
|
const alive = await isHostHealthy(hostUrl);
|
|
76028
76420
|
if (!alive) continue;
|
|
76029
|
-
console.log(` Stopping ${
|
|
76421
|
+
console.log(` Stopping ${import_picocolors29.default.bold(name)} ${import_picocolors29.default.dim(`(${hostUrl})`)}`);
|
|
76030
76422
|
const code = await stopHost(entry2.root, hostUrl);
|
|
76031
76423
|
if (code === 0) stopped++;
|
|
76032
76424
|
else failed++;
|
|
76033
76425
|
}
|
|
76034
76426
|
if (stopped === 0 && failed === 0) {
|
|
76035
|
-
console.log(
|
|
76427
|
+
console.log(import_picocolors29.default.dim("No running source-intelligence hosts found."));
|
|
76036
76428
|
} else {
|
|
76037
76429
|
console.log(
|
|
76038
76430
|
`
|
|
76039
|
-
Stopped ${stopped} host(s)${failed > 0 ?
|
|
76431
|
+
Stopped ${stopped} host(s)${failed > 0 ? import_picocolors29.default.red(`, ${failed} failed`) : ""}.`
|
|
76040
76432
|
);
|
|
76041
76433
|
}
|
|
76042
76434
|
return failed > 0 ? 1 : 0;
|
|
@@ -76088,12 +76480,12 @@ function extractPort(hostUrl) {
|
|
|
76088
76480
|
|
|
76089
76481
|
// ../../packages/cli/src/commands/hosts.ts
|
|
76090
76482
|
init_cjs_shims();
|
|
76091
|
-
var
|
|
76483
|
+
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
76092
76484
|
async function runHosts() {
|
|
76093
76485
|
const registry = readRegistry();
|
|
76094
76486
|
const projects2 = Object.entries(registry.projects);
|
|
76095
76487
|
if (projects2.length === 0) {
|
|
76096
|
-
console.log(
|
|
76488
|
+
console.log(import_picocolors30.default.dim("No registered projects. Run `horus index` in a repo first."));
|
|
76097
76489
|
return 0;
|
|
76098
76490
|
}
|
|
76099
76491
|
const rows = [];
|
|
@@ -76110,29 +76502,29 @@ async function runHosts() {
|
|
|
76110
76502
|
});
|
|
76111
76503
|
const anyHost = rows.some((r) => r.hostUrl !== null);
|
|
76112
76504
|
if (!anyHost) {
|
|
76113
|
-
console.log(
|
|
76505
|
+
console.log(import_picocolors30.default.dim("No source-intelligence hosts found. Run `horus index` to start one."));
|
|
76114
76506
|
return 0;
|
|
76115
76507
|
}
|
|
76116
76508
|
console.log("");
|
|
76117
76509
|
for (const row of rows) {
|
|
76118
76510
|
if (row.hostUrl === null) continue;
|
|
76119
|
-
const status = row.healthy ?
|
|
76511
|
+
const status = row.healthy ? import_picocolors30.default.green("\u25CF running") : import_picocolors30.default.red("\u25CF stopped");
|
|
76120
76512
|
const port = extractPort2(row.hostUrl) ?? "?";
|
|
76121
76513
|
console.log(
|
|
76122
|
-
` ${status} ${
|
|
76514
|
+
` ${status} ${import_picocolors30.default.bold(row.name.padEnd(24))} port ${String(port).padEnd(6)} ${import_picocolors30.default.dim(row.root)}`
|
|
76123
76515
|
);
|
|
76124
76516
|
}
|
|
76125
76517
|
console.log("");
|
|
76126
76518
|
const noHost = rows.filter((r) => r.hostUrl === null);
|
|
76127
76519
|
if (noHost.length > 0) {
|
|
76128
76520
|
for (const row of noHost) {
|
|
76129
|
-
console.log(` ${
|
|
76521
|
+
console.log(` ${import_picocolors30.default.dim("\u25CB no host")} ${import_picocolors30.default.dim(row.name)}`);
|
|
76130
76522
|
}
|
|
76131
76523
|
console.log("");
|
|
76132
76524
|
}
|
|
76133
76525
|
const running = rows.filter((r) => r.healthy).length;
|
|
76134
76526
|
console.log(
|
|
76135
|
-
|
|
76527
|
+
import_picocolors30.default.dim(
|
|
76136
76528
|
`${running} running \xB7 horus stop to reap \xB7 horus stop --all to stop everything`
|
|
76137
76529
|
)
|
|
76138
76530
|
);
|
|
@@ -76149,12 +76541,12 @@ function extractPort2(hostUrl) {
|
|
|
76149
76541
|
|
|
76150
76542
|
// ../../packages/cli/src/commands/doctor.ts
|
|
76151
76543
|
init_cjs_shims();
|
|
76152
|
-
var
|
|
76544
|
+
var import_picocolors31 = __toESM(require_picocolors(), 1);
|
|
76153
76545
|
var DEFAULT_DB_URL4 = "postgresql://horus:horus@localhost:5433/horus";
|
|
76154
76546
|
function mark2(status) {
|
|
76155
|
-
if (status === "pass") return
|
|
76156
|
-
if (status === "warn") return
|
|
76157
|
-
return
|
|
76547
|
+
if (status === "pass") return import_picocolors31.default.green("\u2713");
|
|
76548
|
+
if (status === "warn") return import_picocolors31.default.yellow("~");
|
|
76549
|
+
return import_picocolors31.default.red("\u2717");
|
|
76158
76550
|
}
|
|
76159
76551
|
async function runDoctor(opts) {
|
|
76160
76552
|
const cwd = opts?.cwd ?? process.cwd();
|
|
@@ -76381,11 +76773,11 @@ async function runDoctor(opts) {
|
|
|
76381
76773
|
write(JSON.stringify(output, null, 2));
|
|
76382
76774
|
return hasFailure ? 1 : 0;
|
|
76383
76775
|
}
|
|
76384
|
-
write(
|
|
76776
|
+
write(import_picocolors31.default.bold("\nHorus readiness check\n"));
|
|
76385
76777
|
for (const check of checks) {
|
|
76386
|
-
write(` ${mark2(check.status)} ${
|
|
76778
|
+
write(` ${mark2(check.status)} ${import_picocolors31.default.bold(check.label.padEnd(26))} ${import_picocolors31.default.dim(check.detail)}`);
|
|
76387
76779
|
if (check.next) {
|
|
76388
|
-
write(` ${
|
|
76780
|
+
write(` ${import_picocolors31.default.dim("\u2192 " + check.next)}`);
|
|
76389
76781
|
}
|
|
76390
76782
|
}
|
|
76391
76783
|
write("");
|
|
@@ -76394,20 +76786,20 @@ async function runDoctor(opts) {
|
|
|
76394
76786
|
|
|
76395
76787
|
// ../../packages/cli/src/commands/providers-doctor.ts
|
|
76396
76788
|
init_cjs_shims();
|
|
76397
|
-
var
|
|
76398
|
-
var
|
|
76789
|
+
var import_node_child_process8 = require("child_process");
|
|
76790
|
+
var import_picocolors32 = __toESM(require_picocolors(), 1);
|
|
76399
76791
|
function statusMark(status) {
|
|
76400
|
-
if (status === "ready") return
|
|
76401
|
-
if (status === "installed") return
|
|
76402
|
-
return
|
|
76792
|
+
if (status === "ready") return import_picocolors32.default.green("\u2713");
|
|
76793
|
+
if (status === "installed") return import_picocolors32.default.yellow("~");
|
|
76794
|
+
return import_picocolors32.default.red("\u2717");
|
|
76403
76795
|
}
|
|
76404
76796
|
function statusLabel(status) {
|
|
76405
|
-
if (status === "ready") return
|
|
76406
|
-
if (status === "installed") return
|
|
76407
|
-
return
|
|
76797
|
+
if (status === "ready") return import_picocolors32.default.green("ready");
|
|
76798
|
+
if (status === "installed") return import_picocolors32.default.yellow("installed (not configured)");
|
|
76799
|
+
return import_picocolors32.default.dim("not found on PATH");
|
|
76408
76800
|
}
|
|
76409
76801
|
function detectBinary(id) {
|
|
76410
|
-
const result = (0,
|
|
76802
|
+
const result = (0, import_node_child_process8.spawnSync)(id, ["--version"], { stdio: "pipe", timeout: 2e3 });
|
|
76411
76803
|
if (result.error) {
|
|
76412
76804
|
return { id, status: "unavailable", detail: `${id}: command not found` };
|
|
76413
76805
|
}
|
|
@@ -76421,7 +76813,7 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
76421
76813
|
const registry = opts?.registry ?? DEFAULT_LOCAL_PROVIDER_REGISTRY;
|
|
76422
76814
|
const write = opts?.write ?? ((line2) => console.log(line2));
|
|
76423
76815
|
const results = buildProviderResults(registry, opts?._detect);
|
|
76424
|
-
write(
|
|
76816
|
+
write(import_picocolors32.default.bold("\nLocal AI providers\n"));
|
|
76425
76817
|
for (const result of results) {
|
|
76426
76818
|
const descriptor = registry.get(result.id);
|
|
76427
76819
|
const name = descriptor?.displayName ?? result.id;
|
|
@@ -76429,22 +76821,37 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
76429
76821
|
` ${statusMark(result.status)} ${result.id.padEnd(8)} ${name.padEnd(22)} ${statusLabel(result.status)}`
|
|
76430
76822
|
);
|
|
76431
76823
|
if (result.status !== "ready" && result.detail) {
|
|
76432
|
-
write(` ${
|
|
76824
|
+
write(` ${import_picocolors32.default.dim("\u2192 " + result.detail)}`);
|
|
76433
76825
|
}
|
|
76434
76826
|
}
|
|
76435
76827
|
write("");
|
|
76436
|
-
write(
|
|
76437
|
-
|
|
76438
|
-
if (
|
|
76828
|
+
write(import_picocolors32.default.bold("Cloud AI providers\n"));
|
|
76829
|
+
let source = null;
|
|
76830
|
+
if (opts?._anthropicKey !== void 0) {
|
|
76831
|
+
source = opts._anthropicKey ? "env" : null;
|
|
76832
|
+
} else {
|
|
76833
|
+
try {
|
|
76834
|
+
const config = await loadConfig(opts?.config);
|
|
76835
|
+
const ai = resolveAiSettings(config);
|
|
76836
|
+
if (ai.anthropicApiKey) source = ai.anthropicKeyFromConfig ? "config" : "env";
|
|
76837
|
+
} catch {
|
|
76838
|
+
if (process.env["ANTHROPIC_API_KEY"]) source = "env";
|
|
76839
|
+
}
|
|
76840
|
+
}
|
|
76841
|
+
if (source === "config") {
|
|
76439
76842
|
write(
|
|
76440
|
-
` ${
|
|
76843
|
+
` ${import_picocolors32.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.green("configured (.horus/config.json)")}`
|
|
76844
|
+
);
|
|
76845
|
+
} else if (source === "env") {
|
|
76846
|
+
write(
|
|
76847
|
+
` ${import_picocolors32.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.green("configured (ANTHROPIC_API_KEY env)")}`
|
|
76441
76848
|
);
|
|
76442
76849
|
} else {
|
|
76443
76850
|
write(
|
|
76444
|
-
` ${
|
|
76851
|
+
` ${import_picocolors32.default.red("\u2717")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.dim("not configured")}`
|
|
76445
76852
|
);
|
|
76446
76853
|
write(
|
|
76447
|
-
` ${
|
|
76854
|
+
` ${import_picocolors32.default.dim("\u2192 run `horus connect ai` (or set ANTHROPIC_API_KEY) to enable `horus investigate --ai`")}`
|
|
76448
76855
|
);
|
|
76449
76856
|
}
|
|
76450
76857
|
write("");
|
|
@@ -76455,7 +76862,7 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
76455
76862
|
init_cjs_shims();
|
|
76456
76863
|
var import_node_fs8 = require("fs");
|
|
76457
76864
|
var import_node_path10 = require("path");
|
|
76458
|
-
var
|
|
76865
|
+
var import_picocolors33 = __toESM(require_picocolors(), 1);
|
|
76459
76866
|
function configTemplate(name, repoPath) {
|
|
76460
76867
|
return `export default {
|
|
76461
76868
|
database: {
|
|
@@ -76507,40 +76914,40 @@ async function runGenerateConfig(opts) {
|
|
|
76507
76914
|
const name = opts.name ?? defaults?.name ?? "my-project";
|
|
76508
76915
|
const repoPath = opts.repo ?? defaults?.repoPath ?? `/path/to/${name}`;
|
|
76509
76916
|
if ((0, import_node_fs8.existsSync)(outPath) && !opts.force) {
|
|
76510
|
-
log(`${
|
|
76511
|
-
log(
|
|
76917
|
+
log(`${import_picocolors33.default.red("\u2717")} ${outPath} already exists`);
|
|
76918
|
+
log(import_picocolors33.default.dim(" pass --force to overwrite"));
|
|
76512
76919
|
return 1;
|
|
76513
76920
|
}
|
|
76514
76921
|
try {
|
|
76515
76922
|
(0, import_node_fs8.mkdirSync)((0, import_node_path10.dirname)(outPath), { recursive: true });
|
|
76516
76923
|
(0, import_node_fs8.writeFileSync)(outPath, configTemplate(name, repoPath), "utf8");
|
|
76517
76924
|
} catch (err) {
|
|
76518
|
-
log(`${
|
|
76925
|
+
log(`${import_picocolors33.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
|
|
76519
76926
|
return 1;
|
|
76520
76927
|
}
|
|
76521
|
-
log(`${
|
|
76522
|
-
log(
|
|
76523
|
-
log(
|
|
76928
|
+
log(`${import_picocolors33.default.green("\u2713")} Created ${outPath}`);
|
|
76929
|
+
log(import_picocolors33.default.dim(` project: ${name}`));
|
|
76930
|
+
log(import_picocolors33.default.dim(` repo: ${repoPath}`));
|
|
76524
76931
|
if (hasLocalConfig && localConfigPath2 != null) {
|
|
76525
76932
|
log("");
|
|
76526
|
-
log(
|
|
76933
|
+
log(import_picocolors33.default.yellow("Note:") + ` an initialized Horus project config exists at ${localConfigPath2}`);
|
|
76527
76934
|
log(" \u2022 .horus/config.json \u2014 project config used by `horus investigate` from this repo");
|
|
76528
76935
|
log(" \u2022 horus.config.js \u2014 standalone/global config used with `horus doctor --config <path>`");
|
|
76529
|
-
log(
|
|
76936
|
+
log(import_picocolors33.default.dim(` next: review ${outPath} and copy/adapt it as needed`));
|
|
76530
76937
|
} else {
|
|
76531
|
-
log(
|
|
76938
|
+
log(import_picocolors33.default.dim(` next: horus doctor --config ${outPath}`));
|
|
76532
76939
|
}
|
|
76533
76940
|
return 0;
|
|
76534
76941
|
}
|
|
76535
76942
|
|
|
76536
76943
|
// ../../packages/cli/src/commands/readiness.ts
|
|
76537
76944
|
init_cjs_shims();
|
|
76538
|
-
var
|
|
76945
|
+
var import_picocolors34 = __toESM(require_picocolors(), 1);
|
|
76539
76946
|
var DEFAULT_DB_URL5 = "postgresql://horus:horus@localhost:5433/horus";
|
|
76540
76947
|
function mark3(status) {
|
|
76541
|
-
if (status === "pass") return
|
|
76542
|
-
if (status === "warn") return
|
|
76543
|
-
return
|
|
76948
|
+
if (status === "pass") return import_picocolors34.default.green("\u2713");
|
|
76949
|
+
if (status === "warn") return import_picocolors34.default.yellow("~");
|
|
76950
|
+
return import_picocolors34.default.red("\u2717");
|
|
76544
76951
|
}
|
|
76545
76952
|
async function runReadiness(opts) {
|
|
76546
76953
|
const cwd = opts?.cwd ?? process.cwd();
|
|
@@ -76695,20 +77102,20 @@ async function runReadiness(opts) {
|
|
|
76695
77102
|
}
|
|
76696
77103
|
const blockingChecks = checks.filter((c) => c.blocking);
|
|
76697
77104
|
const optionalChecks = checks.filter((c) => !c.blocking);
|
|
76698
|
-
write(
|
|
76699
|
-
write(
|
|
77105
|
+
write(import_picocolors34.default.bold("\nHorus release readiness\n"));
|
|
77106
|
+
write(import_picocolors34.default.bold(" Blocking"));
|
|
76700
77107
|
for (const check of blockingChecks) {
|
|
76701
|
-
write(` ${mark3(check.status)} ${
|
|
77108
|
+
write(` ${mark3(check.status)} ${import_picocolors34.default.bold(check.label.padEnd(22))} ${import_picocolors34.default.dim(check.detail)}`);
|
|
76702
77109
|
if (check.next) {
|
|
76703
|
-
write(` ${
|
|
77110
|
+
write(` ${import_picocolors34.default.dim("\u2192 " + check.next)}`);
|
|
76704
77111
|
}
|
|
76705
77112
|
}
|
|
76706
77113
|
write("");
|
|
76707
|
-
write(
|
|
77114
|
+
write(import_picocolors34.default.bold(" Optional"));
|
|
76708
77115
|
for (const check of optionalChecks) {
|
|
76709
|
-
write(` ${mark3(check.status)} ${
|
|
77116
|
+
write(` ${mark3(check.status)} ${import_picocolors34.default.bold(check.label.padEnd(22))} ${import_picocolors34.default.dim(check.detail)}`);
|
|
76710
77117
|
if (check.next) {
|
|
76711
|
-
write(` ${
|
|
77118
|
+
write(` ${import_picocolors34.default.dim("\u2192 " + check.next)}`);
|
|
76712
77119
|
}
|
|
76713
77120
|
}
|
|
76714
77121
|
write("");
|
|
@@ -76716,21 +77123,21 @@ async function runReadiness(opts) {
|
|
|
76716
77123
|
const optionalWarns = optionalChecks.filter((c) => c.status === "warn").length;
|
|
76717
77124
|
if (blockingFails.length === 0) {
|
|
76718
77125
|
if (optionalWarns === 0) {
|
|
76719
|
-
write(
|
|
77126
|
+
write(import_picocolors34.default.green(" Ready for demo/release."));
|
|
76720
77127
|
} else {
|
|
76721
77128
|
write(
|
|
76722
|
-
|
|
77129
|
+
import_picocolors34.default.yellow(
|
|
76723
77130
|
` Ready for a basic demo. ${optionalWarns} optional item(s) not configured \u2014 investigation evidence will be limited.`
|
|
76724
77131
|
)
|
|
76725
77132
|
);
|
|
76726
77133
|
}
|
|
76727
77134
|
} else {
|
|
76728
77135
|
write(
|
|
76729
|
-
|
|
77136
|
+
import_picocolors34.default.red(
|
|
76730
77137
|
` Not ready. ${blockingFails.length} blocking item(s) must be resolved before demo/release.`
|
|
76731
77138
|
)
|
|
76732
77139
|
);
|
|
76733
|
-
write(
|
|
77140
|
+
write(import_picocolors34.default.dim(" Re-run `horus readiness` after resolving the items above."));
|
|
76734
77141
|
}
|
|
76735
77142
|
write("");
|
|
76736
77143
|
return blockingFails.length > 0 ? 1 : 0;
|
|
@@ -76780,8 +77187,8 @@ Examples:
|
|
|
76780
77187
|
process.exitCode = await runReadiness({ config: opts.config });
|
|
76781
77188
|
});
|
|
76782
77189
|
program2.command("connect <type>").description(
|
|
76783
|
-
"Add or update a
|
|
76784
|
-
).option("--env <name>", "target environment (default: first environment in config)").option("--url <url>", "connector URL or connection string (Redis with auth: redis://:password@host:6379)").option("--username <user>", "username (elasticsearch / grafana)").option("--password <pass>", "password (elasticsearch / grafana; for Redis embed in --url)").option("--index-pattern <pattern>", "Elasticsearch index pattern (required for elasticsearch)").option("--service <name>", "service name scope for log queries").option("--database <name>", "database name (required for mongodb)").option("--collections <list>", "comma-separated collection allowlist (mongodb)").option("--dashboard <uid>", "default dashboard uid (grafana)").option(
|
|
77190
|
+
"Add or update a connector (elasticsearch / mongodb / grafana / redis / ai) in .horus/config.json"
|
|
77191
|
+
).option("--env <name>", "target environment (default: first environment in config)").option("--provider <name>", "AI provider for `connect ai` (anthropic / claude / codex / gemini)").option("--api-key <key>", "Anthropic API key for `connect ai`").option("--model <id>", "default model for `connect ai`").option("--url <url>", "connector URL or connection string (Redis with auth: redis://:password@host:6379)").option("--username <user>", "username (elasticsearch / grafana)").option("--password <pass>", "password (elasticsearch / grafana; for Redis embed in --url)").option("--index-pattern <pattern>", "Elasticsearch index pattern (required for elasticsearch)").option("--service <name>", "service name scope for log queries").option("--database <name>", "database name (required for mongodb)").option("--collections <list>", "comma-separated collection allowlist (mongodb)").option("--dashboard <uid>", "default dashboard uid (grafana)").option(
|
|
76785
77192
|
"--db <spec>",
|
|
76786
77193
|
"redis logical DB as db:role1,role2 (e.g. 0:cache,state or 1:bullmq,queues); repeatable",
|
|
76787
77194
|
(val, acc) => {
|
|
@@ -76804,6 +77211,9 @@ Examples:
|
|
|
76804
77211
|
db: opts.db,
|
|
76805
77212
|
bullmqPrefix: opts.bullmqPrefix,
|
|
76806
77213
|
scanDbs: opts.scanDbs,
|
|
77214
|
+
provider: opts.provider,
|
|
77215
|
+
apiKey: opts.apiKey,
|
|
77216
|
+
aiModel: opts.model,
|
|
76807
77217
|
noTest: opts.test === false
|
|
76808
77218
|
});
|
|
76809
77219
|
}
|
|
@@ -76841,13 +77251,14 @@ Examples:
|
|
|
76841
77251
|
process.exitCode = await runIndex(opts);
|
|
76842
77252
|
}
|
|
76843
77253
|
);
|
|
76844
|
-
program2.command("queues [name]").description("Show queue topology from source intelligence; --live adds real-time Redis/BullMQ state").option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via registry)").option("--project <name>", "filter edges by project").option("--live", "fetch real-time queue depths and failed-job counts from Redis/BullMQ").action(
|
|
77254
|
+
program2.command("queues [name]").description("Show queue topology from source intelligence; --live adds real-time Redis/BullMQ state").option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via registry)").option("--project <name>", "filter edges by project").option("--live", "fetch real-time queue depths and failed-job counts from Redis/BullMQ").option("--json", "output JSON").action(
|
|
76845
77255
|
async (name, opts) => {
|
|
76846
77256
|
process.exitCode = await runQueues(name, {
|
|
76847
77257
|
config: opts.config,
|
|
76848
77258
|
name: opts.name,
|
|
76849
77259
|
project: opts.project,
|
|
76850
|
-
live: opts.live
|
|
77260
|
+
live: opts.live,
|
|
77261
|
+
json: opts.json
|
|
76851
77262
|
});
|
|
76852
77263
|
}
|
|
76853
77264
|
);
|
|
@@ -77015,14 +77426,14 @@ Examples:
|
|
|
77015
77426
|
});
|
|
77016
77427
|
program2.command("logs [service]").description(
|
|
77017
77428
|
"Synthesize error evidence from logs (signatures, first/last, affected services); --raw for lines"
|
|
77018
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--since <when>", "time window, e.g. 24h, 7d, or an ISO date").option("--level <level>", "minimum level (with --raw): trace|debug|info|warn|error|fatal").option("--grep <text>", "match text in the message").option("--raw", "dump individual log lines instead of synthesized evidence").option("--limit <n>", "max records (with --raw)").action(
|
|
77429
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--since <when>", "time window, e.g. 24h, 7d, or an ISO date").option("--level <level>", "minimum level (with --raw): trace|debug|info|warn|error|fatal").option("--grep <text>", "match text in the message").option("--raw", "dump individual log lines instead of synthesized evidence (error+ by default)").option("--all-levels", "with --raw: show all severity levels, not just error+").option("--limit <n>", "max records (with --raw)").option("--json", "output JSON").action(
|
|
77019
77430
|
async (service, opts) => {
|
|
77020
77431
|
process.exitCode = await runLogs(service, opts);
|
|
77021
77432
|
}
|
|
77022
77433
|
);
|
|
77023
77434
|
program2.command("state").description(
|
|
77024
77435
|
"Surface application-state evidence from MongoDB (read-only, allowlisted): counts, staleness, anomalous statuses"
|
|
77025
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--stale-hours <n>", "staleness threshold in hours (default 24)").action(
|
|
77436
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--stale-hours <n>", "staleness threshold in hours (default 24)").option("--json", "output JSON").action(
|
|
77026
77437
|
async (opts) => {
|
|
77027
77438
|
process.exitCode = await runState(opts);
|
|
77028
77439
|
}
|