@merittdev/horus 0.1.18 → 0.1.20
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 +1950 -498
- 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.20" : "dev";
|
|
50318
50318
|
var PINNED_AXON_VERSION = "1.0.7";
|
|
50319
50319
|
var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
|
|
50320
50320
|
|
|
@@ -55776,6 +55776,16 @@ function buildLevelFilter(m, minLevel) {
|
|
|
55776
55776
|
);
|
|
55777
55777
|
return { terms: { [m.levelField]: stringLevels } };
|
|
55778
55778
|
}
|
|
55779
|
+
function buildTextMust(q, mapping = MERITT_FIELD_MAPPING) {
|
|
55780
|
+
if (q.text === void 0) return [{ match_all: {} }];
|
|
55781
|
+
if (q.broadText === true) {
|
|
55782
|
+
const fields = [mapping.messageField];
|
|
55783
|
+
if (mapping.messageFallbackField !== void 0) fields.push(mapping.messageFallbackField);
|
|
55784
|
+
fields.push("detail", "context.*");
|
|
55785
|
+
return [{ multi_match: { query: q.text, fields, type: "phrase", lenient: true } }];
|
|
55786
|
+
}
|
|
55787
|
+
return [{ match: { [mapping.messageField]: q.text } }];
|
|
55788
|
+
}
|
|
55779
55789
|
function buildSearchBody(q, mapping = MERITT_FIELD_MAPPING) {
|
|
55780
55790
|
const filters = [];
|
|
55781
55791
|
if (q.level !== void 0) {
|
|
@@ -55790,7 +55800,7 @@ function buildSearchBody(q, mapping = MERITT_FIELD_MAPPING) {
|
|
|
55790
55800
|
if (q.service !== void 0) {
|
|
55791
55801
|
filters.push({ term: { [serviceTermField(mapping)]: q.service } });
|
|
55792
55802
|
}
|
|
55793
|
-
const mustClause = q
|
|
55803
|
+
const mustClause = buildTextMust(q, mapping);
|
|
55794
55804
|
return {
|
|
55795
55805
|
query: {
|
|
55796
55806
|
bool: {
|
|
@@ -55815,12 +55825,16 @@ function buildErrorAggBody(q, field, mapping = MERITT_FIELD_MAPPING) {
|
|
|
55815
55825
|
if (q.service !== void 0) {
|
|
55816
55826
|
filters.push({ term: { [serviceTermField(mapping)]: q.service } });
|
|
55817
55827
|
}
|
|
55828
|
+
if (q.eventCode !== void 0) {
|
|
55829
|
+
filters.push({ term: { [signatureTermField(mapping)]: q.eventCode } });
|
|
55830
|
+
}
|
|
55818
55831
|
const aggField = field === mapping.eventCodeField ? signatureTermField(mapping) : `${field}.keyword`;
|
|
55819
55832
|
return {
|
|
55820
55833
|
size: 0,
|
|
55821
55834
|
query: {
|
|
55822
55835
|
bool: {
|
|
55823
|
-
filter: filters
|
|
55836
|
+
filter: filters,
|
|
55837
|
+
must: buildTextMust(q, mapping)
|
|
55824
55838
|
}
|
|
55825
55839
|
},
|
|
55826
55840
|
aggs: {
|
|
@@ -55886,10 +55900,10 @@ function normalizeHit(hit, mapping = MERITT_FIELD_MAPPING) {
|
|
|
55886
55900
|
if (typeof raw === "string") traceId = raw;
|
|
55887
55901
|
}
|
|
55888
55902
|
if (traceId === void 0) {
|
|
55889
|
-
const
|
|
55890
|
-
if (typeof
|
|
55903
|
+
const ctxRaw2 = src["context"];
|
|
55904
|
+
if (typeof ctxRaw2 === "string") {
|
|
55891
55905
|
try {
|
|
55892
|
-
const parsed = JSON.parse(
|
|
55906
|
+
const parsed = JSON.parse(ctxRaw2);
|
|
55893
55907
|
const tid = parsed["traceId"] ?? parsed["trace_id"];
|
|
55894
55908
|
if (typeof tid === "string") traceId = tid;
|
|
55895
55909
|
} catch {
|
|
@@ -55902,6 +55916,21 @@ function normalizeHit(hit, mapping = MERITT_FIELD_MAPPING) {
|
|
|
55902
55916
|
if (typeof raw === "string") requestId = raw;
|
|
55903
55917
|
}
|
|
55904
55918
|
const host = typeof src["hostname"] === "string" ? src["hostname"] : typeof src["host_name"] === "string" ? src["host_name"] : void 0;
|
|
55919
|
+
let context;
|
|
55920
|
+
const ctxRaw = src["context"];
|
|
55921
|
+
if (ctxRaw !== null && typeof ctxRaw === "object" && !Array.isArray(ctxRaw)) {
|
|
55922
|
+
context = ctxRaw;
|
|
55923
|
+
} else if (typeof ctxRaw === "string") {
|
|
55924
|
+
try {
|
|
55925
|
+
const parsed = JSON.parse(ctxRaw);
|
|
55926
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
55927
|
+
context = parsed;
|
|
55928
|
+
}
|
|
55929
|
+
} catch {
|
|
55930
|
+
}
|
|
55931
|
+
}
|
|
55932
|
+
const detailRaw = getField(src, "detail");
|
|
55933
|
+
const detail = typeof detailRaw === "string" ? detailRaw : detailRaw !== null && typeof detailRaw === "object" ? safeStringify(detailRaw) : void 0;
|
|
55905
55934
|
const index2 = typeof h["_index"] === "string" ? h["_index"] : "";
|
|
55906
55935
|
return {
|
|
55907
55936
|
timestamp: timestamp2,
|
|
@@ -55914,10 +55943,45 @@ function normalizeHit(hit, mapping = MERITT_FIELD_MAPPING) {
|
|
|
55914
55943
|
traceId,
|
|
55915
55944
|
requestId,
|
|
55916
55945
|
host,
|
|
55946
|
+
...context !== void 0 ? { context } : {},
|
|
55947
|
+
...detail !== void 0 ? { detail } : {},
|
|
55917
55948
|
index: index2,
|
|
55918
55949
|
raw: src
|
|
55919
55950
|
};
|
|
55920
55951
|
}
|
|
55952
|
+
function safeStringify(value) {
|
|
55953
|
+
try {
|
|
55954
|
+
return JSON.stringify(value);
|
|
55955
|
+
} catch {
|
|
55956
|
+
return void 0;
|
|
55957
|
+
}
|
|
55958
|
+
}
|
|
55959
|
+
function displayValue(v) {
|
|
55960
|
+
if (v === null || v === void 0) return void 0;
|
|
55961
|
+
if (typeof v === "string") return v;
|
|
55962
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
55963
|
+
return safeStringify(v);
|
|
55964
|
+
}
|
|
55965
|
+
function extractContextFields(record, limit = 16) {
|
|
55966
|
+
const out = [];
|
|
55967
|
+
const seen = /* @__PURE__ */ new Set();
|
|
55968
|
+
const push = (key, raw) => {
|
|
55969
|
+
if (out.length >= limit || seen.has(key)) return;
|
|
55970
|
+
const value = displayValue(raw);
|
|
55971
|
+
if (value === void 0 || value.trim() === "") return;
|
|
55972
|
+
seen.add(key);
|
|
55973
|
+
out.push({ key, value });
|
|
55974
|
+
};
|
|
55975
|
+
if (record.eventCode !== void 0) push("code", record.eventCode);
|
|
55976
|
+
if (record.context !== void 0) {
|
|
55977
|
+
for (const [k, v] of Object.entries(record.context)) {
|
|
55978
|
+
if (k === "traceId" || k === "trace_id") continue;
|
|
55979
|
+
push(k, v);
|
|
55980
|
+
}
|
|
55981
|
+
}
|
|
55982
|
+
if (record.detail !== void 0) push("detail", record.detail);
|
|
55983
|
+
return out;
|
|
55984
|
+
}
|
|
55921
55985
|
function hitsToRecords(searchResponse, mapping = MERITT_FIELD_MAPPING) {
|
|
55922
55986
|
const res = searchResponse;
|
|
55923
55987
|
const hits = res["hits"];
|
|
@@ -55989,7 +56053,7 @@ function buildErrorAnalysisBody(q, field = "event_code", mapping = MERITT_FIELD_
|
|
|
55989
56053
|
if (q.service !== void 0) {
|
|
55990
56054
|
filters.push({ term: { [serviceTermField(mapping)]: q.service } });
|
|
55991
56055
|
}
|
|
55992
|
-
const mustClause = q
|
|
56056
|
+
const mustClause = buildTextMust(q, mapping);
|
|
55993
56057
|
const svcTerm = serviceTermField(mapping);
|
|
55994
56058
|
const sigTerm = field === mapping.eventCodeField ? signatureTermField(mapping) : `${field}.keyword`;
|
|
55995
56059
|
return {
|
|
@@ -56006,7 +56070,7 @@ function buildErrorAnalysisBody(q, field = "event_code", mapping = MERITT_FIELD_
|
|
|
56006
56070
|
sample: {
|
|
56007
56071
|
top_hits: {
|
|
56008
56072
|
size: 1,
|
|
56009
|
-
_source: [mapping.messageField, "component", "log_logger"],
|
|
56073
|
+
_source: [mapping.messageField, "component", "log_logger", "context", "detail"],
|
|
56010
56074
|
sort: [{ [mapping.timestampField]: { order: "desc" } }]
|
|
56011
56075
|
}
|
|
56012
56076
|
}
|
|
@@ -56035,6 +56099,8 @@ function parseErrorAnalysis(resp, window2, messageField = "message") {
|
|
|
56035
56099
|
const sampleSrc = sampleArr[0]?.["_source"] ?? {};
|
|
56036
56100
|
const rawMsg = getField(sampleSrc, messageField);
|
|
56037
56101
|
const sampleMessage = typeof rawMsg === "string" ? rawMsg : typeof sampleSrc["message"] === "string" ? sampleSrc["message"] : void 0;
|
|
56102
|
+
const ctx = sampleSrc["context"];
|
|
56103
|
+
const sampleContext = ctx !== null && typeof ctx === "object" && !Array.isArray(ctx) ? ctx : void 0;
|
|
56038
56104
|
return {
|
|
56039
56105
|
key: String(b2["key"] ?? "") || "(none)",
|
|
56040
56106
|
count: typeof b2["doc_count"] === "number" ? b2["doc_count"] : 0,
|
|
@@ -56042,7 +56108,8 @@ function parseErrorAnalysis(resp, window2, messageField = "message") {
|
|
|
56042
56108
|
lastSeen: typeof last === "string" ? last : "",
|
|
56043
56109
|
services,
|
|
56044
56110
|
sampleMessage,
|
|
56045
|
-
sampleComponent: typeof sampleSrc["component"] === "string" ? sampleSrc["component"] : typeof sampleSrc["log_logger"] === "string" ? sampleSrc["log_logger"] : void 0
|
|
56111
|
+
sampleComponent: typeof sampleSrc["component"] === "string" ? sampleSrc["component"] : typeof sampleSrc["log_logger"] === "string" ? sampleSrc["log_logger"] : void 0,
|
|
56112
|
+
...sampleContext !== void 0 ? { sampleContext } : {}
|
|
56046
56113
|
};
|
|
56047
56114
|
});
|
|
56048
56115
|
const affectedServices = bucketsOf(aggs["affected_services"]).map(
|
|
@@ -57299,6 +57366,10 @@ var STARVATION_MIN = 10;
|
|
|
57299
57366
|
var OLD_JOB_WARN_MS = 5 * 6e4;
|
|
57300
57367
|
var OLD_JOB_HIGH_MS = 60 * 6e4;
|
|
57301
57368
|
var BREAKDOWN_PCT_THRESHOLD = 30;
|
|
57369
|
+
var STALE_FAILED_MS = 60 * 6e4;
|
|
57370
|
+
function isStaleFailure(ageMs) {
|
|
57371
|
+
return ageMs !== void 0 && ageMs > STALE_FAILED_MS;
|
|
57372
|
+
}
|
|
57302
57373
|
function fmtMs(ms) {
|
|
57303
57374
|
if (ms >= 864e5) return `${Math.floor(ms / 864e5)}d`;
|
|
57304
57375
|
if (ms >= 36e5) return `${Math.floor(ms / 36e5)}h`;
|
|
@@ -57344,12 +57415,20 @@ function analyzeQueueSignals(q) {
|
|
|
57344
57415
|
}
|
|
57345
57416
|
if (q.failed > FAILED_WARN) {
|
|
57346
57417
|
const severe = q.failed > 100;
|
|
57418
|
+
const stale = isStaleFailure(q.newestFailedAgeMs);
|
|
57419
|
+
const ageStr = q.newestFailedAgeMs !== void 0 ? ` (most recent ${fmtMs(q.newestFailedAgeMs)} ago${stale ? ", STALE" : ""})` : "";
|
|
57347
57420
|
signals.push({
|
|
57348
57421
|
queueName: q.queueName,
|
|
57349
57422
|
kind: "failed-spike",
|
|
57350
|
-
title: `${q.queueName}: ${q.failed} failed jobs${severe ? " (severe)" : ""}`,
|
|
57351
|
-
|
|
57352
|
-
|
|
57423
|
+
title: `${q.queueName}: ${q.failed} failed jobs${severe ? " (severe)" : ""}${ageStr}`,
|
|
57424
|
+
// A purely stale failure pile is less likely to be the live incident — hedge.
|
|
57425
|
+
relevance: stale ? 0.5 : severe ? 0.85 : 0.75,
|
|
57426
|
+
payload: {
|
|
57427
|
+
queueName: q.queueName,
|
|
57428
|
+
failed: q.failed,
|
|
57429
|
+
newestFailedAgeMs: q.newestFailedAgeMs ?? null,
|
|
57430
|
+
stale
|
|
57431
|
+
}
|
|
57353
57432
|
});
|
|
57354
57433
|
}
|
|
57355
57434
|
if (q.delayed > DELAYED_WARN) {
|
|
@@ -57366,17 +57445,22 @@ function analyzeQueueSignals(q) {
|
|
|
57366
57445
|
const sampleTotal = q.failedBreakdown.reduce((sum, b2) => sum + b2.count, 0);
|
|
57367
57446
|
const pct = Math.round(top.count / Math.max(sampleTotal, 1) * 100);
|
|
57368
57447
|
if (pct >= BREAKDOWN_PCT_THRESHOLD) {
|
|
57448
|
+
const stale = isStaleFailure(top.lastFailedAgeMs);
|
|
57449
|
+
const ageStr = top.lastFailedAgeMs !== void 0 ? `, last failed ${fmtMs(top.lastFailedAgeMs)} ago${stale ? " [STALE]" : ""}` : "";
|
|
57369
57450
|
signals.push({
|
|
57370
57451
|
queueName: q.queueName,
|
|
57371
57452
|
kind: "failed-breakdown",
|
|
57372
|
-
title: `${q.queueName}: ${pct}% of recently sampled failures (${top.count}/${sampleTotal} sampled) are "${top.reason}"`,
|
|
57373
|
-
|
|
57453
|
+
title: `${q.queueName}: ${pct}% of recently sampled failures (${top.count}/${sampleTotal} sampled) are "${top.reason}"${ageStr}`,
|
|
57454
|
+
// Demote a stale-only breakdown so a days-old error isn't read as live (HOR-217).
|
|
57455
|
+
relevance: stale ? 0.5 : 0.82,
|
|
57374
57456
|
payload: {
|
|
57375
57457
|
queueName: q.queueName,
|
|
57376
57458
|
topReason: top.reason,
|
|
57377
57459
|
topCount: top.count,
|
|
57378
57460
|
topPct: pct,
|
|
57379
57461
|
totalFailed: q.failed,
|
|
57462
|
+
topLastFailedAgeMs: top.lastFailedAgeMs ?? null,
|
|
57463
|
+
topStale: stale,
|
|
57380
57464
|
breakdown: q.failedBreakdown.slice(0, 3)
|
|
57381
57465
|
}
|
|
57382
57466
|
});
|
|
@@ -57482,14 +57566,34 @@ var BullMQRuntimeProvider = class {
|
|
|
57482
57566
|
this.client.queueKey(name, "failed"),
|
|
57483
57567
|
FAILED_SAMPLE
|
|
57484
57568
|
);
|
|
57485
|
-
const reasons = await
|
|
57569
|
+
const [reasons, finished] = await Promise.all([
|
|
57570
|
+
this.client.pipelineHget(name, failedJobIds, "failedReason"),
|
|
57571
|
+
this.client.pipelineHget(name, failedJobIds, "finishedOn")
|
|
57572
|
+
]);
|
|
57573
|
+
const now = Date.now();
|
|
57486
57574
|
const byReason = /* @__PURE__ */ new Map();
|
|
57487
|
-
|
|
57488
|
-
|
|
57575
|
+
let newestFinishedOverall = 0;
|
|
57576
|
+
for (let i = 0; i < failedJobIds.length; i++) {
|
|
57577
|
+
const r = reasons[i];
|
|
57578
|
+
if (r === null || r === void 0 || r === "") continue;
|
|
57489
57579
|
const key = r.split("\n")[0].trim().slice(0, 120);
|
|
57490
|
-
|
|
57580
|
+
const finRaw = finished[i];
|
|
57581
|
+
const fin = finRaw !== null && finRaw !== void 0 ? Number(finRaw) : NaN;
|
|
57582
|
+
const hasFin = Number.isFinite(fin) && fin > 0;
|
|
57583
|
+
const entry2 = byReason.get(key) ?? { count: 0, newestFinished: 0 };
|
|
57584
|
+
entry2.count += 1;
|
|
57585
|
+
if (hasFin && fin > entry2.newestFinished) entry2.newestFinished = fin;
|
|
57586
|
+
byReason.set(key, entry2);
|
|
57587
|
+
if (hasFin && fin > newestFinishedOverall) newestFinishedOverall = fin;
|
|
57588
|
+
}
|
|
57589
|
+
counts.failedBreakdown = [...byReason.entries()].map(([reason, v]) => ({
|
|
57590
|
+
reason,
|
|
57591
|
+
count: v.count,
|
|
57592
|
+
...v.newestFinished > 0 ? { lastFailedAgeMs: Math.max(0, now - v.newestFinished) } : {}
|
|
57593
|
+
})).sort((a, b2) => b2.count - a.count);
|
|
57594
|
+
if (newestFinishedOverall > 0) {
|
|
57595
|
+
counts.newestFailedAgeMs = Math.max(0, now - newestFinishedOverall);
|
|
57491
57596
|
}
|
|
57492
|
-
counts.failedBreakdown = [...byReason.entries()].map(([reason, count]) => ({ reason, count })).sort((a, b2) => b2.count - a.count);
|
|
57493
57597
|
}
|
|
57494
57598
|
return counts;
|
|
57495
57599
|
}
|
|
@@ -59087,7 +59191,7 @@ function sql(strings, ...params) {
|
|
|
59087
59191
|
return new SQL([new StringChunk(str)]);
|
|
59088
59192
|
}
|
|
59089
59193
|
sql2.raw = raw;
|
|
59090
|
-
function
|
|
59194
|
+
function join8(chunks, separator) {
|
|
59091
59195
|
const result = [];
|
|
59092
59196
|
for (const [i, chunk] of chunks.entries()) {
|
|
59093
59197
|
if (i > 0 && separator !== void 0) {
|
|
@@ -59097,7 +59201,7 @@ function sql(strings, ...params) {
|
|
|
59097
59201
|
}
|
|
59098
59202
|
return new SQL(result);
|
|
59099
59203
|
}
|
|
59100
|
-
sql2.join =
|
|
59204
|
+
sql2.join = join8;
|
|
59101
59205
|
function identifier(value) {
|
|
59102
59206
|
return new Name(value);
|
|
59103
59207
|
}
|
|
@@ -62688,7 +62792,7 @@ var PgSelectQueryBuilderBase = class extends TypedQueryBuilder {
|
|
|
62688
62792
|
return (table, on) => {
|
|
62689
62793
|
const baseTableName = this.tableName;
|
|
62690
62794
|
const tableName = getTableLikeName(table);
|
|
62691
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
62795
|
+
if (typeof tableName === "string" && this.config.joins?.some((join8) => join8.alias === tableName)) {
|
|
62692
62796
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
62693
62797
|
}
|
|
62694
62798
|
if (!this.isPartialSelect) {
|
|
@@ -63700,7 +63804,7 @@ var PgUpdateBase = class extends QueryPromise {
|
|
|
63700
63804
|
createJoin(joinType) {
|
|
63701
63805
|
return (table, on) => {
|
|
63702
63806
|
const tableName = getTableLikeName(table);
|
|
63703
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
63807
|
+
if (typeof tableName === "string" && this.config.joins.some((join8) => join8.alias === tableName)) {
|
|
63704
63808
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
63705
63809
|
}
|
|
63706
63810
|
if (typeof on === "function") {
|
|
@@ -63796,10 +63900,10 @@ var PgUpdateBase = class extends QueryPromise {
|
|
|
63796
63900
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
63797
63901
|
fields[tableName] = fromFields;
|
|
63798
63902
|
}
|
|
63799
|
-
for (const
|
|
63800
|
-
const tableName2 = getTableLikeName(
|
|
63801
|
-
if (typeof tableName2 === "string" && !is(
|
|
63802
|
-
const fromFields = this.getTableLikeFields(
|
|
63903
|
+
for (const join8 of this.config.joins) {
|
|
63904
|
+
const tableName2 = getTableLikeName(join8.table);
|
|
63905
|
+
if (typeof tableName2 === "string" && !is(join8.table, SQL)) {
|
|
63906
|
+
const fromFields = this.getTableLikeFields(join8.table);
|
|
63803
63907
|
fields[tableName2] = fromFields;
|
|
63804
63908
|
}
|
|
63805
63909
|
}
|
|
@@ -69850,6 +69954,53 @@ function classifyLogRelevance(sigKey, sigServices, seedTerms, inputService) {
|
|
|
69850
69954
|
relevanceReason: "no structural link to seed \u2014 ambient runtime noise"
|
|
69851
69955
|
};
|
|
69852
69956
|
}
|
|
69957
|
+
function detectEntityFields(context, limit = 2) {
|
|
69958
|
+
if (context === void 0) return [];
|
|
69959
|
+
const out = [];
|
|
69960
|
+
for (const [k, v] of Object.entries(context)) {
|
|
69961
|
+
if (v === null || typeof v !== "string" && typeof v !== "number") continue;
|
|
69962
|
+
const looksLikeId = /(^|_)ids?$/i.test(k) || /id$/.test(k) || /^(brand|order|user|customer|account|tenant|shop|merchant|product|seller)/i.test(k);
|
|
69963
|
+
if (looksLikeId) {
|
|
69964
|
+
out.push(k);
|
|
69965
|
+
if (out.length >= limit) break;
|
|
69966
|
+
}
|
|
69967
|
+
}
|
|
69968
|
+
return out;
|
|
69969
|
+
}
|
|
69970
|
+
async function reseedFromLogs(hint, input, deps) {
|
|
69971
|
+
if (!deps.logs || typeof deps.logs.searchLogs !== "function") return null;
|
|
69972
|
+
try {
|
|
69973
|
+
const from = logWindowFrom(input.since);
|
|
69974
|
+
const records = await deps.logs.searchLogs({
|
|
69975
|
+
text: hint,
|
|
69976
|
+
broadText: true,
|
|
69977
|
+
from,
|
|
69978
|
+
limit: 5,
|
|
69979
|
+
service: input.service
|
|
69980
|
+
});
|
|
69981
|
+
if (records.length === 0) return null;
|
|
69982
|
+
const candidates = [...new Set(records.map((r) => r.component).filter((c) => !!c))];
|
|
69983
|
+
for (const name of candidates) {
|
|
69984
|
+
const syms = await deps.code.searchSymbols(name, 5);
|
|
69985
|
+
if (syms.length > 0) {
|
|
69986
|
+
const rankedSeeds = rankSeeds(syms, [...new Set(tokenize(name))]);
|
|
69987
|
+
const first = records[0];
|
|
69988
|
+
const componentLabel = first?.component ?? "log entry";
|
|
69989
|
+
const codeLabel = first?.eventCode ? ` (${first.eventCode})` : "";
|
|
69990
|
+
const seedName = rankedSeeds[0]?.symbol.name ?? name;
|
|
69991
|
+
return {
|
|
69992
|
+
seeds: rankedSeeds.map((r) => r.symbol),
|
|
69993
|
+
note: `Hint matched no symbol; resolved via logs: ${componentLabel}${codeLabel} \u2192 ${seedName}`,
|
|
69994
|
+
...first?.component !== void 0 ? { component: first.component } : {},
|
|
69995
|
+
...first?.eventCode !== void 0 ? { eventCode: first.eventCode } : {}
|
|
69996
|
+
};
|
|
69997
|
+
}
|
|
69998
|
+
}
|
|
69999
|
+
return null;
|
|
70000
|
+
} catch {
|
|
70001
|
+
return null;
|
|
70002
|
+
}
|
|
70003
|
+
}
|
|
69853
70004
|
function queueFindingConfidence(opts) {
|
|
69854
70005
|
const { starvedCount, backloggedCount, failingCount } = opts;
|
|
69855
70006
|
return starvedCount > 0 && backloggedCount === 0 && failingCount === 0 ? 0.65 : 0.85;
|
|
@@ -69884,8 +70035,16 @@ async function investigate(input, deps) {
|
|
|
69884
70035
|
const rawSeeds = await code.searchSymbols(hint, 5);
|
|
69885
70036
|
const hintTokens = [...new Set(tokenize(hint))];
|
|
69886
70037
|
const ranked = rankSeeds(rawSeeds, hintTokens);
|
|
69887
|
-
|
|
69888
|
-
|
|
70038
|
+
let seeds = ranked.map((r) => r.symbol);
|
|
70039
|
+
let top = seeds[0];
|
|
70040
|
+
let logReseed = null;
|
|
70041
|
+
if (!top) {
|
|
70042
|
+
logReseed = await reseedFromLogs(hint, input, deps);
|
|
70043
|
+
if (logReseed !== null) {
|
|
70044
|
+
seeds = logReseed.seeds;
|
|
70045
|
+
top = seeds[0];
|
|
70046
|
+
}
|
|
70047
|
+
}
|
|
69889
70048
|
if (!top) {
|
|
69890
70049
|
const report2 = {
|
|
69891
70050
|
id: globalThis.crypto.randomUUID(),
|
|
@@ -69903,7 +70062,7 @@ async function investigate(input, deps) {
|
|
|
69903
70062
|
graph: { nodes: [], edges: [] },
|
|
69904
70063
|
confidence: 0,
|
|
69905
70064
|
nextActions: [
|
|
69906
|
-
`No symbols matched "${hint}". Try a more specific hint \u2014 an exact function, class, or file name.`
|
|
70065
|
+
deps.logs != null ? `No symbols matched "${hint}", and no recent error log matched it either. Try a more specific hint \u2014 an exact function, class, or file name \u2014 or widen --since.` : `No symbols matched "${hint}". Try a more specific hint \u2014 an exact function, class, or file name.`
|
|
69907
70066
|
]
|
|
69908
70067
|
};
|
|
69909
70068
|
const persistedId2 = await persist(db, input, report2);
|
|
@@ -70013,6 +70172,8 @@ async function investigate(input, deps) {
|
|
|
70013
70172
|
const logEvIds = [];
|
|
70014
70173
|
const directLogEvIds = [];
|
|
70015
70174
|
const ambientLogEvIds = [];
|
|
70175
|
+
const entityEvIds = [];
|
|
70176
|
+
const entitySummaries = [];
|
|
70016
70177
|
let logsCollected = false;
|
|
70017
70178
|
let logsCompatibilityError;
|
|
70018
70179
|
if (deps.logs) {
|
|
@@ -70078,6 +70239,49 @@ async function investigate(input, deps) {
|
|
|
70078
70239
|
if (relevanceClass === "direct") directLogEvIds.push(ev.id);
|
|
70079
70240
|
else ambientLogEvIds.push(ev.id);
|
|
70080
70241
|
}
|
|
70242
|
+
if (typeof deps.logs.aggregateErrors === "function") {
|
|
70243
|
+
const sigForEntities = analysis.signatures.find(
|
|
70244
|
+
(s) => detectEntityFields(s.sampleContext).length > 0
|
|
70245
|
+
);
|
|
70246
|
+
if (sigForEntities !== void 0) {
|
|
70247
|
+
for (const f of detectEntityFields(sigForEntities.sampleContext)) {
|
|
70248
|
+
try {
|
|
70249
|
+
const buckets = await deps.logs.aggregateErrors(
|
|
70250
|
+
{
|
|
70251
|
+
service: input.service,
|
|
70252
|
+
from,
|
|
70253
|
+
eventCode: sigForEntities.key !== "(none)" ? sigForEntities.key : void 0
|
|
70254
|
+
},
|
|
70255
|
+
`context.${f}`
|
|
70256
|
+
);
|
|
70257
|
+
if (buckets.length > 0) {
|
|
70258
|
+
const top3 = buckets.slice(0, 3).map((b2) => `${b2.key} (${b2.count}x)`).join(", ");
|
|
70259
|
+
const plus = buckets.length >= 20 ? "+" : "";
|
|
70260
|
+
const ev = mkEv(
|
|
70261
|
+
"log",
|
|
70262
|
+
`Distinct context.${f} for ${sigForEntities.key}: ${buckets.length}${plus} value(s) (top: ${top3})`.slice(
|
|
70263
|
+
0,
|
|
70264
|
+
180
|
|
70265
|
+
),
|
|
70266
|
+
{
|
|
70267
|
+
field: `context.${f}`,
|
|
70268
|
+
signature: sigForEntities.key,
|
|
70269
|
+
distinctShown: buckets.length,
|
|
70270
|
+
capped: buckets.length >= 20,
|
|
70271
|
+
top: buckets.slice(0, 10)
|
|
70272
|
+
},
|
|
70273
|
+
{},
|
|
70274
|
+
void 0,
|
|
70275
|
+
0.6
|
|
70276
|
+
);
|
|
70277
|
+
entityEvIds.push(ev.id);
|
|
70278
|
+
entitySummaries.push(`${buckets.length}${plus} ${f}`);
|
|
70279
|
+
}
|
|
70280
|
+
} catch {
|
|
70281
|
+
}
|
|
70282
|
+
}
|
|
70283
|
+
}
|
|
70284
|
+
}
|
|
70081
70285
|
}
|
|
70082
70286
|
} catch {
|
|
70083
70287
|
analysis = null;
|
|
@@ -70273,6 +70477,15 @@ async function investigate(input, deps) {
|
|
|
70273
70477
|
confidence: 1,
|
|
70274
70478
|
evidenceIds: [seedEv.id]
|
|
70275
70479
|
});
|
|
70480
|
+
if (logReseed !== null) {
|
|
70481
|
+
findings2.push({
|
|
70482
|
+
kind: "observation",
|
|
70483
|
+
title: logReseed.note,
|
|
70484
|
+
detail: "Seed was resolved by matching the hint against recent logs, not source search.",
|
|
70485
|
+
confidence: 0.6,
|
|
70486
|
+
evidenceIds: [seedEv.id]
|
|
70487
|
+
});
|
|
70488
|
+
}
|
|
70276
70489
|
if (ranked.length > 1) {
|
|
70277
70490
|
const candidates = ranked.slice(0, 4).map((r) => `${r.symbol.name} [${r.role}]`).join(", ");
|
|
70278
70491
|
findings2.push({
|
|
@@ -70359,6 +70572,14 @@ async function investigate(input, deps) {
|
|
|
70359
70572
|
});
|
|
70360
70573
|
}
|
|
70361
70574
|
}
|
|
70575
|
+
if (entityEvIds.length > 0) {
|
|
70576
|
+
findings2.push({
|
|
70577
|
+
kind: "observation",
|
|
70578
|
+
title: `Distinct failing entities: ${entitySummaries.join(" \xD7 ")}`,
|
|
70579
|
+
confidence: 0.6,
|
|
70580
|
+
evidenceIds: entityEvIds
|
|
70581
|
+
});
|
|
70582
|
+
}
|
|
70362
70583
|
if (stateAnalysis !== null && stateEvIds.length > 0) {
|
|
70363
70584
|
findings2.push({
|
|
70364
70585
|
kind: "anomaly",
|
|
@@ -72938,22 +73159,22 @@ function extractQueueGraph(input) {
|
|
|
72938
73159
|
}
|
|
72939
73160
|
}
|
|
72940
73161
|
const producers = [];
|
|
72941
|
-
for (const
|
|
72942
|
-
for (const m of
|
|
73162
|
+
for (const pc41 of input.producerClasses) {
|
|
73163
|
+
for (const m of pc41.content.matchAll(INJECT_QUEUE_RE)) {
|
|
72943
73164
|
const queue = m[1] ?? "";
|
|
72944
|
-
if (queue) producers.push({ queue, symbol:
|
|
73165
|
+
if (queue) producers.push({ queue, symbol: pc41.name, file: pc41.filePath });
|
|
72945
73166
|
}
|
|
72946
|
-
for (const m of
|
|
73167
|
+
for (const m of pc41.content.matchAll(NEW_BULL_RE)) {
|
|
72947
73168
|
if (m[2] !== "Queue") continue;
|
|
72948
73169
|
const queue = resolveArg(m[3] ?? "", constMap);
|
|
72949
73170
|
if (queue) {
|
|
72950
|
-
producers.push({ queue, symbol: (m[1] ??
|
|
73171
|
+
producers.push({ queue, symbol: (m[1] ?? pc41.name) || baseName(pc41.filePath), file: pc41.filePath });
|
|
72951
73172
|
}
|
|
72952
73173
|
}
|
|
72953
|
-
for (const m of
|
|
73174
|
+
for (const m of pc41.content.matchAll(QUEUE_NAME_CONST_RE)) {
|
|
72954
73175
|
const ident = m[1] ?? "";
|
|
72955
73176
|
const queue = m[2] ?? "";
|
|
72956
|
-
if (queue) producers.push({ queue, symbol: ident || baseName(
|
|
73177
|
+
if (queue) producers.push({ queue, symbol: ident || baseName(pc41.filePath), file: pc41.filePath });
|
|
72957
73178
|
}
|
|
72958
73179
|
}
|
|
72959
73180
|
const workers = [];
|
|
@@ -74120,9 +74341,19 @@ function printLiveTable(queues, staticNames = /* @__PURE__ */ new Set()) {
|
|
|
74120
74341
|
const age = formatAge(q.oldestWaitingMs);
|
|
74121
74342
|
console.log(import_picocolors4.default.dim(` oldest waiting: ${age}`));
|
|
74122
74343
|
}
|
|
74344
|
+
if (q.newestFailedAgeMs !== void 0) {
|
|
74345
|
+
const stale = q.newestFailedAgeMs > 36e5;
|
|
74346
|
+
const ageLine = ` most recent failure: ${formatAge(q.newestFailedAgeMs)} ago`;
|
|
74347
|
+
console.log(stale ? import_picocolors4.default.yellow(`${ageLine} [STALE]`) : import_picocolors4.default.dim(ageLine));
|
|
74348
|
+
}
|
|
74123
74349
|
if (q.failedBreakdown && q.failedBreakdown.length > 0) {
|
|
74124
|
-
for (const
|
|
74125
|
-
|
|
74350
|
+
for (const fb of q.failedBreakdown.slice(0, 3)) {
|
|
74351
|
+
let suffix = "";
|
|
74352
|
+
if (fb.lastFailedAgeMs !== void 0) {
|
|
74353
|
+
const stale = fb.lastFailedAgeMs > 36e5;
|
|
74354
|
+
suffix = import_picocolors4.default.dim(` (last ${formatAge(fb.lastFailedAgeMs)} ago)`) + (stale ? import_picocolors4.default.yellow(" [STALE]") : "");
|
|
74355
|
+
}
|
|
74356
|
+
console.log(import_picocolors4.default.red(` \u2717 [${fb.count}x] ${fb.reason}`) + suffix);
|
|
74126
74357
|
}
|
|
74127
74358
|
}
|
|
74128
74359
|
}
|
|
@@ -74137,7 +74368,480 @@ function formatAge(ms) {
|
|
|
74137
74368
|
|
|
74138
74369
|
// ../../packages/cli/src/commands/investigate.ts
|
|
74139
74370
|
init_cjs_shims();
|
|
74371
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
74372
|
+
|
|
74373
|
+
// ../../packages/cli/src/lib/cloud/context-store.ts
|
|
74374
|
+
init_cjs_shims();
|
|
74375
|
+
var import_node_fs6 = require("fs");
|
|
74376
|
+
var import_node_path8 = require("path");
|
|
74377
|
+
|
|
74378
|
+
// ../../packages/cli/src/lib/cloud/paths.ts
|
|
74379
|
+
init_cjs_shims();
|
|
74380
|
+
var import_node_path7 = require("path");
|
|
74381
|
+
var import_node_os2 = require("os");
|
|
74382
|
+
var CLOUD_CONFIG_FILE = "cloud.json";
|
|
74383
|
+
var AUTH_FILE = "auth.json";
|
|
74384
|
+
function cloudConfigPath(root) {
|
|
74385
|
+
return (0, import_node_path7.join)(root, HORUS_DIR, CLOUD_CONFIG_FILE);
|
|
74386
|
+
}
|
|
74387
|
+
function horusHome() {
|
|
74388
|
+
return process.env.HORUS_HOME ?? (0, import_node_path7.join)((0, import_node_os2.homedir)(), HORUS_DIR);
|
|
74389
|
+
}
|
|
74390
|
+
function authPath() {
|
|
74391
|
+
return (0, import_node_path7.join)(horusHome(), AUTH_FILE);
|
|
74392
|
+
}
|
|
74393
|
+
|
|
74394
|
+
// ../../packages/cli/src/lib/cloud/context-store.ts
|
|
74395
|
+
function readCloudConfig(root) {
|
|
74396
|
+
const p = cloudConfigPath(root);
|
|
74397
|
+
if (!(0, import_node_fs6.existsSync)(p)) return null;
|
|
74398
|
+
try {
|
|
74399
|
+
const parsed = JSON.parse((0, import_node_fs6.readFileSync)(p, "utf8"));
|
|
74400
|
+
if (parsed.context !== "local" && parsed.context !== "cloud") return null;
|
|
74401
|
+
return parsed;
|
|
74402
|
+
} catch {
|
|
74403
|
+
return null;
|
|
74404
|
+
}
|
|
74405
|
+
}
|
|
74406
|
+
function writeCloudConfig(root, cfg) {
|
|
74407
|
+
const p = cloudConfigPath(root);
|
|
74408
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path8.dirname)(p), { recursive: true });
|
|
74409
|
+
(0, import_node_fs6.writeFileSync)(p, JSON.stringify(cfg, null, 2) + "\n");
|
|
74410
|
+
}
|
|
74411
|
+
function clearCloudConfig(root) {
|
|
74412
|
+
const p = cloudConfigPath(root);
|
|
74413
|
+
if ((0, import_node_fs6.existsSync)(p)) (0, import_node_fs6.rmSync)(p);
|
|
74414
|
+
}
|
|
74415
|
+
function isCloudActive(cfg) {
|
|
74416
|
+
return !!cfg && cfg.context === "cloud" && !!cfg.project;
|
|
74417
|
+
}
|
|
74418
|
+
|
|
74419
|
+
// ../../packages/cli/src/lib/cloud/session.ts
|
|
74420
|
+
init_cjs_shims();
|
|
74421
|
+
|
|
74422
|
+
// ../../packages/cli/src/lib/cloud/api.ts
|
|
74423
|
+
init_cjs_shims();
|
|
74424
|
+
var DEFAULT_API_BASE_URL = "https://api.horus.dev";
|
|
74425
|
+
var CloudError = class extends Error {
|
|
74426
|
+
constructor(status, code, message) {
|
|
74427
|
+
super(message);
|
|
74428
|
+
this.status = status;
|
|
74429
|
+
this.code = code;
|
|
74430
|
+
this.name = "CloudError";
|
|
74431
|
+
}
|
|
74432
|
+
status;
|
|
74433
|
+
code;
|
|
74434
|
+
};
|
|
74435
|
+
var CloudOfflineError = class extends Error {
|
|
74436
|
+
constructor(message) {
|
|
74437
|
+
super(message);
|
|
74438
|
+
this.name = "CloudOfflineError";
|
|
74439
|
+
}
|
|
74440
|
+
};
|
|
74441
|
+
var CloudClient = class {
|
|
74442
|
+
constructor(baseUrl, token) {
|
|
74443
|
+
this.baseUrl = baseUrl;
|
|
74444
|
+
this.token = token;
|
|
74445
|
+
}
|
|
74446
|
+
baseUrl;
|
|
74447
|
+
token;
|
|
74448
|
+
async request(method, path, body) {
|
|
74449
|
+
const headers = {};
|
|
74450
|
+
if (this.token) headers.authorization = `Bearer ${this.token}`;
|
|
74451
|
+
if (body !== void 0) headers["content-type"] = "application/json";
|
|
74452
|
+
let res;
|
|
74453
|
+
try {
|
|
74454
|
+
res = await fetch(`${this.baseUrl.replace(/\/$/, "")}${path}`, {
|
|
74455
|
+
method,
|
|
74456
|
+
headers,
|
|
74457
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
74458
|
+
});
|
|
74459
|
+
} catch (err) {
|
|
74460
|
+
throw new CloudOfflineError(
|
|
74461
|
+
`Cannot reach Horus Cloud at ${this.baseUrl} (${err.message}).`
|
|
74462
|
+
);
|
|
74463
|
+
}
|
|
74464
|
+
if (res.status === 204) return void 0;
|
|
74465
|
+
const text2 = await res.text();
|
|
74466
|
+
const json2 = text2 ? JSON.parse(text2) : void 0;
|
|
74467
|
+
if (!res.ok) {
|
|
74468
|
+
const envelope = json2;
|
|
74469
|
+
throw new CloudError(
|
|
74470
|
+
res.status,
|
|
74471
|
+
envelope?.error?.code ?? "http_error",
|
|
74472
|
+
envelope?.error?.message ?? `Request failed (${res.status}).`
|
|
74473
|
+
);
|
|
74474
|
+
}
|
|
74475
|
+
return json2;
|
|
74476
|
+
}
|
|
74477
|
+
me() {
|
|
74478
|
+
return this.request("GET", "/v1/me");
|
|
74479
|
+
}
|
|
74480
|
+
context() {
|
|
74481
|
+
return this.request("GET", "/v1/context");
|
|
74482
|
+
}
|
|
74483
|
+
listTokens() {
|
|
74484
|
+
return this.request("GET", "/v1/cli/tokens");
|
|
74485
|
+
}
|
|
74486
|
+
revokeToken(id) {
|
|
74487
|
+
return this.request("DELETE", `/v1/cli/tokens/${id}`);
|
|
74488
|
+
}
|
|
74489
|
+
createInvestigation(projectId, body) {
|
|
74490
|
+
return this.request("POST", `/v1/projects/${projectId}/investigations`, body);
|
|
74491
|
+
}
|
|
74492
|
+
listInvestigations(projectId) {
|
|
74493
|
+
return this.request(
|
|
74494
|
+
"GET",
|
|
74495
|
+
`/v1/projects/${projectId}/investigations`
|
|
74496
|
+
);
|
|
74497
|
+
}
|
|
74498
|
+
getInvestigation(projectId, investigationId) {
|
|
74499
|
+
return this.request(
|
|
74500
|
+
"GET",
|
|
74501
|
+
`/v1/projects/${projectId}/investigations/${investigationId}`
|
|
74502
|
+
);
|
|
74503
|
+
}
|
|
74504
|
+
updateInvestigation(projectId, investigationId, body) {
|
|
74505
|
+
return this.request(
|
|
74506
|
+
"PATCH",
|
|
74507
|
+
`/v1/projects/${projectId}/investigations/${investigationId}`,
|
|
74508
|
+
body
|
|
74509
|
+
);
|
|
74510
|
+
}
|
|
74511
|
+
addInvestigationRepositories(projectId, investigationId, repositoryIds) {
|
|
74512
|
+
return this.request(
|
|
74513
|
+
"POST",
|
|
74514
|
+
`/v1/projects/${projectId}/investigations/${investigationId}/repositories`,
|
|
74515
|
+
{ repositoryIds }
|
|
74516
|
+
);
|
|
74517
|
+
}
|
|
74518
|
+
createEvidence(projectId, investigationId, body) {
|
|
74519
|
+
return this.request(
|
|
74520
|
+
"POST",
|
|
74521
|
+
`/v1/projects/${projectId}/investigations/${investigationId}/evidence`,
|
|
74522
|
+
body
|
|
74523
|
+
);
|
|
74524
|
+
}
|
|
74525
|
+
listEvidence(projectId, investigationId) {
|
|
74526
|
+
return this.request(
|
|
74527
|
+
"GET",
|
|
74528
|
+
`/v1/projects/${projectId}/investigations/${investigationId}/evidence`
|
|
74529
|
+
);
|
|
74530
|
+
}
|
|
74531
|
+
createAgentRun(projectId, investigationId, body) {
|
|
74532
|
+
return this.request(
|
|
74533
|
+
"POST",
|
|
74534
|
+
`/v1/projects/${projectId}/investigations/${investigationId}/agent-runs`,
|
|
74535
|
+
body
|
|
74536
|
+
);
|
|
74537
|
+
}
|
|
74538
|
+
};
|
|
74539
|
+
|
|
74540
|
+
// ../../packages/cli/src/lib/cloud/auth-store.ts
|
|
74541
|
+
init_cjs_shims();
|
|
74542
|
+
var import_node_fs7 = require("fs");
|
|
74543
|
+
function readAuth() {
|
|
74544
|
+
const p = authPath();
|
|
74545
|
+
if (!(0, import_node_fs7.existsSync)(p)) return null;
|
|
74546
|
+
try {
|
|
74547
|
+
const parsed = JSON.parse((0, import_node_fs7.readFileSync)(p, "utf8"));
|
|
74548
|
+
if (!parsed.token || !parsed.apiBaseUrl || !parsed.account) return null;
|
|
74549
|
+
return parsed;
|
|
74550
|
+
} catch {
|
|
74551
|
+
return null;
|
|
74552
|
+
}
|
|
74553
|
+
}
|
|
74554
|
+
function writeAuth(state) {
|
|
74555
|
+
(0, import_node_fs7.mkdirSync)(horusHome(), { recursive: true });
|
|
74556
|
+
const p = authPath();
|
|
74557
|
+
(0, import_node_fs7.writeFileSync)(p, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
74558
|
+
try {
|
|
74559
|
+
(0, import_node_fs7.chmodSync)(p, 384);
|
|
74560
|
+
} catch {
|
|
74561
|
+
}
|
|
74562
|
+
}
|
|
74563
|
+
function clearAuth() {
|
|
74564
|
+
const p = authPath();
|
|
74565
|
+
if ((0, import_node_fs7.existsSync)(p)) (0, import_node_fs7.rmSync)(p);
|
|
74566
|
+
}
|
|
74567
|
+
|
|
74568
|
+
// ../../packages/cli/src/lib/cloud/session.ts
|
|
74569
|
+
function resolveApiBaseUrl(explicit) {
|
|
74570
|
+
return explicit ?? process.env.HORUS_CLOUD_API_URL ?? readAuth()?.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
74571
|
+
}
|
|
74572
|
+
function repoRootOrCwd(cwd = process.cwd()) {
|
|
74573
|
+
return findRepoRoot(cwd) ?? cwd;
|
|
74574
|
+
}
|
|
74575
|
+
function authedClient() {
|
|
74576
|
+
const auth = readAuth();
|
|
74577
|
+
if (!auth) return null;
|
|
74578
|
+
return { client: new CloudClient(auth.apiBaseUrl, auth.token), auth };
|
|
74579
|
+
}
|
|
74580
|
+
|
|
74581
|
+
// ../../packages/cli/src/lib/cloud/investigation-sync.ts
|
|
74582
|
+
init_cjs_shims();
|
|
74583
|
+
var REPORT_EVIDENCE_TYPE = "note";
|
|
74584
|
+
var REPORT_EVIDENCE_SOURCE = "cli";
|
|
74585
|
+
var REPORT_EVIDENCE_TITLE = "Investigation report snapshot";
|
|
74586
|
+
var REPORT_EVIDENCE_KIND = "horus:report";
|
|
74587
|
+
var REPORT_CONTENT_FORMAT = "application/json";
|
|
74588
|
+
function idempotencyKey(reportId, suffix) {
|
|
74589
|
+
return `${reportId}:${suffix}`;
|
|
74590
|
+
}
|
|
74591
|
+
function clone(value) {
|
|
74592
|
+
return JSON.parse(JSON.stringify(value));
|
|
74593
|
+
}
|
|
74594
|
+
async function uploadInvestigationToCloud(client, cfg, report) {
|
|
74595
|
+
if (!cfg.project) {
|
|
74596
|
+
throw new Error("Cloud config is missing a linked project.");
|
|
74597
|
+
}
|
|
74598
|
+
const projectId = cfg.project.id;
|
|
74599
|
+
const repositoryIds = cfg.repository?.id ? [cfg.repository.id] : void 0;
|
|
74600
|
+
const investigation = await client.createInvestigation(projectId, {
|
|
74601
|
+
title: report.input.hint.slice(0, 500) || "Untitled investigation",
|
|
74602
|
+
hint: report.input.hint,
|
|
74603
|
+
repositoryIds,
|
|
74604
|
+
idempotencyKey: idempotencyKey(report.id, "investigation")
|
|
74605
|
+
});
|
|
74606
|
+
await client.createEvidence(projectId, investigation.id, {
|
|
74607
|
+
type: REPORT_EVIDENCE_TYPE,
|
|
74608
|
+
source: REPORT_EVIDENCE_SOURCE,
|
|
74609
|
+
title: REPORT_EVIDENCE_TITLE,
|
|
74610
|
+
content: JSON.stringify(report),
|
|
74611
|
+
contentFormat: REPORT_CONTENT_FORMAT,
|
|
74612
|
+
payload: { kind: REPORT_EVIDENCE_KIND, report: clone(report) },
|
|
74613
|
+
idempotencyKey: idempotencyKey(report.id, "report")
|
|
74614
|
+
});
|
|
74615
|
+
await client.createAgentRun(projectId, investigation.id, {
|
|
74616
|
+
repositoryId: cfg.repository?.id,
|
|
74617
|
+
status: "completed",
|
|
74618
|
+
cliVersion: process.env.npm_package_version ?? "0.0.0",
|
|
74619
|
+
summary: report.summary,
|
|
74620
|
+
idempotencyKey: idempotencyKey(report.id, "run")
|
|
74621
|
+
});
|
|
74622
|
+
await client.updateInvestigation(projectId, investigation.id, { status: "completed" });
|
|
74623
|
+
return { projectId, investigationId: investigation.id };
|
|
74624
|
+
}
|
|
74625
|
+
function findReportEvidence(evidence2) {
|
|
74626
|
+
const record = evidence2.find((e) => {
|
|
74627
|
+
const payload2 = e.payload;
|
|
74628
|
+
return e.source === REPORT_EVIDENCE_SOURCE && payload2?.kind === REPORT_EVIDENCE_KIND;
|
|
74629
|
+
});
|
|
74630
|
+
if (!record) return null;
|
|
74631
|
+
const payload = record.payload;
|
|
74632
|
+
if (payload?.report) return clone(payload.report);
|
|
74633
|
+
try {
|
|
74634
|
+
return JSON.parse(record.content);
|
|
74635
|
+
} catch {
|
|
74636
|
+
return null;
|
|
74637
|
+
}
|
|
74638
|
+
}
|
|
74639
|
+
async function fetchInvestigationReportFromCloud(client, cfg, investigationId) {
|
|
74640
|
+
if (!cfg.project) {
|
|
74641
|
+
throw new Error("Cloud config is missing a linked project.");
|
|
74642
|
+
}
|
|
74643
|
+
const projectId = cfg.project.id;
|
|
74644
|
+
await client.getInvestigation(projectId, investigationId);
|
|
74645
|
+
const evidence2 = await client.listEvidence(projectId, investigationId);
|
|
74646
|
+
return findReportEvidence(evidence2);
|
|
74647
|
+
}
|
|
74648
|
+
async function listCloudInvestigations(client, cfg) {
|
|
74649
|
+
if (!cfg.project) {
|
|
74650
|
+
throw new Error("Cloud config is missing a linked project.");
|
|
74651
|
+
}
|
|
74652
|
+
const result = await client.listInvestigations(cfg.project.id);
|
|
74653
|
+
return result.investigations;
|
|
74654
|
+
}
|
|
74655
|
+
|
|
74656
|
+
// ../../packages/cli/src/commands/context.ts
|
|
74657
|
+
init_cjs_shims();
|
|
74140
74658
|
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
74659
|
+
function resolveTriple(ctx, target) {
|
|
74660
|
+
const parts = target.split("/");
|
|
74661
|
+
if (parts.length !== 3) return null;
|
|
74662
|
+
const [orgSlug, wsSlug, projSlug] = parts;
|
|
74663
|
+
const organization = ctx.organizations.find((o) => o.slug === orgSlug);
|
|
74664
|
+
if (!organization) return null;
|
|
74665
|
+
const workspace = ctx.workspaces.find(
|
|
74666
|
+
(w) => w.slug === wsSlug && w.organizationId === organization.id
|
|
74667
|
+
);
|
|
74668
|
+
if (!workspace) return null;
|
|
74669
|
+
const project = ctx.projects.find(
|
|
74670
|
+
(p) => p.slug === projSlug && p.workspaceId === workspace.id
|
|
74671
|
+
);
|
|
74672
|
+
if (!project) return null;
|
|
74673
|
+
return {
|
|
74674
|
+
organization: { id: organization.id, slug: organization.slug },
|
|
74675
|
+
workspace: { id: workspace.id, slug: workspace.slug },
|
|
74676
|
+
project: { id: project.id, slug: project.slug }
|
|
74677
|
+
};
|
|
74678
|
+
}
|
|
74679
|
+
function tripleLabel(ctx, projectId) {
|
|
74680
|
+
const project = ctx.projects.find((p) => p.id === projectId);
|
|
74681
|
+
if (!project) return null;
|
|
74682
|
+
const ws = ctx.workspaces.find((w) => w.id === project.workspaceId);
|
|
74683
|
+
const org = ctx.organizations.find((o) => o.id === project.organizationId);
|
|
74684
|
+
if (!ws || !org) return null;
|
|
74685
|
+
return `${org.slug}/${ws.slug}/${project.slug}`;
|
|
74686
|
+
}
|
|
74687
|
+
async function runContextList(opts = {}) {
|
|
74688
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
74689
|
+
const cfg = readCloudConfig(root);
|
|
74690
|
+
const session = authedClient();
|
|
74691
|
+
const activeCloud = isCloudActive(cfg);
|
|
74692
|
+
console.log(` ${import_picocolors5.default.bold("CONTEXT".padEnd(44))} STORAGE`);
|
|
74693
|
+
const mark4 = (active) => active ? import_picocolors5.default.green("*") : " ";
|
|
74694
|
+
console.log(`${mark4(!activeCloud)} ${"local".padEnd(44)} ${import_picocolors5.default.dim("~/.horus local Postgres")}`);
|
|
74695
|
+
if (!session) {
|
|
74696
|
+
console.log("");
|
|
74697
|
+
console.log(import_picocolors5.default.dim(`Run ${import_picocolors5.default.bold("horus login")} to see cloud contexts you can use.`));
|
|
74698
|
+
return 0;
|
|
74699
|
+
}
|
|
74700
|
+
try {
|
|
74701
|
+
const ctx = await session.client.context();
|
|
74702
|
+
for (const project of ctx.projects) {
|
|
74703
|
+
const label = tripleLabel(ctx, project.id);
|
|
74704
|
+
if (!label) continue;
|
|
74705
|
+
const active = activeCloud && cfg?.project?.id === project.id;
|
|
74706
|
+
console.log(`${mark4(active)} ${label.padEnd(44)} ${import_picocolors5.default.dim("Horus Cloud")}`);
|
|
74707
|
+
}
|
|
74708
|
+
console.log("");
|
|
74709
|
+
console.log(import_picocolors5.default.dim("(* = active for this repo)"));
|
|
74710
|
+
return 0;
|
|
74711
|
+
} catch (err) {
|
|
74712
|
+
return reportCloudError(err);
|
|
74713
|
+
}
|
|
74714
|
+
}
|
|
74715
|
+
async function runContextUse(target, opts = {}) {
|
|
74716
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
74717
|
+
if (target === "local") {
|
|
74718
|
+
const cfg = readCloudConfig(root) ?? { context: "local" };
|
|
74719
|
+
writeCloudConfig(root, { ...cfg, context: "local" });
|
|
74720
|
+
console.log(
|
|
74721
|
+
`${import_picocolors5.default.green("\u2713")} Context for this repo is now ${import_picocolors5.default.bold("local")}. ` + import_picocolors5.default.dim("(local data preserved)")
|
|
74722
|
+
);
|
|
74723
|
+
return 0;
|
|
74724
|
+
}
|
|
74725
|
+
const session = authedClient();
|
|
74726
|
+
if (!session) {
|
|
74727
|
+
console.error(import_picocolors5.default.red(`Not logged in. Run ${import_picocolors5.default.bold("horus login")} first.`));
|
|
74728
|
+
return 1;
|
|
74729
|
+
}
|
|
74730
|
+
let ctx;
|
|
74731
|
+
try {
|
|
74732
|
+
ctx = await session.client.context();
|
|
74733
|
+
} catch (err) {
|
|
74734
|
+
return reportCloudError(err);
|
|
74735
|
+
}
|
|
74736
|
+
const resolved = resolveTriple(ctx, target);
|
|
74737
|
+
if (!resolved) {
|
|
74738
|
+
console.error(
|
|
74739
|
+
import_picocolors5.default.red(`You don't have access to ${import_picocolors5.default.bold(target)}.`) + `
|
|
74740
|
+
Ask an org admin for an invite, or run ${import_picocolors5.default.bold("horus context list")} to see available projects.`
|
|
74741
|
+
);
|
|
74742
|
+
return 1;
|
|
74743
|
+
}
|
|
74744
|
+
writeCloudConfig(root, {
|
|
74745
|
+
context: "cloud",
|
|
74746
|
+
organization: resolved.organization,
|
|
74747
|
+
workspace: resolved.workspace,
|
|
74748
|
+
project: resolved.project
|
|
74749
|
+
});
|
|
74750
|
+
console.log(
|
|
74751
|
+
`${import_picocolors5.default.green("\u2713")} Context for this repo is now ${import_picocolors5.default.bold(target)} ${import_picocolors5.default.dim("(cloud)")}.`
|
|
74752
|
+
);
|
|
74753
|
+
return 0;
|
|
74754
|
+
}
|
|
74755
|
+
function formatRelativeTime(iso) {
|
|
74756
|
+
if (!iso) return null;
|
|
74757
|
+
const then = new Date(iso).getTime();
|
|
74758
|
+
if (Number.isNaN(then)) return null;
|
|
74759
|
+
const secs = Math.max(0, Math.round((Date.now() - then) / 1e3));
|
|
74760
|
+
if (secs < 60) return "just now";
|
|
74761
|
+
const mins = Math.round(secs / 60);
|
|
74762
|
+
if (mins < 60) return `${mins}m ago`;
|
|
74763
|
+
const hours = Math.round(mins / 60);
|
|
74764
|
+
if (hours < 24) return `${hours}h ago`;
|
|
74765
|
+
const days = Math.round(hours / 24);
|
|
74766
|
+
return `${days}d ago`;
|
|
74767
|
+
}
|
|
74768
|
+
function syncMetaLines(project) {
|
|
74769
|
+
const lines = [];
|
|
74770
|
+
if (project.provider && project.provider !== "other") {
|
|
74771
|
+
lines.push(`${import_picocolors5.default.bold("Provider:")} ${project.provider}`);
|
|
74772
|
+
}
|
|
74773
|
+
if (project.remoteUrl) lines.push(`${import_picocolors5.default.bold("Remote:")} ${project.remoteUrl}`);
|
|
74774
|
+
if (project.defaultBranch) lines.push(`${import_picocolors5.default.bold("Branch:")} ${project.defaultBranch}`);
|
|
74775
|
+
const synced = formatRelativeTime(project.lastSyncedAt);
|
|
74776
|
+
lines.push(`${import_picocolors5.default.bold("Last sync:")} ${synced ?? import_picocolors5.default.dim("never")}`);
|
|
74777
|
+
return lines;
|
|
74778
|
+
}
|
|
74779
|
+
async function runContextShow(opts = {}) {
|
|
74780
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
74781
|
+
const cfg = readCloudConfig(root);
|
|
74782
|
+
if (!isCloudActive(cfg) || !cfg.project) {
|
|
74783
|
+
console.log(`${import_picocolors5.default.bold("Context:")} local`);
|
|
74784
|
+
console.log(`${import_picocolors5.default.bold("Storage:")} local Postgres ${import_picocolors5.default.dim("(~/.horus)")}`);
|
|
74785
|
+
console.log(
|
|
74786
|
+
import_picocolors5.default.dim(`Tip: ${import_picocolors5.default.bold("horus login")} then ${import_picocolors5.default.bold("horus context use <org>/<workspace>/<project>")} to use a cloud project.`)
|
|
74787
|
+
);
|
|
74788
|
+
return 0;
|
|
74789
|
+
}
|
|
74790
|
+
const triple = `${cfg.organization?.slug}/${cfg.workspace?.slug}/${cfg.project.slug}`;
|
|
74791
|
+
console.log(`${import_picocolors5.default.bold("Context:")} ${triple} ${import_picocolors5.default.dim("(cloud)")}`);
|
|
74792
|
+
const session = authedClient();
|
|
74793
|
+
if (!session) {
|
|
74794
|
+
console.log(`${import_picocolors5.default.bold("Account:")} ${import_picocolors5.default.dim("not logged in")}`);
|
|
74795
|
+
console.log(import_picocolors5.default.dim(`Showing cached context. Run ${import_picocolors5.default.bold("horus login")} for live project + sync details.`));
|
|
74796
|
+
return 0;
|
|
74797
|
+
}
|
|
74798
|
+
let ctx;
|
|
74799
|
+
try {
|
|
74800
|
+
ctx = await session.client.context();
|
|
74801
|
+
} catch (err) {
|
|
74802
|
+
console.log(`${import_picocolors5.default.bold("Account:")} ${session.auth.account.email}`);
|
|
74803
|
+
if (err instanceof CloudOfflineError) {
|
|
74804
|
+
console.log(import_picocolors5.default.dim("Offline \u2014 showing cached context; sync details unavailable."));
|
|
74805
|
+
return 0;
|
|
74806
|
+
}
|
|
74807
|
+
return reportCloudError(err);
|
|
74808
|
+
}
|
|
74809
|
+
const project = ctx.projects.find((p) => p.id === cfg.project?.id);
|
|
74810
|
+
if (!project) {
|
|
74811
|
+
console.log(`${import_picocolors5.default.bold("Account:")} ${session.auth.account.email}`);
|
|
74812
|
+
console.error(
|
|
74813
|
+
import_picocolors5.default.yellow(`This repo's stored project is no longer in your accessible context.`) + `
|
|
74814
|
+
Your access may have changed or the project was removed.
|
|
74815
|
+
Run ${import_picocolors5.default.bold("horus context list")} to see available projects, then ${import_picocolors5.default.bold("horus context use <org>/<workspace>/<project>")}.`
|
|
74816
|
+
);
|
|
74817
|
+
return 1;
|
|
74818
|
+
}
|
|
74819
|
+
const ws = ctx.workspaces.find((w) => w.id === project.workspaceId);
|
|
74820
|
+
const org = ctx.organizations.find((o) => o.id === project.organizationId);
|
|
74821
|
+
console.log(
|
|
74822
|
+
`${import_picocolors5.default.bold("Project:")} ${project.name}` + (ws && org ? ` \xB7 workspace ${ws.name} \xB7 org ${org.name}` : "")
|
|
74823
|
+
);
|
|
74824
|
+
for (const line2 of syncMetaLines(project)) console.log(line2);
|
|
74825
|
+
console.log(`${import_picocolors5.default.bold("Account:")} ${session.auth.account.email}`);
|
|
74826
|
+
console.log(`${import_picocolors5.default.bold("Sync to:")} ${triple} ${import_picocolors5.default.dim("(horus cloud sync)")}`);
|
|
74827
|
+
return 0;
|
|
74828
|
+
}
|
|
74829
|
+
function reportCloudError(err) {
|
|
74830
|
+
if (err instanceof CloudOfflineError) {
|
|
74831
|
+
console.error(import_picocolors5.default.red(err.message));
|
|
74832
|
+
return 1;
|
|
74833
|
+
}
|
|
74834
|
+
if (err instanceof CloudError && err.status === 401) {
|
|
74835
|
+
console.error(
|
|
74836
|
+
import_picocolors5.default.red("Your session expired.") + ` Run ${import_picocolors5.default.bold("horus login")} to continue. ` + import_picocolors5.default.dim("(local mode still works)")
|
|
74837
|
+
);
|
|
74838
|
+
return 1;
|
|
74839
|
+
}
|
|
74840
|
+
console.error(import_picocolors5.default.red(`Cloud request failed: ${err.message}`));
|
|
74841
|
+
return 1;
|
|
74842
|
+
}
|
|
74843
|
+
|
|
74844
|
+
// ../../packages/cli/src/commands/investigate.ts
|
|
74141
74845
|
function extractEvidenceExcerpt(e) {
|
|
74142
74846
|
if (!e.payload || typeof e.payload !== "object") return void 0;
|
|
74143
74847
|
const p = e.payload;
|
|
@@ -74195,37 +74899,37 @@ function renderStoredAIJudgment(judgment, write = (l) => console.log(l)) {
|
|
|
74195
74899
|
const sep = "\u2500".repeat(60);
|
|
74196
74900
|
write(`
|
|
74197
74901
|
${sep}`);
|
|
74198
|
-
write(
|
|
74902
|
+
write(import_picocolors6.default.bold("AI Judgment") + import_picocolors6.default.dim(` (confidence: ${(judgment.confidence * 100).toFixed(0)}%, provider: ${judgment.provider})`));
|
|
74199
74903
|
write(sep);
|
|
74200
74904
|
if (judgment.hypothesisJudgments && judgment.hypothesisJudgments.length > 0) {
|
|
74201
|
-
write(
|
|
74905
|
+
write(import_picocolors6.default.bold("Hypothesis verdicts:"));
|
|
74202
74906
|
for (const j of judgment.hypothesisJudgments) {
|
|
74203
|
-
const verdictColor = j.verdict === "supported" ?
|
|
74204
|
-
write(` ${verdictColor(`[${j.verdict}]`)} ${j.category}` +
|
|
74205
|
-
write(
|
|
74907
|
+
const verdictColor = j.verdict === "supported" ? import_picocolors6.default.green : j.verdict === "weakened" ? import_picocolors6.default.yellow : j.verdict === "eliminated" ? import_picocolors6.default.red : import_picocolors6.default.dim;
|
|
74908
|
+
write(` ${verdictColor(`[${j.verdict}]`)} ${j.category}` + import_picocolors6.default.dim(` (${(j.confidence * 100).toFixed(0)}%)`));
|
|
74909
|
+
write(import_picocolors6.default.dim(` ${j.rationale}`));
|
|
74206
74910
|
}
|
|
74207
74911
|
write("");
|
|
74208
74912
|
}
|
|
74209
74913
|
if (judgment.rootCauseAssessment) {
|
|
74210
74914
|
const rca = judgment.rootCauseAssessment;
|
|
74211
|
-
const uncertaintyColor = rca.uncertainty === "low" ?
|
|
74212
|
-
write(
|
|
74915
|
+
const uncertaintyColor = rca.uncertainty === "low" ? import_picocolors6.default.green : rca.uncertainty === "medium" ? import_picocolors6.default.yellow : import_picocolors6.default.red;
|
|
74916
|
+
write(import_picocolors6.default.bold("Root cause assessment:") + import_picocolors6.default.dim(` uncertainty: ${uncertaintyColor(rca.uncertainty)}`));
|
|
74213
74917
|
write(rca.summary);
|
|
74214
74918
|
if (rca.citedEvidenceIds.length > 0) {
|
|
74215
|
-
write(
|
|
74919
|
+
write(import_picocolors6.default.dim(` Cited: ${rca.citedEvidenceIds.join(", ")}`));
|
|
74216
74920
|
}
|
|
74217
74921
|
write("");
|
|
74218
74922
|
}
|
|
74219
|
-
write(
|
|
74220
|
-
write(
|
|
74923
|
+
write(import_picocolors6.default.bold("What:") + " " + judgment.what);
|
|
74924
|
+
write(import_picocolors6.default.bold("Why:") + " " + judgment.why);
|
|
74221
74925
|
if (judgment.whereNext.length > 0) {
|
|
74222
|
-
write(
|
|
74926
|
+
write(import_picocolors6.default.bold("Next steps:"));
|
|
74223
74927
|
for (const step of judgment.whereNext) {
|
|
74224
74928
|
write(` \u2022 ${step}`);
|
|
74225
74929
|
}
|
|
74226
74930
|
}
|
|
74227
74931
|
if (judgment.citations.length > 0) {
|
|
74228
|
-
write(
|
|
74932
|
+
write(import_picocolors6.default.dim(`
|
|
74229
74933
|
Cited evidence: ${judgment.citations.map((c) => c.evidenceId).join(", ")}`));
|
|
74230
74934
|
}
|
|
74231
74935
|
}
|
|
@@ -74250,13 +74954,25 @@ async function runInvestigate(hint, opts) {
|
|
|
74250
74954
|
try {
|
|
74251
74955
|
renv = resolveEnvironment(config, { project: projectName, env: opts.env });
|
|
74252
74956
|
} catch (err) {
|
|
74253
|
-
console.error(
|
|
74957
|
+
console.error(import_picocolors6.default.red(err.message));
|
|
74958
|
+
return 1;
|
|
74959
|
+
}
|
|
74960
|
+
const repoRoot = repoRootOrCwd(renv.path);
|
|
74961
|
+
const cloudCfg = readCloudConfig(repoRoot);
|
|
74962
|
+
const cloudActive = isCloudActive(cloudCfg);
|
|
74963
|
+
const cloudSession = cloudActive ? authedClient() : null;
|
|
74964
|
+
if (cloudActive && !cloudSession) {
|
|
74965
|
+
console.error(
|
|
74966
|
+
import_picocolors6.default.red(
|
|
74967
|
+
`This repo is linked to Horus Cloud but you are not logged in. Run ${import_picocolors6.default.bold("horus login")} first.`
|
|
74968
|
+
)
|
|
74969
|
+
);
|
|
74254
74970
|
return 1;
|
|
74255
74971
|
}
|
|
74256
74972
|
const code = codeForEnv(renv);
|
|
74257
74973
|
if (!code) {
|
|
74258
74974
|
console.error(
|
|
74259
|
-
|
|
74975
|
+
import_picocolors6.default.red(
|
|
74260
74976
|
`No source-intelligence connector configured for project "${renv.project}" / env "${renv.env}".`
|
|
74261
74977
|
)
|
|
74262
74978
|
);
|
|
@@ -74266,7 +74982,7 @@ async function runInvestigate(hint, opts) {
|
|
|
74266
74982
|
if (!health.ok) {
|
|
74267
74983
|
const sourceUrl = renv.repositories[0]?.sourceHostUrl ?? renv.repositories[0]?.axonHostUrl;
|
|
74268
74984
|
console.error(
|
|
74269
|
-
|
|
74985
|
+
import_picocolors6.default.red(
|
|
74270
74986
|
`Source-intelligence host unreachable for ${renv.project}/${renv.env}` + (sourceUrl ? ` (${sourceUrl}) \u2014 run: horus index` : " \u2014 run: horus index")
|
|
74271
74987
|
)
|
|
74272
74988
|
);
|
|
@@ -74311,7 +75027,7 @@ async function runInvestigate(hint, opts) {
|
|
|
74311
75027
|
model,
|
|
74312
75028
|
...ai.anthropicApiKey !== void 0 ? { apiKey: ai.anthropicApiKey } : {}
|
|
74313
75029
|
});
|
|
74314
|
-
console.log(
|
|
75030
|
+
console.log(import_picocolors6.default.dim(`[ai] model: ${model}`));
|
|
74315
75031
|
const narrativeInput = buildNarrativeInput(report);
|
|
74316
75032
|
const { output, fromProvider, degraded, validationErrors } = await renderNarrative(
|
|
74317
75033
|
narrativeInput,
|
|
@@ -74327,24 +75043,32 @@ async function runInvestigate(hint, opts) {
|
|
|
74327
75043
|
renderStoredAIJudgment(stored);
|
|
74328
75044
|
} else if (degraded) {
|
|
74329
75045
|
if (validationErrors && validationErrors.length > 0) {
|
|
74330
|
-
console.error(
|
|
75046
|
+
console.error(import_picocolors6.default.yellow(`[ai] structured validation incomplete \u2014 showing raw AI narrative`));
|
|
74331
75047
|
}
|
|
74332
75048
|
console.log("");
|
|
74333
|
-
console.log(
|
|
74334
|
-
console.log(
|
|
75049
|
+
console.log(import_picocolors6.default.dim("\u2500".repeat(60)));
|
|
75050
|
+
console.log(import_picocolors6.default.bold("AI narrative ") + import_picocolors6.default.yellow("(unstructured \u2014 not validated)"));
|
|
74335
75051
|
if (output.what?.trim()) {
|
|
74336
|
-
console.log(
|
|
75052
|
+
console.log(import_picocolors6.default.bold("\nWhat:"), output.what.trim());
|
|
74337
75053
|
}
|
|
74338
75054
|
if (output.why?.trim()) {
|
|
74339
|
-
console.log(
|
|
75055
|
+
console.log(import_picocolors6.default.bold("\nWhy:"), output.why.trim());
|
|
74340
75056
|
}
|
|
74341
75057
|
if (output.whereNext.length > 0) {
|
|
74342
|
-
console.log(
|
|
75058
|
+
console.log(import_picocolors6.default.bold("\nNext:"));
|
|
74343
75059
|
for (const a of output.whereNext) console.log(` - ${a}`);
|
|
74344
75060
|
}
|
|
74345
75061
|
} else {
|
|
74346
75062
|
const reason = classifyAIFailure(validationErrors?.[0]);
|
|
74347
|
-
console.error(
|
|
75063
|
+
console.error(import_picocolors6.default.yellow(`[ai] fallback to deterministic \u2014 ${reason}`));
|
|
75064
|
+
}
|
|
75065
|
+
}
|
|
75066
|
+
if (cloudActive && cloudSession && cloudCfg) {
|
|
75067
|
+
try {
|
|
75068
|
+
const refs = await uploadInvestigationToCloud(cloudSession.client, cloudCfg, report);
|
|
75069
|
+
console.log(import_picocolors6.default.dim(`[cloud] investigation saved: ${refs.investigationId}`));
|
|
75070
|
+
} catch (err) {
|
|
75071
|
+
return reportCloudError(err);
|
|
74348
75072
|
}
|
|
74349
75073
|
}
|
|
74350
75074
|
} finally {
|
|
@@ -74354,14 +75078,14 @@ async function runInvestigate(hint, opts) {
|
|
|
74354
75078
|
}
|
|
74355
75079
|
return 0;
|
|
74356
75080
|
} catch (err) {
|
|
74357
|
-
console.error(
|
|
75081
|
+
console.error(import_picocolors6.default.red(err.message));
|
|
74358
75082
|
return 1;
|
|
74359
75083
|
}
|
|
74360
75084
|
}
|
|
74361
75085
|
|
|
74362
75086
|
// ../../packages/cli/src/commands/changes.ts
|
|
74363
75087
|
init_cjs_shims();
|
|
74364
|
-
var
|
|
75088
|
+
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
74365
75089
|
var CHANGES_AI_CONTRACT = `Provide a clearly separated AI change-impact review with:
|
|
74366
75090
|
|
|
74367
75091
|
Highest-risk changes
|
|
@@ -74386,7 +75110,7 @@ async function runChanges(base2, compare, opts) {
|
|
|
74386
75110
|
const health = await code.health();
|
|
74387
75111
|
if (!health.ok) {
|
|
74388
75112
|
console.error(
|
|
74389
|
-
|
|
75113
|
+
import_picocolors7.default.red("Source-intelligence host unreachable \u2014 run: horus index")
|
|
74390
75114
|
);
|
|
74391
75115
|
return 1;
|
|
74392
75116
|
}
|
|
@@ -74408,20 +75132,20 @@ async function runChanges(base2, compare, opts) {
|
|
|
74408
75132
|
});
|
|
74409
75133
|
console.log("\n" + renderInterpretation(result));
|
|
74410
75134
|
if (!result.ok) {
|
|
74411
|
-
console.error(
|
|
75135
|
+
console.error(import_picocolors7.default.yellow(`[ai] ${result.warning}`));
|
|
74412
75136
|
}
|
|
74413
75137
|
}
|
|
74414
75138
|
}
|
|
74415
75139
|
return 0;
|
|
74416
75140
|
} catch (err) {
|
|
74417
|
-
console.error(
|
|
75141
|
+
console.error(import_picocolors7.default.red(err.message));
|
|
74418
75142
|
return 1;
|
|
74419
75143
|
}
|
|
74420
75144
|
}
|
|
74421
75145
|
|
|
74422
75146
|
// ../../packages/cli/src/commands/timeline.ts
|
|
74423
75147
|
init_cjs_shims();
|
|
74424
|
-
var
|
|
75148
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
74425
75149
|
var DEFAULT_SINCE = "7 days ago";
|
|
74426
75150
|
function resolveTimelineWindow(opts) {
|
|
74427
75151
|
const usingDefault = !opts.all && opts.since === void 0;
|
|
@@ -74451,7 +75175,7 @@ async function runTimeline(service, opts) {
|
|
|
74451
75175
|
try {
|
|
74452
75176
|
renv = resolveEnvironment(config, { project: opts.repo });
|
|
74453
75177
|
} catch (err) {
|
|
74454
|
-
console.error(
|
|
75178
|
+
console.error(import_picocolors8.default.red(err.message));
|
|
74455
75179
|
return 1;
|
|
74456
75180
|
}
|
|
74457
75181
|
const { code } = createConnectors(config);
|
|
@@ -74466,9 +75190,9 @@ async function runTimeline(service, opts) {
|
|
|
74466
75190
|
console.log(renderChangeTimeline(t));
|
|
74467
75191
|
if (usingDefaultWindow) {
|
|
74468
75192
|
console.log(
|
|
74469
|
-
|
|
75193
|
+
import_picocolors8.default.dim(
|
|
74470
75194
|
`
|
|
74471
|
-
Showing the last 7 days (default). Widen with ${
|
|
75195
|
+
Showing the last 7 days (default). Widen with ${import_picocolors8.default.bold('--since "30 days ago"')}, pin a range with ${import_picocolors8.default.bold("--since <when> --until <when>")}, or see everything with ${import_picocolors8.default.bold("--all")}.`
|
|
74472
75196
|
)
|
|
74473
75197
|
);
|
|
74474
75198
|
}
|
|
@@ -74485,20 +75209,20 @@ async function runTimeline(service, opts) {
|
|
|
74485
75209
|
});
|
|
74486
75210
|
console.log("\n" + renderInterpretation(result));
|
|
74487
75211
|
if (!result.ok) {
|
|
74488
|
-
console.error(
|
|
75212
|
+
console.error(import_picocolors8.default.yellow(`[ai] ${result.warning}`));
|
|
74489
75213
|
}
|
|
74490
75214
|
}
|
|
74491
75215
|
}
|
|
74492
75216
|
return 0;
|
|
74493
75217
|
} catch (err) {
|
|
74494
|
-
console.error(
|
|
75218
|
+
console.error(import_picocolors8.default.red(err.message));
|
|
74495
75219
|
return 1;
|
|
74496
75220
|
}
|
|
74497
75221
|
}
|
|
74498
75222
|
|
|
74499
75223
|
// ../../packages/cli/src/commands/what-changed.ts
|
|
74500
75224
|
init_cjs_shims();
|
|
74501
|
-
var
|
|
75225
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
74502
75226
|
var DEFAULT_SINCE2 = "7 days ago";
|
|
74503
75227
|
var WHAT_CHANGED_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
|
|
74504
75228
|
|
|
@@ -74522,7 +75246,7 @@ async function runWhatChanged(service, opts) {
|
|
|
74522
75246
|
try {
|
|
74523
75247
|
renv = resolveEnvironment(config, { project: opts.repo });
|
|
74524
75248
|
} catch (err) {
|
|
74525
|
-
console.error(
|
|
75249
|
+
console.error(import_picocolors9.default.red(err.message));
|
|
74526
75250
|
return 1;
|
|
74527
75251
|
}
|
|
74528
75252
|
const { code } = createConnectors(config);
|
|
@@ -74545,19 +75269,19 @@ async function runWhatChanged(service, opts) {
|
|
|
74545
75269
|
});
|
|
74546
75270
|
console.log("\n" + renderInterpretation(result));
|
|
74547
75271
|
if (!result.ok) {
|
|
74548
|
-
console.error(
|
|
75272
|
+
console.error(import_picocolors9.default.yellow(`[ai] ${result.warning}`));
|
|
74549
75273
|
}
|
|
74550
75274
|
}
|
|
74551
75275
|
return 0;
|
|
74552
75276
|
} catch (err) {
|
|
74553
|
-
console.error(
|
|
75277
|
+
console.error(import_picocolors9.default.red(err.message));
|
|
74554
75278
|
return 1;
|
|
74555
75279
|
}
|
|
74556
75280
|
}
|
|
74557
75281
|
|
|
74558
75282
|
// ../../packages/cli/src/commands/architecture.ts
|
|
74559
75283
|
init_cjs_shims();
|
|
74560
|
-
var
|
|
75284
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
74561
75285
|
var ARCHITECTURE_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
|
|
74562
75286
|
|
|
74563
75287
|
System summary
|
|
@@ -74586,7 +75310,7 @@ async function runArchitecture(opts) {
|
|
|
74586
75310
|
const health = await code.health();
|
|
74587
75311
|
if (!health.ok) {
|
|
74588
75312
|
console.error(
|
|
74589
|
-
|
|
75313
|
+
import_picocolors10.default.red("Source-intelligence host unreachable \u2014 run: horus index")
|
|
74590
75314
|
);
|
|
74591
75315
|
return 1;
|
|
74592
75316
|
}
|
|
@@ -74614,7 +75338,7 @@ async function runArchitecture(opts) {
|
|
|
74614
75338
|
});
|
|
74615
75339
|
console.log("\n" + renderInterpretation(result));
|
|
74616
75340
|
if (!result.ok) {
|
|
74617
|
-
console.error(
|
|
75341
|
+
console.error(import_picocolors10.default.yellow(`[ai] ${result.warning}`));
|
|
74618
75342
|
}
|
|
74619
75343
|
}
|
|
74620
75344
|
}
|
|
@@ -74623,14 +75347,14 @@ async function runArchitecture(opts) {
|
|
|
74623
75347
|
}
|
|
74624
75348
|
return 0;
|
|
74625
75349
|
} catch (err) {
|
|
74626
|
-
console.error(
|
|
75350
|
+
console.error(import_picocolors10.default.red(err.message));
|
|
74627
75351
|
return 1;
|
|
74628
75352
|
}
|
|
74629
75353
|
}
|
|
74630
75354
|
|
|
74631
75355
|
// ../../packages/cli/src/commands/blast-radius.ts
|
|
74632
75356
|
init_cjs_shims();
|
|
74633
|
-
var
|
|
75357
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
74634
75358
|
var BLAST_RADIUS_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
|
|
74635
75359
|
|
|
74636
75360
|
Evidence used
|
|
@@ -74657,7 +75381,7 @@ async function runBlastRadius(query, opts) {
|
|
|
74657
75381
|
const { code } = createConnectors(config);
|
|
74658
75382
|
const health = await code.health();
|
|
74659
75383
|
if (!health.ok) {
|
|
74660
|
-
console.error(
|
|
75384
|
+
console.error(import_picocolors11.default.red("Source-intelligence host unreachable \u2014 run: horus index"));
|
|
74661
75385
|
return 1;
|
|
74662
75386
|
}
|
|
74663
75387
|
let project;
|
|
@@ -74670,12 +75394,12 @@ async function runBlastRadius(query, opts) {
|
|
|
74670
75394
|
const r = await analyzeBlastRadius(query, { code, db, project }, opts.depth ?? 3);
|
|
74671
75395
|
if (!r) {
|
|
74672
75396
|
console.log(`No symbol found for: ${query}`);
|
|
74673
|
-
console.log(
|
|
75397
|
+
console.log(import_picocolors11.default.dim(` Tip: use an exact class or function name, e.g. "MyService"`));
|
|
74674
75398
|
return 1;
|
|
74675
75399
|
}
|
|
74676
75400
|
if (r.seed.name.toLowerCase() !== query.toLowerCase()) {
|
|
74677
75401
|
console.log(
|
|
74678
|
-
|
|
75402
|
+
import_picocolors11.default.yellow(` No exact match for "${query}"`) + import_picocolors11.default.dim(` \u2014 showing closest: "${r.seed.name}" (fuzzy match)`)
|
|
74679
75403
|
);
|
|
74680
75404
|
}
|
|
74681
75405
|
if (opts.json) {
|
|
@@ -74695,7 +75419,7 @@ async function runBlastRadius(query, opts) {
|
|
|
74695
75419
|
});
|
|
74696
75420
|
console.log("\n" + renderInterpretation(result));
|
|
74697
75421
|
if (!result.ok) {
|
|
74698
|
-
console.error(
|
|
75422
|
+
console.error(import_picocolors11.default.yellow(`[ai] ${result.warning}`));
|
|
74699
75423
|
}
|
|
74700
75424
|
}
|
|
74701
75425
|
}
|
|
@@ -74704,28 +75428,28 @@ async function runBlastRadius(query, opts) {
|
|
|
74704
75428
|
}
|
|
74705
75429
|
return 0;
|
|
74706
75430
|
} catch (err) {
|
|
74707
|
-
console.error(
|
|
75431
|
+
console.error(import_picocolors11.default.red(err.message));
|
|
74708
75432
|
return 1;
|
|
74709
75433
|
}
|
|
74710
75434
|
}
|
|
74711
75435
|
|
|
74712
75436
|
// ../../packages/cli/src/commands/repos.ts
|
|
74713
75437
|
init_cjs_shims();
|
|
74714
|
-
var
|
|
75438
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
74715
75439
|
async function runRepos(opts) {
|
|
74716
75440
|
const config = await loadConfig(opts.config);
|
|
74717
75441
|
const providers = repoProviders(config);
|
|
74718
75442
|
if (providers.length === 0) {
|
|
74719
|
-
console.log(
|
|
75443
|
+
console.log(import_picocolors12.default.dim("No repositories configured."));
|
|
74720
75444
|
return 0;
|
|
74721
75445
|
}
|
|
74722
75446
|
const health = await reposHealth(providers);
|
|
74723
75447
|
for (const h of health) {
|
|
74724
|
-
const dot = h.reachable ?
|
|
74725
|
-
const name =
|
|
74726
|
-
const hostUrl =
|
|
74727
|
-
const path =
|
|
74728
|
-
const detail = h.detail ?
|
|
75448
|
+
const dot = h.reachable ? import_picocolors12.default.green("\u25CF") : import_picocolors12.default.red("\u25CF");
|
|
75449
|
+
const name = import_picocolors12.default.bold(h.repo.padEnd(20));
|
|
75450
|
+
const hostUrl = import_picocolors12.default.cyan(h.hostUrl.padEnd(30));
|
|
75451
|
+
const path = import_picocolors12.default.dim(h.path);
|
|
75452
|
+
const detail = h.detail ? import_picocolors12.default.dim(`(${h.detail})`) : "";
|
|
74729
75453
|
console.log(` ${dot} ${name} ${hostUrl} ${path} ${detail}`);
|
|
74730
75454
|
}
|
|
74731
75455
|
return 0;
|
|
@@ -74733,7 +75457,7 @@ async function runRepos(opts) {
|
|
|
74733
75457
|
|
|
74734
75458
|
// ../../packages/cli/src/commands/search.ts
|
|
74735
75459
|
init_cjs_shims();
|
|
74736
|
-
var
|
|
75460
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
74737
75461
|
async function runSearch(query, opts) {
|
|
74738
75462
|
const config = await loadConfig(opts.config);
|
|
74739
75463
|
const providers = repoProviders(config);
|
|
@@ -74744,16 +75468,16 @@ async function runSearch(query, opts) {
|
|
|
74744
75468
|
}
|
|
74745
75469
|
let totalMatches = 0;
|
|
74746
75470
|
for (const result of results) {
|
|
74747
|
-
const statusDot = result.reachable ?
|
|
75471
|
+
const statusDot = result.reachable ? import_picocolors13.default.green("\u25CF") : import_picocolors13.default.red("\u25CF");
|
|
74748
75472
|
console.log("");
|
|
74749
|
-
console.log(`${statusDot} ${
|
|
75473
|
+
console.log(`${statusDot} ${import_picocolors13.default.bold("## " + result.repo)} ${import_picocolors13.default.dim(result.hostUrl)}`);
|
|
74750
75474
|
if (!result.reachable) {
|
|
74751
|
-
console.log(
|
|
75475
|
+
console.log(import_picocolors13.default.dim(" (unreachable)"));
|
|
74752
75476
|
} else if (result.symbols.length === 0) {
|
|
74753
|
-
console.log(
|
|
75477
|
+
console.log(import_picocolors13.default.dim(" (no matches)"));
|
|
74754
75478
|
} else {
|
|
74755
75479
|
for (const sym of result.symbols) {
|
|
74756
|
-
console.log(` - ${
|
|
75480
|
+
console.log(` - ${import_picocolors13.default.bold(sym.name)} ${import_picocolors13.default.dim(sym.filePath)}`);
|
|
74757
75481
|
}
|
|
74758
75482
|
totalMatches += result.symbols.length;
|
|
74759
75483
|
}
|
|
@@ -74761,7 +75485,7 @@ async function runSearch(query, opts) {
|
|
|
74761
75485
|
console.log("");
|
|
74762
75486
|
const reachableCount = results.filter((r) => r.reachable).length;
|
|
74763
75487
|
console.log(
|
|
74764
|
-
|
|
75488
|
+
import_picocolors13.default.dim(
|
|
74765
75489
|
`${totalMatches} match(es) across ${reachableCount}/${results.length} reachable repo(s) for query: "${query}"`
|
|
74766
75490
|
)
|
|
74767
75491
|
);
|
|
@@ -74770,6 +75494,7 @@ async function runSearch(query, opts) {
|
|
|
74770
75494
|
|
|
74771
75495
|
// ../../packages/cli/src/commands/investigations.ts
|
|
74772
75496
|
init_cjs_shims();
|
|
75497
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
74773
75498
|
|
|
74774
75499
|
// ../../packages/cli/src/lib/format.ts
|
|
74775
75500
|
init_cjs_shims();
|
|
@@ -74803,6 +75528,34 @@ async function resolveDbUrl(configPath) {
|
|
|
74803
75528
|
|
|
74804
75529
|
// ../../packages/cli/src/commands/investigations.ts
|
|
74805
75530
|
async function runInvestigations(opts) {
|
|
75531
|
+
const repoRoot = repoRootOrCwd();
|
|
75532
|
+
const cloudCfg = readCloudConfig(repoRoot);
|
|
75533
|
+
if (isCloudActive(cloudCfg)) {
|
|
75534
|
+
const session = authedClient();
|
|
75535
|
+
if (!session) {
|
|
75536
|
+
console.error(
|
|
75537
|
+
import_picocolors14.default.red(
|
|
75538
|
+
`This repo is linked to Horus Cloud but you are not logged in. Run ${import_picocolors14.default.bold("horus login")} first.`
|
|
75539
|
+
)
|
|
75540
|
+
);
|
|
75541
|
+
return 1;
|
|
75542
|
+
}
|
|
75543
|
+
try {
|
|
75544
|
+
const rows = await listCloudInvestigations(session.client, cloudCfg);
|
|
75545
|
+
if (rows.length === 0) {
|
|
75546
|
+
console.log('No investigations yet. Run: horus investigate "<hint>"');
|
|
75547
|
+
} else {
|
|
75548
|
+
for (const row of rows) {
|
|
75549
|
+
const ts = formatDateTime(new Date(row.createdAt));
|
|
75550
|
+
const title = (row.title ?? "").length > 60 ? (row.title ?? "").slice(0, 57) + "..." : row.title ?? "";
|
|
75551
|
+
console.log(`${row.id} ${ts} ${title}`);
|
|
75552
|
+
}
|
|
75553
|
+
}
|
|
75554
|
+
} catch (err) {
|
|
75555
|
+
return reportCloudError(err);
|
|
75556
|
+
}
|
|
75557
|
+
return 0;
|
|
75558
|
+
}
|
|
74806
75559
|
const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
|
|
74807
75560
|
try {
|
|
74808
75561
|
const rows = await listInvestigations(db, opts.limit ?? 20);
|
|
@@ -74823,18 +75576,18 @@ async function runInvestigations(opts) {
|
|
|
74823
75576
|
|
|
74824
75577
|
// ../../packages/cli/src/commands/replay.ts
|
|
74825
75578
|
init_cjs_shims();
|
|
74826
|
-
var
|
|
75579
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
74827
75580
|
async function runReplay(id, opts) {
|
|
74828
75581
|
const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
|
|
74829
75582
|
try {
|
|
74830
75583
|
const row = await getInvestigation(db, id);
|
|
74831
75584
|
if (!row) {
|
|
74832
|
-
console.error(
|
|
75585
|
+
console.error(import_picocolors15.default.red("No investigation found: " + id));
|
|
74833
75586
|
return 1;
|
|
74834
75587
|
}
|
|
74835
75588
|
if (!row.report) {
|
|
74836
75589
|
console.error(
|
|
74837
|
-
|
|
75590
|
+
import_picocolors15.default.red(
|
|
74838
75591
|
"Investigation " + id + " has no stored report (run a newer investigation)."
|
|
74839
75592
|
)
|
|
74840
75593
|
);
|
|
@@ -74847,7 +75600,7 @@ async function runReplay(id, opts) {
|
|
|
74847
75600
|
if (opts.ai && fmt !== "json") {
|
|
74848
75601
|
if (report.aiJudgment && !opts.refreshAi) {
|
|
74849
75602
|
renderStoredAIJudgment(report.aiJudgment);
|
|
74850
|
-
console.log(
|
|
75603
|
+
console.log(import_picocolors15.default.dim("[ai] Stored judgment replayed. Use --refresh-ai to regenerate."));
|
|
74851
75604
|
} else {
|
|
74852
75605
|
const narrativeInput = buildNarrativeInput(report);
|
|
74853
75606
|
const { provider } = await buildNarrativeProvider({
|
|
@@ -74860,17 +75613,17 @@ async function runReplay(id, opts) {
|
|
|
74860
75613
|
);
|
|
74861
75614
|
if (!fromProvider && degraded) {
|
|
74862
75615
|
if (validationErrors?.length) {
|
|
74863
|
-
console.error(
|
|
75616
|
+
console.error(import_picocolors15.default.yellow("[ai] structured validation incomplete \u2014 showing raw AI narrative"));
|
|
74864
75617
|
}
|
|
74865
75618
|
console.log("");
|
|
74866
|
-
console.log(
|
|
74867
|
-
console.log(
|
|
74868
|
-
if (output.what?.trim()) console.log(
|
|
74869
|
-
if (output.why?.trim()) console.log(
|
|
75619
|
+
console.log(import_picocolors15.default.dim("\u2500".repeat(60)));
|
|
75620
|
+
console.log(import_picocolors15.default.bold("AI narrative ") + import_picocolors15.default.yellow("(unstructured \u2014 not validated)"));
|
|
75621
|
+
if (output.what?.trim()) console.log(import_picocolors15.default.bold("\nWhat:"), output.what.trim());
|
|
75622
|
+
if (output.why?.trim()) console.log(import_picocolors15.default.bold("\nWhy:"), output.why.trim());
|
|
74870
75623
|
} else if (!fromProvider) {
|
|
74871
|
-
console.error(
|
|
75624
|
+
console.error(import_picocolors15.default.yellow("[ai] Provider unavailable \u2014 deterministic output shown above."));
|
|
74872
75625
|
if (validationErrors?.length) {
|
|
74873
|
-
console.error(
|
|
75626
|
+
console.error(import_picocolors15.default.dim(` ${validationErrors[0]}`));
|
|
74874
75627
|
}
|
|
74875
75628
|
} else {
|
|
74876
75629
|
const stored = narrativeOutputToStoredJudgment(output, "anthropic");
|
|
@@ -74891,7 +75644,7 @@ async function runReplay(id, opts) {
|
|
|
74891
75644
|
|
|
74892
75645
|
// ../../packages/cli/src/commands/owner.ts
|
|
74893
75646
|
init_cjs_shims();
|
|
74894
|
-
var
|
|
75647
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
74895
75648
|
async function runOwner(query, opts) {
|
|
74896
75649
|
try {
|
|
74897
75650
|
const config = await loadConfig(opts.config);
|
|
@@ -74899,7 +75652,7 @@ async function runOwner(query, opts) {
|
|
|
74899
75652
|
try {
|
|
74900
75653
|
renv = resolveEnvironment(config, { project: opts.repo });
|
|
74901
75654
|
} catch (err) {
|
|
74902
|
-
console.error(
|
|
75655
|
+
console.error(import_picocolors16.default.red(err.message));
|
|
74903
75656
|
return 1;
|
|
74904
75657
|
}
|
|
74905
75658
|
const code = codeForRepo(config, renv.project);
|
|
@@ -74907,16 +75660,16 @@ async function runOwner(query, opts) {
|
|
|
74907
75660
|
console.log(opts.json ? ownershipToJSON(o) : renderOwnership(o));
|
|
74908
75661
|
return 0;
|
|
74909
75662
|
} catch (err) {
|
|
74910
|
-
console.error(
|
|
75663
|
+
console.error(import_picocolors16.default.red(err.message));
|
|
74911
75664
|
return 1;
|
|
74912
75665
|
}
|
|
74913
75666
|
}
|
|
74914
75667
|
|
|
74915
75668
|
// ../../packages/cli/src/commands/postmortem.ts
|
|
74916
75669
|
init_cjs_shims();
|
|
74917
|
-
var
|
|
74918
|
-
var
|
|
74919
|
-
var
|
|
75670
|
+
var import_node_fs8 = require("fs");
|
|
75671
|
+
var import_node_path9 = require("path");
|
|
75672
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
74920
75673
|
async function runPostmortem(id, opts) {
|
|
74921
75674
|
const log = opts.write ?? ((line2) => console.log(line2));
|
|
74922
75675
|
let sql2;
|
|
@@ -74930,11 +75683,11 @@ async function runPostmortem(id, opts) {
|
|
|
74930
75683
|
const { db } = conn;
|
|
74931
75684
|
const row = await getInvestigation(db, id);
|
|
74932
75685
|
if (!row) {
|
|
74933
|
-
log(
|
|
75686
|
+
log(import_picocolors17.default.red("No investigation found: " + id));
|
|
74934
75687
|
return 1;
|
|
74935
75688
|
}
|
|
74936
75689
|
if (!row.report) {
|
|
74937
|
-
log(
|
|
75690
|
+
log(import_picocolors17.default.red("Investigation " + id + " has no stored report."));
|
|
74938
75691
|
return 1;
|
|
74939
75692
|
}
|
|
74940
75693
|
report = migrateReport(row.report);
|
|
@@ -75026,25 +75779,25 @@ _AI summary unavailable: ${validationErrors?.[0] ?? "provider error"}_
|
|
|
75026
75779
|
}
|
|
75027
75780
|
}
|
|
75028
75781
|
if (opts.output) {
|
|
75029
|
-
const outputPath = (0,
|
|
75030
|
-
if ((0,
|
|
75031
|
-
log(
|
|
75032
|
-
log(
|
|
75782
|
+
const outputPath = (0, import_node_path9.resolve)(opts.output);
|
|
75783
|
+
if ((0, import_node_fs8.existsSync)(outputPath) && !opts.force) {
|
|
75784
|
+
log(import_picocolors17.default.red("\u2717") + " " + outputPath + " already exists");
|
|
75785
|
+
log(import_picocolors17.default.dim(" pass --force to overwrite"));
|
|
75033
75786
|
return 1;
|
|
75034
75787
|
}
|
|
75035
75788
|
try {
|
|
75036
|
-
(0,
|
|
75789
|
+
(0, import_node_fs8.writeFileSync)(outputPath, content, "utf8");
|
|
75037
75790
|
} catch (err) {
|
|
75038
|
-
log(
|
|
75791
|
+
log(import_picocolors17.default.red("\u2717 Could not write " + outputPath + ": ") + err.message);
|
|
75039
75792
|
return 1;
|
|
75040
75793
|
}
|
|
75041
|
-
log(
|
|
75794
|
+
log(import_picocolors17.default.green("\u2713") + " Saved postmortem to " + outputPath);
|
|
75042
75795
|
} else {
|
|
75043
75796
|
log(content);
|
|
75044
75797
|
}
|
|
75045
75798
|
} catch (err) {
|
|
75046
75799
|
const message = err instanceof Error ? err.message : String(err);
|
|
75047
|
-
log(
|
|
75800
|
+
log(import_picocolors17.default.red("postmortem failed: " + message));
|
|
75048
75801
|
return 1;
|
|
75049
75802
|
} finally {
|
|
75050
75803
|
if (sql2) {
|
|
@@ -75056,7 +75809,7 @@ _AI summary unavailable: ${validationErrors?.[0] ?? "provider error"}_
|
|
|
75056
75809
|
|
|
75057
75810
|
// ../../packages/cli/src/commands/score.ts
|
|
75058
75811
|
init_cjs_shims();
|
|
75059
|
-
var
|
|
75812
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
75060
75813
|
var SCORE_AI_CONTRACT = `Provide a clearly separated AI score explanation with:
|
|
75061
75814
|
|
|
75062
75815
|
Why this scored this way
|
|
@@ -75075,11 +75828,11 @@ async function runScore(id, opts) {
|
|
|
75075
75828
|
try {
|
|
75076
75829
|
const row = await getInvestigation(db, id);
|
|
75077
75830
|
if (!row) {
|
|
75078
|
-
console.error(
|
|
75831
|
+
console.error(import_picocolors18.default.red("No investigation found: " + id));
|
|
75079
75832
|
return 1;
|
|
75080
75833
|
}
|
|
75081
75834
|
if (!row.report) {
|
|
75082
|
-
console.error(
|
|
75835
|
+
console.error(import_picocolors18.default.red("Investigation " + id + " has no stored report."));
|
|
75083
75836
|
return 1;
|
|
75084
75837
|
}
|
|
75085
75838
|
const s = scoreInvestigation(migrateReport(row.report));
|
|
@@ -75100,7 +75853,7 @@ async function runScore(id, opts) {
|
|
|
75100
75853
|
});
|
|
75101
75854
|
console.log("\n" + renderInterpretation(result));
|
|
75102
75855
|
if (!result.ok) {
|
|
75103
|
-
console.error(
|
|
75856
|
+
console.error(import_picocolors18.default.yellow(`[ai] ${result.warning}`));
|
|
75104
75857
|
}
|
|
75105
75858
|
}
|
|
75106
75859
|
}
|
|
@@ -75140,56 +75893,90 @@ async function runScores(opts) {
|
|
|
75140
75893
|
|
|
75141
75894
|
// ../../packages/cli/src/commands/ask.ts
|
|
75142
75895
|
init_cjs_shims();
|
|
75143
|
-
var
|
|
75896
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
75897
|
+
function renderAnswer(report, directive, opts) {
|
|
75898
|
+
const answer = answerQuestion(report, directive);
|
|
75899
|
+
if (answer) {
|
|
75900
|
+
console.log(opts.json ? qaToJSON(answer) : renderQAAnswer(answer));
|
|
75901
|
+
return 0;
|
|
75902
|
+
}
|
|
75903
|
+
const v = refineInvestigation(report, directive);
|
|
75904
|
+
console.log(opts.json ? refinedToJSON(report, v) : renderRefined(report, v));
|
|
75905
|
+
if (!opts.json && report.aiJudgment) {
|
|
75906
|
+
const j = report.aiJudgment;
|
|
75907
|
+
console.log("");
|
|
75908
|
+
console.log(import_picocolors19.default.dim("\u2500".repeat(60)));
|
|
75909
|
+
console.log(import_picocolors19.default.dim(`Stored AI judgment (${j.provider}, ${j.generatedAt}):`));
|
|
75910
|
+
if (j.rootCauseAssessment) {
|
|
75911
|
+
console.log(import_picocolors19.default.bold("Root cause (AI):"), j.rootCauseAssessment.summary);
|
|
75912
|
+
console.log(import_picocolors19.default.dim(`Uncertainty: ${j.rootCauseAssessment.uncertainty}`));
|
|
75913
|
+
} else {
|
|
75914
|
+
console.log(import_picocolors19.default.bold("AI Why:"), j.why);
|
|
75915
|
+
}
|
|
75916
|
+
}
|
|
75917
|
+
return 0;
|
|
75918
|
+
}
|
|
75144
75919
|
async function runAsk(id, directive, opts) {
|
|
75920
|
+
const repoRoot = repoRootOrCwd();
|
|
75921
|
+
const cloudCfg = readCloudConfig(repoRoot);
|
|
75922
|
+
if (isCloudActive(cloudCfg)) {
|
|
75923
|
+
const session = authedClient();
|
|
75924
|
+
if (!session) {
|
|
75925
|
+
console.error(
|
|
75926
|
+
import_picocolors19.default.red(
|
|
75927
|
+
`This repo is linked to Horus Cloud but you are not logged in. Run ${import_picocolors19.default.bold("horus login")} first.`
|
|
75928
|
+
)
|
|
75929
|
+
);
|
|
75930
|
+
return 1;
|
|
75931
|
+
}
|
|
75932
|
+
try {
|
|
75933
|
+
const report = await fetchInvestigationReportFromCloud(
|
|
75934
|
+
session.client,
|
|
75935
|
+
cloudCfg,
|
|
75936
|
+
id
|
|
75937
|
+
);
|
|
75938
|
+
if (!report) {
|
|
75939
|
+
console.error(
|
|
75940
|
+
import_picocolors19.default.red(
|
|
75941
|
+
`Cloud investigation ${id} has no saved report. Run this investigation with ${import_picocolors19.default.bold("horus investigate")} first.`
|
|
75942
|
+
)
|
|
75943
|
+
);
|
|
75944
|
+
return 1;
|
|
75945
|
+
}
|
|
75946
|
+
return renderAnswer(report, directive, opts);
|
|
75947
|
+
} catch (err) {
|
|
75948
|
+
return reportCloudError(err);
|
|
75949
|
+
}
|
|
75950
|
+
}
|
|
75145
75951
|
const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
|
|
75146
75952
|
try {
|
|
75147
75953
|
const row = await getInvestigation(db, id);
|
|
75148
75954
|
if (!row) {
|
|
75149
|
-
console.error(
|
|
75955
|
+
console.error(import_picocolors19.default.red("No investigation found: " + id));
|
|
75150
75956
|
return 1;
|
|
75151
75957
|
}
|
|
75152
75958
|
if (!row.report) {
|
|
75153
|
-
console.error(
|
|
75959
|
+
console.error(import_picocolors19.default.red("Investigation " + id + " has no stored report."));
|
|
75154
75960
|
return 1;
|
|
75155
75961
|
}
|
|
75156
75962
|
const report = migrateReport(row.report);
|
|
75157
|
-
|
|
75158
|
-
if (answer) {
|
|
75159
|
-
console.log(opts.json ? qaToJSON(answer) : renderQAAnswer(answer));
|
|
75160
|
-
return 0;
|
|
75161
|
-
}
|
|
75162
|
-
const v = refineInvestigation(report, directive);
|
|
75163
|
-
console.log(opts.json ? refinedToJSON(report, v) : renderRefined(report, v));
|
|
75164
|
-
if (!opts.json && report.aiJudgment) {
|
|
75165
|
-
const j = report.aiJudgment;
|
|
75166
|
-
console.log("");
|
|
75167
|
-
console.log(import_picocolors17.default.dim("\u2500".repeat(60)));
|
|
75168
|
-
console.log(import_picocolors17.default.dim(`Stored AI judgment (${j.provider}, ${j.generatedAt}):`));
|
|
75169
|
-
if (j.rootCauseAssessment) {
|
|
75170
|
-
console.log(import_picocolors17.default.bold("Root cause (AI):"), j.rootCauseAssessment.summary);
|
|
75171
|
-
console.log(import_picocolors17.default.dim(`Uncertainty: ${j.rootCauseAssessment.uncertainty}`));
|
|
75172
|
-
} else {
|
|
75173
|
-
console.log(import_picocolors17.default.bold("AI Why:"), j.why);
|
|
75174
|
-
}
|
|
75175
|
-
}
|
|
75963
|
+
return renderAnswer(report, directive, opts);
|
|
75176
75964
|
} catch (err) {
|
|
75177
75965
|
const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
75178
75966
|
if (code === "22P02") {
|
|
75179
|
-
console.error(
|
|
75967
|
+
console.error(import_picocolors19.default.red("No investigation found: " + id));
|
|
75180
75968
|
return 1;
|
|
75181
75969
|
}
|
|
75182
|
-
console.error(
|
|
75970
|
+
console.error(import_picocolors19.default.red(err instanceof Error ? err.message : String(err)));
|
|
75183
75971
|
return 1;
|
|
75184
75972
|
} finally {
|
|
75185
75973
|
await sql2.end();
|
|
75186
75974
|
}
|
|
75187
|
-
return 0;
|
|
75188
75975
|
}
|
|
75189
75976
|
|
|
75190
75977
|
// ../../packages/cli/src/commands/onboard.ts
|
|
75191
75978
|
init_cjs_shims();
|
|
75192
|
-
var
|
|
75979
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
75193
75980
|
var ONBOARD_AI_CONTRACT = `Provide a clearly separated AI onboarding guide with:
|
|
75194
75981
|
|
|
75195
75982
|
Start here
|
|
@@ -75217,7 +76004,7 @@ async function runOnboard(area, opts) {
|
|
|
75217
76004
|
try {
|
|
75218
76005
|
renv = resolveEnvironment(config, { project: opts.repo });
|
|
75219
76006
|
} catch (err) {
|
|
75220
|
-
console.error(
|
|
76007
|
+
console.error(import_picocolors20.default.red(err.message));
|
|
75221
76008
|
return 1;
|
|
75222
76009
|
}
|
|
75223
76010
|
const repo = { name: renv.project, path: renv.path };
|
|
@@ -75225,7 +76012,7 @@ async function runOnboard(area, opts) {
|
|
|
75225
76012
|
const health = await code.health();
|
|
75226
76013
|
if (!health.ok) {
|
|
75227
76014
|
console.error(
|
|
75228
|
-
|
|
76015
|
+
import_picocolors20.default.red("Source-intelligence host unreachable \u2014 run: horus index")
|
|
75229
76016
|
);
|
|
75230
76017
|
return 1;
|
|
75231
76018
|
}
|
|
@@ -75252,7 +76039,7 @@ async function runOnboard(area, opts) {
|
|
|
75252
76039
|
});
|
|
75253
76040
|
console.log("\n" + renderInterpretation(result));
|
|
75254
76041
|
if (!result.ok) {
|
|
75255
|
-
console.error(
|
|
76042
|
+
console.error(import_picocolors20.default.yellow(`[ai] ${result.warning}`));
|
|
75256
76043
|
}
|
|
75257
76044
|
}
|
|
75258
76045
|
}
|
|
@@ -75261,14 +76048,14 @@ async function runOnboard(area, opts) {
|
|
|
75261
76048
|
}
|
|
75262
76049
|
return 0;
|
|
75263
76050
|
} catch (err) {
|
|
75264
|
-
console.error(
|
|
76051
|
+
console.error(import_picocolors20.default.red(err.message));
|
|
75265
76052
|
return 1;
|
|
75266
76053
|
}
|
|
75267
76054
|
}
|
|
75268
76055
|
|
|
75269
76056
|
// ../../packages/cli/src/commands/simulate.ts
|
|
75270
76057
|
init_cjs_shims();
|
|
75271
|
-
var
|
|
76058
|
+
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
75272
76059
|
async function runSimulate(scenarioId, opts) {
|
|
75273
76060
|
try {
|
|
75274
76061
|
if (scenarioId === void 0) {
|
|
@@ -75278,7 +76065,7 @@ async function runSimulate(scenarioId, opts) {
|
|
|
75278
76065
|
const scenario = getScenario(scenarioId);
|
|
75279
76066
|
if (scenario === null) {
|
|
75280
76067
|
console.error(
|
|
75281
|
-
|
|
76068
|
+
import_picocolors21.default.red(
|
|
75282
76069
|
`Unknown scenario: "${scenarioId}". Run horus simulate to list available scenarios.`
|
|
75283
76070
|
)
|
|
75284
76071
|
);
|
|
@@ -75289,14 +76076,14 @@ async function runSimulate(scenarioId, opts) {
|
|
|
75289
76076
|
try {
|
|
75290
76077
|
renv = resolveEnvironment(config, { project: opts.repo });
|
|
75291
76078
|
} catch (err) {
|
|
75292
|
-
console.error(
|
|
76079
|
+
console.error(import_picocolors21.default.red(err.message));
|
|
75293
76080
|
return 1;
|
|
75294
76081
|
}
|
|
75295
76082
|
const code = codeForRepo(config, renv.project);
|
|
75296
76083
|
const health = await code.health();
|
|
75297
76084
|
if (!health.ok) {
|
|
75298
76085
|
console.error(
|
|
75299
|
-
|
|
76086
|
+
import_picocolors21.default.red("Source-intelligence host unreachable \u2014 run: horus index")
|
|
75300
76087
|
);
|
|
75301
76088
|
return 1;
|
|
75302
76089
|
}
|
|
@@ -75313,14 +76100,14 @@ async function runSimulate(scenarioId, opts) {
|
|
|
75313
76100
|
}
|
|
75314
76101
|
return 0;
|
|
75315
76102
|
} catch (err) {
|
|
75316
|
-
console.error(
|
|
76103
|
+
console.error(import_picocolors21.default.red(err.message));
|
|
75317
76104
|
return 1;
|
|
75318
76105
|
}
|
|
75319
76106
|
}
|
|
75320
76107
|
|
|
75321
76108
|
// ../../packages/cli/src/commands/logs.ts
|
|
75322
76109
|
init_cjs_shims();
|
|
75323
|
-
var
|
|
76110
|
+
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
75324
76111
|
var LOGS_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
|
|
75325
76112
|
|
|
75326
76113
|
Evidence used
|
|
@@ -75353,9 +76140,9 @@ function resolveRawLevel(opts) {
|
|
|
75353
76140
|
return opts.level ?? "error";
|
|
75354
76141
|
}
|
|
75355
76142
|
function levelColor(level, text2) {
|
|
75356
|
-
if (level === "error" || level === "fatal") return
|
|
75357
|
-
if (level === "warn") return
|
|
75358
|
-
return
|
|
76143
|
+
if (level === "error" || level === "fatal") return import_picocolors22.default.red(text2);
|
|
76144
|
+
if (level === "warn") return import_picocolors22.default.yellow(text2);
|
|
76145
|
+
return import_picocolors22.default.dim(text2);
|
|
75359
76146
|
}
|
|
75360
76147
|
async function runLogs(service, opts) {
|
|
75361
76148
|
try {
|
|
@@ -75364,13 +76151,13 @@ async function runLogs(service, opts) {
|
|
|
75364
76151
|
try {
|
|
75365
76152
|
renv = resolveEnvironment(config, { project: opts.project, env: opts.env });
|
|
75366
76153
|
} catch (err) {
|
|
75367
|
-
console.error(
|
|
76154
|
+
console.error(import_picocolors22.default.red(err.message));
|
|
75368
76155
|
return 1;
|
|
75369
76156
|
}
|
|
75370
76157
|
const logs = logsForEnv(renv);
|
|
75371
76158
|
if (logs === null) {
|
|
75372
76159
|
console.error(
|
|
75373
|
-
|
|
76160
|
+
import_picocolors22.default.red(
|
|
75374
76161
|
`No Elasticsearch connector configured for ${renv.project}/${renv.env} (set ES_URL / ES_USERNAME / ES_PASSWORD)`
|
|
75375
76162
|
)
|
|
75376
76163
|
);
|
|
@@ -75378,35 +76165,34 @@ async function runLogs(service, opts) {
|
|
|
75378
76165
|
}
|
|
75379
76166
|
const health = await logs.health();
|
|
75380
76167
|
if (!health.ok) {
|
|
75381
|
-
console.error(
|
|
76168
|
+
console.error(import_picocolors22.default.red(`Elasticsearch unreachable: ${health.detail}`));
|
|
75382
76169
|
return 1;
|
|
75383
76170
|
}
|
|
75384
76171
|
const resolvedService = service ?? renv.connectors.elasticsearch?.serviceName;
|
|
75385
76172
|
const indexPattern = renv.connectors.elasticsearch?.indexPattern ?? "*";
|
|
75386
76173
|
try {
|
|
76174
|
+
const aggregatesEventCode = opts.raw !== true && opts.groupBy === void 0;
|
|
75387
76175
|
const compat = await logs.checkCompatibility({
|
|
75388
76176
|
requiresService: resolvedService !== void 0,
|
|
75389
|
-
|
|
75390
|
-
|
|
75391
|
-
requiresServiceAggregation: opts.raw !== true,
|
|
75392
|
-
requiresEventCode: opts.raw !== true
|
|
76177
|
+
requiresServiceAggregation: aggregatesEventCode,
|
|
76178
|
+
requiresEventCode: aggregatesEventCode
|
|
75393
76179
|
});
|
|
75394
76180
|
for (const w of compat.issues.filter((i) => i.severity === "warning")) {
|
|
75395
|
-
console.warn(
|
|
76181
|
+
console.warn(import_picocolors22.default.yellow(`[warn] ${w.field}: ${w.message}`));
|
|
75396
76182
|
}
|
|
75397
76183
|
const errors2 = compat.issues.filter((i) => i.severity === "error");
|
|
75398
76184
|
if (errors2.length > 0) {
|
|
75399
|
-
console.error(
|
|
76185
|
+
console.error(import_picocolors22.default.red("Elasticsearch field mapping is incompatible with the index:"));
|
|
75400
76186
|
for (const e of errors2) {
|
|
75401
|
-
console.error(
|
|
76187
|
+
console.error(import_picocolors22.default.red(` ${e.field}: ${e.message}`));
|
|
75402
76188
|
}
|
|
75403
76189
|
console.error(
|
|
75404
|
-
|
|
76190
|
+
import_picocolors22.default.dim("Fix fields.* overrides in your connector config or choose the correct preset.")
|
|
75405
76191
|
);
|
|
75406
76192
|
return 1;
|
|
75407
76193
|
}
|
|
75408
76194
|
} catch {
|
|
75409
|
-
console.warn(
|
|
76195
|
+
console.warn(import_picocolors22.default.dim("[warn] Elasticsearch compatibility check unavailable \u2014 proceeding."));
|
|
75410
76196
|
}
|
|
75411
76197
|
const from = sinceToIso(opts.since) ?? new Date(Date.now() - 7 * 864e5).toISOString();
|
|
75412
76198
|
const fromDisplay = from.slice(0, 16).replace("T", " ");
|
|
@@ -75418,6 +76204,9 @@ async function runLogs(service, opts) {
|
|
|
75418
76204
|
from,
|
|
75419
76205
|
level: rawLevel,
|
|
75420
76206
|
text: opts.grep,
|
|
76207
|
+
// grep searches message + detail + context.* so error strings buried
|
|
76208
|
+
// outside the message field are still found (HOR-216).
|
|
76209
|
+
broadText: opts.grep !== void 0,
|
|
75421
76210
|
limit
|
|
75422
76211
|
});
|
|
75423
76212
|
if (opts.json) {
|
|
@@ -75440,6 +76229,9 @@ async function runLogs(service, opts) {
|
|
|
75440
76229
|
component: r.component ?? null,
|
|
75441
76230
|
eventCode: r.eventCode ?? null,
|
|
75442
76231
|
message: r.message,
|
|
76232
|
+
// Structured fields restored to raw output (HOR-215/216).
|
|
76233
|
+
context: r.context ?? null,
|
|
76234
|
+
detail: r.detail ?? null,
|
|
75443
76235
|
traceId: r.traceId ?? null,
|
|
75444
76236
|
requestId: r.requestId ?? null
|
|
75445
76237
|
}))
|
|
@@ -75451,7 +76243,7 @@ async function runLogs(service, opts) {
|
|
|
75451
76243
|
return 0;
|
|
75452
76244
|
}
|
|
75453
76245
|
if (records.length === 0) {
|
|
75454
|
-
console.log(
|
|
76246
|
+
console.log(import_picocolors22.default.dim("No logs matched."));
|
|
75455
76247
|
return 0;
|
|
75456
76248
|
}
|
|
75457
76249
|
for (const r of records) {
|
|
@@ -75460,18 +76252,74 @@ async function runLogs(service, opts) {
|
|
|
75460
76252
|
const comp = (r.component ?? r.service ?? "").padEnd(30).slice(0, 30);
|
|
75461
76253
|
const line2 = `${ts} ${lvl} ${comp} ${r.message.slice(0, 120)}`;
|
|
75462
76254
|
console.log(levelColor(r.level, line2));
|
|
76255
|
+
for (const { key, value } of extractContextFields(r)) {
|
|
76256
|
+
console.log(import_picocolors22.default.dim(` ${key}: ${value.slice(0, 200)}`));
|
|
76257
|
+
}
|
|
76258
|
+
}
|
|
76259
|
+
return 0;
|
|
76260
|
+
}
|
|
76261
|
+
if (opts.groupBy !== void 0) {
|
|
76262
|
+
const field = opts.groupBy;
|
|
76263
|
+
const buckets = await logs.aggregateErrors(
|
|
76264
|
+
{
|
|
76265
|
+
service: resolvedService,
|
|
76266
|
+
from,
|
|
76267
|
+
text: opts.grep,
|
|
76268
|
+
broadText: opts.grep !== void 0
|
|
76269
|
+
},
|
|
76270
|
+
field
|
|
76271
|
+
);
|
|
76272
|
+
if (opts.json) {
|
|
76273
|
+
console.log(
|
|
76274
|
+
JSON.stringify(
|
|
76275
|
+
{
|
|
76276
|
+
scope: {
|
|
76277
|
+
project: renv.project,
|
|
76278
|
+
env: renv.env,
|
|
76279
|
+
service: resolvedService ?? null,
|
|
76280
|
+
index: indexPattern,
|
|
76281
|
+
from,
|
|
76282
|
+
severity: "error+",
|
|
76283
|
+
grep: opts.grep ?? null,
|
|
76284
|
+
groupBy: field
|
|
76285
|
+
},
|
|
76286
|
+
buckets
|
|
76287
|
+
},
|
|
76288
|
+
null,
|
|
76289
|
+
2
|
|
76290
|
+
)
|
|
76291
|
+
);
|
|
76292
|
+
return 0;
|
|
76293
|
+
}
|
|
76294
|
+
console.log(
|
|
76295
|
+
import_picocolors22.default.bold(`Top ${field}`) + import_picocolors22.default.dim(
|
|
76296
|
+
` \u2014 ${renv.project}/${renv.env}` + (resolvedService ? ` \xB7 service ${resolvedService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
|
|
76297
|
+
)
|
|
76298
|
+
);
|
|
76299
|
+
console.log(import_picocolors22.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
|
|
76300
|
+
console.log("");
|
|
76301
|
+
if (buckets.length === 0) {
|
|
76302
|
+
console.log(import_picocolors22.default.dim(` No error logs with field "${field}" in the window.`));
|
|
76303
|
+
console.log(import_picocolors22.default.dim(` Tip: ensure "${field}" is an aggregatable (keyword) field.`));
|
|
76304
|
+
return 0;
|
|
76305
|
+
}
|
|
76306
|
+
for (const b2 of buckets) {
|
|
76307
|
+
console.log(` ${import_picocolors22.default.red(String(b2.count).padStart(6))} ${b2.key}`);
|
|
75463
76308
|
}
|
|
76309
|
+
console.log("");
|
|
76310
|
+
console.log(import_picocolors22.default.dim(` ${buckets.length} distinct ${field} value(s) shown (top by error count).`));
|
|
75464
76311
|
return 0;
|
|
75465
76312
|
}
|
|
75466
76313
|
let analysis = await logs.analyzeErrors({
|
|
75467
76314
|
service: resolvedService,
|
|
75468
76315
|
from,
|
|
75469
|
-
text: opts.grep
|
|
76316
|
+
text: opts.grep,
|
|
76317
|
+
broadText: opts.grep !== void 0
|
|
75470
76318
|
});
|
|
75471
76319
|
let scopeService = resolvedService;
|
|
75472
76320
|
let broadeningNote;
|
|
75473
76321
|
if (analysis.signatures.length === 0 && resolvedService !== void 0) {
|
|
75474
|
-
const broader = await logs.analyzeErrors({ from, text: opts.grep });
|
|
76322
|
+
const broader = await logs.analyzeErrors({ from, text: opts.grep, broadText: opts.grep !== void 0 });
|
|
75475
76323
|
if (broader.signatures.length > 0) {
|
|
75476
76324
|
analysis = broader;
|
|
75477
76325
|
broadeningNote = `No errors found for service "${resolvedService}" \u2014 showing all services`;
|
|
@@ -75503,46 +76351,46 @@ async function runLogs(service, opts) {
|
|
|
75503
76351
|
return 0;
|
|
75504
76352
|
}
|
|
75505
76353
|
console.log(
|
|
75506
|
-
|
|
76354
|
+
import_picocolors22.default.bold(`Error analysis`) + import_picocolors22.default.dim(
|
|
75507
76355
|
` \u2014 ${renv.project}/${renv.env}` + (scopeService ? ` \xB7 service ${scopeService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
|
|
75508
76356
|
)
|
|
75509
76357
|
);
|
|
75510
|
-
console.log(
|
|
76358
|
+
console.log(import_picocolors22.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
|
|
75511
76359
|
if (broadeningNote) {
|
|
75512
|
-
console.log(
|
|
76360
|
+
console.log(import_picocolors22.default.yellow(` ${broadeningNote}`));
|
|
75513
76361
|
}
|
|
75514
76362
|
console.log(
|
|
75515
|
-
` ${analysis.totalErrors} error(s) \xB7 ${analysis.signatures.length} signature(s) \xB7 ${
|
|
76363
|
+
` ${analysis.totalErrors} error(s) \xB7 ${analysis.signatures.length} signature(s) \xB7 ${import_picocolors22.default.yellow(String(analysis.newSignatures.length))} new`
|
|
75516
76364
|
);
|
|
75517
76365
|
console.log("");
|
|
75518
76366
|
if (analysis.signatures.length === 0) {
|
|
75519
|
-
console.log(
|
|
76367
|
+
console.log(import_picocolors22.default.dim(" No error-level logs in the window."));
|
|
75520
76368
|
console.log(
|
|
75521
|
-
|
|
76369
|
+
import_picocolors22.default.dim(
|
|
75522
76370
|
` Searched: ${scopeService ? `service "${scopeService}"` : "all services"} \xB7 index: ${indexPattern} \xB7 from: ${fromDisplay} UTC`
|
|
75523
76371
|
)
|
|
75524
76372
|
);
|
|
75525
|
-
console.log(
|
|
76373
|
+
console.log(import_picocolors22.default.dim(` Tip: use --since to widen the window (e.g. --since 30d).`));
|
|
75526
76374
|
return 0;
|
|
75527
76375
|
}
|
|
75528
76376
|
for (const s of analysis.signatures) {
|
|
75529
76377
|
const tags = [];
|
|
75530
|
-
if (s.isNew) tags.push(
|
|
76378
|
+
if (s.isNew) tags.push(import_picocolors22.default.red("NEW"));
|
|
75531
76379
|
else if (s.ratio !== void 0 && Number.isFinite(s.ratio) && s.ratio >= 1.5) {
|
|
75532
|
-
tags.push(
|
|
76380
|
+
tags.push(import_picocolors22.default.yellow(`spike x${s.ratio.toFixed(1)}`));
|
|
75533
76381
|
}
|
|
75534
|
-
const svc = s.services.length > 0 ?
|
|
76382
|
+
const svc = s.services.length > 0 ? import_picocolors22.default.dim(` \xB7 ${s.services.slice(0, 3).join(", ")}`) : "";
|
|
75535
76383
|
const tagStr = tags.length > 0 ? ` [${tags.join(", ")}]` : "";
|
|
75536
76384
|
console.log(
|
|
75537
|
-
` ${
|
|
76385
|
+
` ${import_picocolors22.default.red(String(s.count).padStart(6))} ${import_picocolors22.default.bold(s.key.padEnd(14).slice(0, 14))} ` + import_picocolors22.default.dim(`first ${shortTs(s.firstSeen)} \xB7 last ${shortTs(s.lastSeen)}`) + svc + tagStr
|
|
75538
76386
|
);
|
|
75539
76387
|
}
|
|
75540
76388
|
if (analysis.affectedServices.length > 0) {
|
|
75541
76389
|
console.log("");
|
|
75542
|
-
console.log(` ${
|
|
76390
|
+
console.log(` ${import_picocolors22.default.bold("Affected services:")} ${analysis.affectedServices.join(", ")}`);
|
|
75543
76391
|
}
|
|
75544
76392
|
console.log("");
|
|
75545
|
-
console.log(
|
|
76393
|
+
console.log(import_picocolors22.default.dim(" (use --raw to see individual log lines)"));
|
|
75546
76394
|
if (opts.ai) {
|
|
75547
76395
|
const result = await renderAiInterpretation({
|
|
75548
76396
|
command: "logs",
|
|
@@ -75561,19 +76409,19 @@ async function runLogs(service, opts) {
|
|
|
75561
76409
|
});
|
|
75562
76410
|
console.log("\n" + renderInterpretation(result));
|
|
75563
76411
|
if (!result.ok) {
|
|
75564
|
-
console.error(
|
|
76412
|
+
console.error(import_picocolors22.default.yellow(`[ai] ${result.warning}`));
|
|
75565
76413
|
}
|
|
75566
76414
|
}
|
|
75567
76415
|
return 0;
|
|
75568
76416
|
} catch (err) {
|
|
75569
|
-
console.error(
|
|
76417
|
+
console.error(import_picocolors22.default.red(err.message));
|
|
75570
76418
|
return 1;
|
|
75571
76419
|
}
|
|
75572
76420
|
}
|
|
75573
76421
|
|
|
75574
76422
|
// ../../packages/cli/src/commands/metrics.ts
|
|
75575
76423
|
init_cjs_shims();
|
|
75576
|
-
var
|
|
76424
|
+
var import_picocolors23 = __toESM(require_picocolors(), 1);
|
|
75577
76425
|
var METRICS_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
|
|
75578
76426
|
|
|
75579
76427
|
Evidence used
|
|
@@ -75618,10 +76466,10 @@ function anomalyColor(anomaly) {
|
|
|
75618
76466
|
case "latency-spike":
|
|
75619
76467
|
case "error-rate-change":
|
|
75620
76468
|
case "throughput-drop":
|
|
75621
|
-
return
|
|
76469
|
+
return import_picocolors23.default.red;
|
|
75622
76470
|
case "queue-growth":
|
|
75623
76471
|
case "change":
|
|
75624
|
-
return
|
|
76472
|
+
return import_picocolors23.default.yellow;
|
|
75625
76473
|
default:
|
|
75626
76474
|
return (s) => s;
|
|
75627
76475
|
}
|
|
@@ -75648,7 +76496,7 @@ async function runMetrics(hint, opts) {
|
|
|
75648
76496
|
const metrics = metricsProviderFromConfig(config);
|
|
75649
76497
|
if (metrics === null) {
|
|
75650
76498
|
console.error(
|
|
75651
|
-
|
|
76499
|
+
import_picocolors23.default.red(
|
|
75652
76500
|
"Grafana not configured \u2014 set GRAFANA_URL + GRAFANA_USER + GRAFANA_PASSWORD"
|
|
75653
76501
|
)
|
|
75654
76502
|
);
|
|
@@ -75656,7 +76504,7 @@ async function runMetrics(hint, opts) {
|
|
|
75656
76504
|
}
|
|
75657
76505
|
const health = await metrics.health();
|
|
75658
76506
|
if (!health.ok) {
|
|
75659
|
-
console.error(
|
|
76507
|
+
console.error(import_picocolors23.default.red(`Grafana unreachable: ${health.detail}`));
|
|
75660
76508
|
return 1;
|
|
75661
76509
|
}
|
|
75662
76510
|
const stepNum = opts.step !== void 0 ? Number(opts.step) : void 0;
|
|
@@ -75685,14 +76533,14 @@ async function runMetrics(hint, opts) {
|
|
|
75685
76533
|
return 0;
|
|
75686
76534
|
}
|
|
75687
76535
|
if (series.length === 0) {
|
|
75688
|
-
console.log(
|
|
76536
|
+
console.log(import_picocolors23.default.dim("No series returned."));
|
|
75689
76537
|
return 0;
|
|
75690
76538
|
}
|
|
75691
76539
|
for (const s of series) {
|
|
75692
76540
|
const summary = summarize(s);
|
|
75693
76541
|
const label = labelStr(s.labels);
|
|
75694
76542
|
console.log(
|
|
75695
|
-
` ${
|
|
76543
|
+
` ${import_picocolors23.default.cyan(label || "(no labels)")} last=${import_picocolors23.default.bold(fmtNum(summary.last))} avg=${fmtNum(summary.avg)}`
|
|
75696
76544
|
);
|
|
75697
76545
|
}
|
|
75698
76546
|
return 0;
|
|
@@ -75707,36 +76555,36 @@ async function runMetrics(hint, opts) {
|
|
|
75707
76555
|
if (findings2.length === 0) {
|
|
75708
76556
|
if (hint !== void 0) {
|
|
75709
76557
|
const tokens = extractHintTokens(hint);
|
|
75710
|
-
console.log(
|
|
76558
|
+
console.log(import_picocolors23.default.dim(`No panels matched hint "${hint}".`));
|
|
75711
76559
|
if (tokens.length > 0) {
|
|
75712
|
-
console.log(
|
|
76560
|
+
console.log(import_picocolors23.default.dim(` Searched for panels containing: ${tokens.join(", ")}`));
|
|
75713
76561
|
}
|
|
75714
76562
|
console.log(
|
|
75715
|
-
|
|
75716
|
-
` Tip: run ${
|
|
76563
|
+
import_picocolors23.default.dim(
|
|
76564
|
+
` Tip: run ${import_picocolors23.default.bold("horus metrics")} (no hint) to see all available panels, then use a panel title or keyword directly.`
|
|
75717
76565
|
)
|
|
75718
76566
|
);
|
|
75719
76567
|
} else {
|
|
75720
|
-
console.log(
|
|
76568
|
+
console.log(import_picocolors23.default.dim("No panels found in configured Grafana dashboards."));
|
|
75721
76569
|
}
|
|
75722
76570
|
return 0;
|
|
75723
76571
|
}
|
|
75724
76572
|
const hintSuffix = hint !== void 0 ? ` (hint: "${hint}")` : "";
|
|
75725
76573
|
const matchSources = hint !== void 0 ? [...new Set(findings2.map((f) => f.matchSource).filter(Boolean))] : [];
|
|
75726
|
-
const matchSuffix = matchSources.length > 0 ?
|
|
76574
|
+
const matchSuffix = matchSources.length > 0 ? import_picocolors23.default.dim(` [matched via ${matchSources.join(", ")}]`) : "";
|
|
75727
76575
|
console.log(
|
|
75728
|
-
|
|
76576
|
+
import_picocolors23.default.bold(
|
|
75729
76577
|
`Grafana metrics${hintSuffix} \u2014 ${findings2.length} series across panels`
|
|
75730
76578
|
) + matchSuffix
|
|
75731
76579
|
);
|
|
75732
76580
|
console.log(
|
|
75733
|
-
|
|
76581
|
+
import_picocolors23.default.dim(
|
|
75734
76582
|
` window: ${new Date(from * 1e3).toISOString()} \u2013 ${new Date(to * 1e3).toISOString()}`
|
|
75735
76583
|
)
|
|
75736
76584
|
);
|
|
75737
76585
|
console.log("");
|
|
75738
76586
|
if (flagged.length === 0) {
|
|
75739
|
-
console.log(
|
|
76587
|
+
console.log(import_picocolors23.default.green(" No anomalies detected."));
|
|
75740
76588
|
} else {
|
|
75741
76589
|
for (const f of flagged) {
|
|
75742
76590
|
const color = anomalyColor(f.anomaly);
|
|
@@ -75745,14 +76593,14 @@ async function runMetrics(hint, opts) {
|
|
|
75745
76593
|
const lblStr = labelStr(f.labels);
|
|
75746
76594
|
console.log(
|
|
75747
76595
|
color(
|
|
75748
|
-
` ${label.padEnd(22)} ${f.panelTitle.padEnd(40).slice(0, 40)} ${fmtNum(f.baselineAvg)} -> ${fmtNum(f.currentAvg)} (${ratioStr})` + (lblStr !== "" ? ` ${
|
|
76596
|
+
` ${label.padEnd(22)} ${f.panelTitle.padEnd(40).slice(0, 40)} ${fmtNum(f.baselineAvg)} -> ${fmtNum(f.currentAvg)} (${ratioStr})` + (lblStr !== "" ? ` ${import_picocolors23.default.dim(lblStr)}` : "")
|
|
75749
76597
|
)
|
|
75750
76598
|
);
|
|
75751
76599
|
}
|
|
75752
76600
|
}
|
|
75753
76601
|
if (ok.length > 0) {
|
|
75754
76602
|
console.log("");
|
|
75755
|
-
console.log(
|
|
76603
|
+
console.log(import_picocolors23.default.dim(` ${ok.length} panel series with no anomaly.`));
|
|
75756
76604
|
}
|
|
75757
76605
|
if (opts.ai) {
|
|
75758
76606
|
const result = await renderAiInterpretation({
|
|
@@ -75767,19 +76615,19 @@ async function runMetrics(hint, opts) {
|
|
|
75767
76615
|
});
|
|
75768
76616
|
console.log("\n" + renderInterpretation(result));
|
|
75769
76617
|
if (!result.ok) {
|
|
75770
|
-
console.error(
|
|
76618
|
+
console.error(import_picocolors23.default.yellow(`[ai] ${result.warning}`));
|
|
75771
76619
|
}
|
|
75772
76620
|
}
|
|
75773
76621
|
return 0;
|
|
75774
76622
|
} catch (err) {
|
|
75775
|
-
console.error(
|
|
76623
|
+
console.error(import_picocolors23.default.red(err.message));
|
|
75776
76624
|
return 1;
|
|
75777
76625
|
}
|
|
75778
76626
|
}
|
|
75779
76627
|
|
|
75780
76628
|
// ../../packages/cli/src/commands/state.ts
|
|
75781
76629
|
init_cjs_shims();
|
|
75782
|
-
var
|
|
76630
|
+
var import_picocolors24 = __toESM(require_picocolors(), 1);
|
|
75783
76631
|
var STATE_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
|
|
75784
76632
|
|
|
75785
76633
|
Evidence used
|
|
@@ -75805,13 +76653,13 @@ async function runState(opts) {
|
|
|
75805
76653
|
try {
|
|
75806
76654
|
renv = resolveEnvironment(config, { project: opts.project, env: opts.env });
|
|
75807
76655
|
} catch (err) {
|
|
75808
|
-
console.error(
|
|
76656
|
+
console.error(import_picocolors24.default.red(err.message));
|
|
75809
76657
|
return 1;
|
|
75810
76658
|
}
|
|
75811
76659
|
const mongo = mongoForEnv(renv);
|
|
75812
76660
|
if (mongo === null) {
|
|
75813
76661
|
console.error(
|
|
75814
|
-
|
|
76662
|
+
import_picocolors24.default.red(
|
|
75815
76663
|
`No MongoDB connector configured for ${renv.project}/${renv.env} (set the project's Mongo URL env var).`
|
|
75816
76664
|
)
|
|
75817
76665
|
);
|
|
@@ -75819,7 +76667,7 @@ async function runState(opts) {
|
|
|
75819
76667
|
}
|
|
75820
76668
|
const health = await mongo.health();
|
|
75821
76669
|
if (!health.ok) {
|
|
75822
|
-
console.error(
|
|
76670
|
+
console.error(import_picocolors24.default.red(`MongoDB unreachable: ${health.detail}`));
|
|
75823
76671
|
await mongo.close();
|
|
75824
76672
|
return 1;
|
|
75825
76673
|
}
|
|
@@ -75845,38 +76693,38 @@ async function runState(opts) {
|
|
|
75845
76693
|
);
|
|
75846
76694
|
return 0;
|
|
75847
76695
|
}
|
|
75848
|
-
const discoveryNote = analysis.autoDiscovered ?
|
|
76696
|
+
const discoveryNote = analysis.autoDiscovered ? import_picocolors24.default.dim(` (${analysis.collections.length} collections, auto-discovered)`) : "";
|
|
75849
76697
|
console.log(
|
|
75850
|
-
|
|
76698
|
+
import_picocolors24.default.bold("State analysis") + import_picocolors24.default.dim(` \u2014 ${renv.project}/${renv.env} \xB7 db ${analysis.database}`) + discoveryNote
|
|
75851
76699
|
);
|
|
75852
76700
|
console.log("");
|
|
75853
76701
|
let signals = 0;
|
|
75854
76702
|
for (const c of analysis.collections) {
|
|
75855
76703
|
const flags = [];
|
|
75856
76704
|
if (c.isStale === true) {
|
|
75857
|
-
flags.push(
|
|
76705
|
+
flags.push(import_picocolors24.default.yellow(`stale (last ${c.dateField}, ~${Math.round(c.ageHours ?? 0)}h)`));
|
|
75858
76706
|
}
|
|
75859
76707
|
for (const a of c.anomalies) {
|
|
75860
|
-
flags.push(
|
|
76708
|
+
flags.push(import_picocolors24.default.red(`${a.count}\xD7 "${a.value}"`));
|
|
75861
76709
|
signals += 1;
|
|
75862
76710
|
}
|
|
75863
76711
|
if (c.isStale === true) signals += 1;
|
|
75864
|
-
const head = ` ${
|
|
75865
|
-
console.log(flags.length > 0 ? `${head} ${flags.join(" ")}` :
|
|
76712
|
+
const head = ` ${import_picocolors24.default.bold(c.collection.padEnd(18).slice(0, 18))} ${import_picocolors24.default.dim(String(c.count).padStart(8) + " docs")}`;
|
|
76713
|
+
console.log(flags.length > 0 ? `${head} ${flags.join(" ")}` : import_picocolors24.default.dim(head));
|
|
75866
76714
|
}
|
|
75867
76715
|
console.log("");
|
|
75868
76716
|
if (analysis.collections.length === 0) {
|
|
75869
76717
|
console.log(
|
|
75870
|
-
|
|
76718
|
+
import_picocolors24.default.yellow(" No collections discovered in this database.") + import_picocolors24.default.dim(" Verify the database name in your MongoDB connector config.")
|
|
75871
76719
|
);
|
|
75872
76720
|
} else {
|
|
75873
76721
|
const scope = analysis.autoDiscovered ? "collection(s)" : "allowlisted collection(s)";
|
|
75874
76722
|
console.log(
|
|
75875
|
-
signals > 0 ? ` ${
|
|
76723
|
+
signals > 0 ? ` ${import_picocolors24.default.bold(String(signals))} state signal(s) across ${analysis.collections.length} ${scope}` : import_picocolors24.default.dim(` No state anomalies across ${analysis.collections.length} ${scope}.`)
|
|
75876
76724
|
);
|
|
75877
76725
|
if (analysis.autoDiscovered) {
|
|
75878
76726
|
console.log(
|
|
75879
|
-
|
|
76727
|
+
import_picocolors24.default.dim(
|
|
75880
76728
|
` Tip: add a "collections" list to your MongoDB connector config to restrict analysis.`
|
|
75881
76729
|
)
|
|
75882
76730
|
);
|
|
@@ -75894,7 +76742,7 @@ async function runState(opts) {
|
|
|
75894
76742
|
});
|
|
75895
76743
|
console.log("\n" + renderInterpretation(result));
|
|
75896
76744
|
if (!result.ok) {
|
|
75897
|
-
console.error(
|
|
76745
|
+
console.error(import_picocolors24.default.yellow(`[ai] ${result.warning}`));
|
|
75898
76746
|
}
|
|
75899
76747
|
}
|
|
75900
76748
|
return 0;
|
|
@@ -75902,20 +76750,20 @@ async function runState(opts) {
|
|
|
75902
76750
|
await mongo.close();
|
|
75903
76751
|
}
|
|
75904
76752
|
} catch (err) {
|
|
75905
|
-
console.error(
|
|
76753
|
+
console.error(import_picocolors24.default.red(err.message));
|
|
75906
76754
|
return 1;
|
|
75907
76755
|
}
|
|
75908
76756
|
}
|
|
75909
76757
|
|
|
75910
76758
|
// ../../packages/cli/src/commands/init.ts
|
|
75911
76759
|
init_cjs_shims();
|
|
75912
|
-
var
|
|
75913
|
-
var
|
|
76760
|
+
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
76761
|
+
var import_node_path10 = require("path");
|
|
75914
76762
|
async function runInit(opts) {
|
|
75915
76763
|
try {
|
|
75916
76764
|
const cwd = process.cwd();
|
|
75917
|
-
const root = opts.path ? (0,
|
|
75918
|
-
const name = opts.name ?? (0,
|
|
76765
|
+
const root = opts.path ? (0, import_node_path10.resolve)(opts.path) : findRepoRoot(cwd) ?? cwd;
|
|
76766
|
+
const name = opts.name ?? (0, import_node_path10.basename)(root);
|
|
75919
76767
|
const envName = opts.env ?? "production";
|
|
75920
76768
|
const repo = { name, path: root };
|
|
75921
76769
|
if (opts.source) repo["source"] = { hostUrl: opts.source };
|
|
@@ -75930,65 +76778,65 @@ async function runInit(opts) {
|
|
|
75930
76778
|
const configPath = writeLocalConfig(root, file);
|
|
75931
76779
|
registerProject(name, root, configPath);
|
|
75932
76780
|
ensureProjectGitignore(root);
|
|
75933
|
-
console.log(`${
|
|
75934
|
-
console.log(
|
|
75935
|
-
console.log(
|
|
76781
|
+
console.log(`${import_picocolors25.default.green("\u2713")} Initialized Horus project ${import_picocolors25.default.bold(name)}`);
|
|
76782
|
+
console.log(import_picocolors25.default.dim(` config: ${configPath}`));
|
|
76783
|
+
console.log(import_picocolors25.default.dim(` registered: horus investigate --name ${name} "<hint>"`));
|
|
75936
76784
|
if (!opts.source) {
|
|
75937
76785
|
console.log(
|
|
75938
|
-
|
|
76786
|
+
import_picocolors25.default.dim(" no source-intelligence host set \u2014 run `horus index` to analyze + host, or pass --source <url>")
|
|
75939
76787
|
);
|
|
75940
76788
|
}
|
|
75941
76789
|
console.log(
|
|
75942
|
-
|
|
76790
|
+
import_picocolors25.default.dim(" add runtime connectors (elasticsearch/mongodb/grafana) to .horus/config.json")
|
|
75943
76791
|
);
|
|
75944
76792
|
console.log(
|
|
75945
|
-
|
|
76793
|
+
import_picocolors25.default.dim(" .horus/ is gitignored \u2014 local config and runtime state stay out of version control")
|
|
75946
76794
|
);
|
|
75947
76795
|
return 0;
|
|
75948
76796
|
} catch (err) {
|
|
75949
|
-
console.error(
|
|
76797
|
+
console.error(import_picocolors25.default.red(err.message));
|
|
75950
76798
|
return 1;
|
|
75951
76799
|
}
|
|
75952
76800
|
}
|
|
75953
76801
|
|
|
75954
76802
|
// ../../packages/cli/src/commands/projects.ts
|
|
75955
76803
|
init_cjs_shims();
|
|
75956
|
-
var
|
|
76804
|
+
var import_picocolors26 = __toESM(require_picocolors(), 1);
|
|
75957
76805
|
async function runProjects() {
|
|
75958
76806
|
const reg = readRegistry();
|
|
75959
76807
|
const names = Object.keys(reg.projects).sort();
|
|
75960
76808
|
if (names.length === 0) {
|
|
75961
76809
|
console.log(
|
|
75962
|
-
|
|
76810
|
+
import_picocolors26.default.dim("No registered projects. Run `horus index` (or `horus init`) inside a repo.")
|
|
75963
76811
|
);
|
|
75964
76812
|
return 0;
|
|
75965
76813
|
}
|
|
75966
|
-
console.log(
|
|
76814
|
+
console.log(import_picocolors26.default.bold("Registered Horus projects"));
|
|
75967
76815
|
console.log("");
|
|
75968
76816
|
for (const name of names) {
|
|
75969
76817
|
const entry2 = reg.projects[name];
|
|
75970
76818
|
if (entry2 === void 0) continue;
|
|
75971
|
-
console.log(` ${
|
|
76819
|
+
console.log(` ${import_picocolors26.default.bold(name.padEnd(22))} ${import_picocolors26.default.dim(entry2.root)}`);
|
|
75972
76820
|
}
|
|
75973
76821
|
console.log("");
|
|
75974
|
-
console.log(
|
|
76822
|
+
console.log(import_picocolors26.default.dim(' horus investigate --name <name> "<hint>"'));
|
|
75975
76823
|
return 0;
|
|
75976
76824
|
}
|
|
75977
76825
|
|
|
75978
76826
|
// ../../packages/cli/src/commands/setup.ts
|
|
75979
76827
|
init_cjs_shims();
|
|
75980
|
-
var
|
|
76828
|
+
var import_picocolors27 = __toESM(require_picocolors(), 1);
|
|
75981
76829
|
var DEFAULT_DB_URL3 = "postgresql://horus:horus@localhost:5433/horus";
|
|
75982
76830
|
async function runSetup(opts) {
|
|
75983
76831
|
const write = opts.write ?? ((line2) => console.log(line2));
|
|
75984
76832
|
let ok = true;
|
|
75985
|
-
write(
|
|
76833
|
+
write(import_picocolors27.default.bold("\nHorus setup\n"));
|
|
75986
76834
|
const backendVersion = await getSourceVersion();
|
|
75987
76835
|
if (backendVersion === null) {
|
|
75988
76836
|
ok = false;
|
|
75989
|
-
write(` ${
|
|
76837
|
+
write(` ${import_picocolors27.default.red("\u25CF")} Horus source-intelligence backend not found`);
|
|
75990
76838
|
write(
|
|
75991
|
-
|
|
76839
|
+
import_picocolors27.default.dim(
|
|
75992
76840
|
` install it (Python 3.11+ required):
|
|
75993
76841
|
pip install horus-source
|
|
75994
76842
|
ensure ~/.local/bin is on your PATH`
|
|
@@ -75997,17 +76845,17 @@ async function runSetup(opts) {
|
|
|
75997
76845
|
} else if (backendVersion !== PINNED_SOURCE_VERSION) {
|
|
75998
76846
|
ok = false;
|
|
75999
76847
|
write(
|
|
76000
|
-
` ${
|
|
76848
|
+
` ${import_picocolors27.default.yellow("\u25CF")} Horus source-intelligence backend version mismatch` + import_picocolors27.default.dim(` (installed: ${backendVersion}, required: ${PINNED_SOURCE_VERSION})`)
|
|
76001
76849
|
);
|
|
76002
76850
|
write(
|
|
76003
|
-
|
|
76851
|
+
import_picocolors27.default.dim(
|
|
76004
76852
|
` update it:
|
|
76005
76853
|
pip install --upgrade horus-source`
|
|
76006
76854
|
)
|
|
76007
76855
|
);
|
|
76008
76856
|
} else {
|
|
76009
76857
|
write(
|
|
76010
|
-
` ${
|
|
76858
|
+
` ${import_picocolors27.default.green("\u25CF")} Horus source-intelligence backend ` + import_picocolors27.default.dim(`(${backendVersion})`)
|
|
76011
76859
|
);
|
|
76012
76860
|
}
|
|
76013
76861
|
let dbUrl = process.env["DATABASE_URL"] ?? DEFAULT_DB_URL3;
|
|
@@ -76019,11 +76867,11 @@ async function runSetup(opts) {
|
|
|
76019
76867
|
}
|
|
76020
76868
|
const db = await checkDatabase(dbUrl);
|
|
76021
76869
|
if (db.reachable) {
|
|
76022
|
-
write(` ${
|
|
76870
|
+
write(` ${import_picocolors27.default.green("\u25CF")} Postgres reachable ${import_picocolors27.default.dim(`(${db.schemaDetail})`)}`);
|
|
76023
76871
|
if (!db.schemaReady) {
|
|
76024
76872
|
ok = false;
|
|
76025
76873
|
write(
|
|
76026
|
-
|
|
76874
|
+
import_picocolors27.default.dim(
|
|
76027
76875
|
` schema not applied \u2014 run migrations:
|
|
76028
76876
|
from the Horus repo: pnpm db migrate
|
|
76029
76877
|
or apply the schema directly: https://github.com/meritt-dev/horus/tree/master/packages/db/drizzle`
|
|
@@ -76032,9 +76880,9 @@ async function runSetup(opts) {
|
|
|
76032
76880
|
}
|
|
76033
76881
|
} else {
|
|
76034
76882
|
ok = false;
|
|
76035
|
-
write(` ${
|
|
76883
|
+
write(` ${import_picocolors27.default.red("\u25CF")} Postgres unreachable`);
|
|
76036
76884
|
write(
|
|
76037
|
-
|
|
76885
|
+
import_picocolors27.default.dim(
|
|
76038
76886
|
` start a local instance:
|
|
76039
76887
|
docker run -d --name horus-db \\
|
|
76040
76888
|
-e POSTGRES_USER=horus -e POSTGRES_PASSWORD=horus -e POSTGRES_DB=horus \\
|
|
@@ -76059,10 +76907,10 @@ async function runSetup(opts) {
|
|
|
76059
76907
|
if (!health.ok) {
|
|
76060
76908
|
ok = false;
|
|
76061
76909
|
write(
|
|
76062
|
-
` ${
|
|
76910
|
+
` ${import_picocolors27.default.red("\u25CF")} Source intelligence host unreachable for ${import_picocolors27.default.bold(repo.name)} ` + import_picocolors27.default.dim(`(${repoHostUrl})`)
|
|
76063
76911
|
);
|
|
76064
76912
|
write(
|
|
76065
|
-
|
|
76913
|
+
import_picocolors27.default.dim(
|
|
76066
76914
|
` start the source intelligence host:
|
|
76067
76915
|
cd ${repo.path}
|
|
76068
76916
|
horus index`
|
|
@@ -76073,10 +76921,10 @@ async function runSetup(opts) {
|
|
|
76073
76921
|
if (count === 0) {
|
|
76074
76922
|
ok = false;
|
|
76075
76923
|
write(
|
|
76076
|
-
` ${
|
|
76924
|
+
` ${import_picocolors27.default.yellow("\u25CF")} Source intelligence host running but ${import_picocolors27.default.bold(repo.name)} is not indexed`
|
|
76077
76925
|
);
|
|
76078
76926
|
write(
|
|
76079
|
-
|
|
76927
|
+
import_picocolors27.default.dim(
|
|
76080
76928
|
` index the repo:
|
|
76081
76929
|
cd ${repo.path}
|
|
76082
76930
|
horus index`
|
|
@@ -76084,7 +76932,7 @@ async function runSetup(opts) {
|
|
|
76084
76932
|
);
|
|
76085
76933
|
} else {
|
|
76086
76934
|
write(
|
|
76087
|
-
` ${
|
|
76935
|
+
` ${import_picocolors27.default.green("\u25CF")} ${import_picocolors27.default.bold(repo.name)} \u2014 ${count} nodes indexed ` + import_picocolors27.default.dim(`(${repoHostUrl})`)
|
|
76088
76936
|
);
|
|
76089
76937
|
}
|
|
76090
76938
|
}
|
|
@@ -76094,10 +76942,10 @@ async function runSetup(opts) {
|
|
|
76094
76942
|
write("");
|
|
76095
76943
|
if (ok) {
|
|
76096
76944
|
write(
|
|
76097
|
-
|
|
76945
|
+
import_picocolors27.default.green("Ready.") + ' Next: `cd` into a repo and run `horus index`, then `horus investigate "<hint>"`.'
|
|
76098
76946
|
);
|
|
76099
76947
|
} else {
|
|
76100
|
-
write(
|
|
76948
|
+
write(import_picocolors27.default.yellow("Resolve the items above, then re-run `horus setup`."));
|
|
76101
76949
|
}
|
|
76102
76950
|
return ok ? 0 : 1;
|
|
76103
76951
|
}
|
|
@@ -76106,12 +76954,12 @@ async function runSetup(opts) {
|
|
|
76106
76954
|
init_cjs_shims();
|
|
76107
76955
|
var import_node_readline = require("readline");
|
|
76108
76956
|
var import_node_child_process6 = require("child_process");
|
|
76109
|
-
var
|
|
76957
|
+
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
76110
76958
|
|
|
76111
76959
|
// ../../packages/cli/src/lib/tty-selector.ts
|
|
76112
76960
|
init_cjs_shims();
|
|
76113
76961
|
var import_node_tty = require("tty");
|
|
76114
|
-
var
|
|
76962
|
+
var import_picocolors28 = __toESM(require_picocolors(), 1);
|
|
76115
76963
|
var ExitPromptError = class extends Error {
|
|
76116
76964
|
constructor(message = "Prompt was cancelled") {
|
|
76117
76965
|
super(message);
|
|
@@ -76167,7 +77015,7 @@ async function checkboxSearch(opts) {
|
|
|
76167
77015
|
clear();
|
|
76168
77016
|
const lines = [];
|
|
76169
77017
|
lines.push(
|
|
76170
|
-
`? ${
|
|
77018
|
+
`? ${import_picocolors28.default.bold(message)} ${import_picocolors28.default.dim("(\u2191\u2193 navigate \u2022 space toggle \u2022 enter confirm \u2022 type filter)")}`
|
|
76171
77019
|
);
|
|
76172
77020
|
const visible = filtered.slice(visibleOffset, visibleOffset + pageSize);
|
|
76173
77021
|
for (let i = 0; i < visible.length; i++) {
|
|
@@ -76175,29 +77023,29 @@ async function checkboxSearch(opts) {
|
|
|
76175
77023
|
const choice = visible[i];
|
|
76176
77024
|
const isCursor = idx === cursor;
|
|
76177
77025
|
const isSelected = selected.has(choice);
|
|
76178
|
-
const marker = isSelected ?
|
|
77026
|
+
const marker = isSelected ? import_picocolors28.default.green("\u25CF") : " ";
|
|
76179
77027
|
const prefix = `[${marker}]`;
|
|
76180
|
-
const label = isCursor ?
|
|
76181
|
-
const pointer = isCursor ?
|
|
77028
|
+
const label = isCursor ? import_picocolors28.default.cyan(choice) : choice;
|
|
77029
|
+
const pointer = isCursor ? import_picocolors28.default.cyan("\u276F") : " ";
|
|
76182
77030
|
lines.push(` ${pointer} ${prefix} ${label}`);
|
|
76183
77031
|
}
|
|
76184
77032
|
if (filtered.length === 0) {
|
|
76185
|
-
lines.push(
|
|
77033
|
+
lines.push(import_picocolors28.default.dim(" No matches"));
|
|
76186
77034
|
}
|
|
76187
77035
|
const statusParts = [
|
|
76188
77036
|
`${selected.size} selected`,
|
|
76189
77037
|
`${filtered.length}/${choices.length} matches`
|
|
76190
77038
|
];
|
|
76191
77039
|
if (filter) statusParts.push(`filter: ${filter}`);
|
|
76192
|
-
lines.push(
|
|
77040
|
+
lines.push(import_picocolors28.default.dim(` ${statusParts.join(" \xB7 ")}`));
|
|
76193
77041
|
output.write(lines.join("\n"));
|
|
76194
77042
|
lastRenderLines = lines.length;
|
|
76195
77043
|
}
|
|
76196
77044
|
function finish(values2) {
|
|
76197
77045
|
cleanup();
|
|
76198
|
-
const summary = values2.length > 0 ? values2.join(", ") :
|
|
77046
|
+
const summary = values2.length > 0 ? values2.join(", ") : import_picocolors28.default.dim("none");
|
|
76199
77047
|
output.write(`
|
|
76200
|
-
? ${
|
|
77048
|
+
? ${import_picocolors28.default.bold(message)} ${summary}
|
|
76201
77049
|
`);
|
|
76202
77050
|
resolve8(values2);
|
|
76203
77051
|
}
|
|
@@ -76289,7 +77137,7 @@ async function checkboxSearch(opts) {
|
|
|
76289
77137
|
// ../../packages/cli/src/commands/connect-ai.ts
|
|
76290
77138
|
init_cjs_shims();
|
|
76291
77139
|
var import_node_child_process5 = require("child_process");
|
|
76292
|
-
var
|
|
77140
|
+
var import_picocolors29 = __toESM(require_picocolors(), 1);
|
|
76293
77141
|
function cliInstalled(id) {
|
|
76294
77142
|
const r = (0, import_node_child_process5.spawnSync)(id, ["--version"], { stdio: "ignore", timeout: 2e3 });
|
|
76295
77143
|
return !r.error;
|
|
@@ -76317,13 +77165,13 @@ async function runConnectAi(opts) {
|
|
|
76317
77165
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
76318
77166
|
const configPath = discoverLocalConfig(cwd) ?? localConfigPath(root);
|
|
76319
77167
|
const installed = LOCAL_PROVIDER_IDS.filter((id) => cliInstalled(id));
|
|
76320
|
-
console.log(
|
|
77168
|
+
console.log(import_picocolors29.default.bold("\nAI providers"));
|
|
76321
77169
|
console.log(
|
|
76322
|
-
|
|
77170
|
+
import_picocolors29.default.dim(
|
|
76323
77171
|
` Local CLIs on PATH: ${installed.length > 0 ? installed.join(", ") : "none detected"}`
|
|
76324
77172
|
)
|
|
76325
77173
|
);
|
|
76326
|
-
console.log(
|
|
77174
|
+
console.log(import_picocolors29.default.dim(" Cloud: anthropic (Anthropic Claude API \u2014 used by `investigate --ai`)\n"));
|
|
76327
77175
|
let provider = opts.provider;
|
|
76328
77176
|
if (provider === void 0) {
|
|
76329
77177
|
if (isInteractive()) {
|
|
@@ -76334,7 +77182,7 @@ async function runConnectAi(opts) {
|
|
|
76334
77182
|
}
|
|
76335
77183
|
}
|
|
76336
77184
|
if (!AI_PROVIDERS.includes(provider)) {
|
|
76337
|
-
console.error(
|
|
77185
|
+
console.error(import_picocolors29.default.red(`Unknown AI provider: ${provider}`) + import_picocolors29.default.dim(`
|
|
76338
77186
|
supported: ${AI_PROVIDERS.join(", ")}`));
|
|
76339
77187
|
return 1;
|
|
76340
77188
|
}
|
|
@@ -76347,7 +77195,7 @@ async function runConnectAi(opts) {
|
|
|
76347
77195
|
}
|
|
76348
77196
|
if (!apiKey) {
|
|
76349
77197
|
console.error(
|
|
76350
|
-
|
|
77198
|
+
import_picocolors29.default.red("No API key provided.") + import_picocolors29.default.dim("\n Pass --api-key sk-ant-\u2026 or set ANTHROPIC_API_KEY and re-run.")
|
|
76351
77199
|
);
|
|
76352
77200
|
return 1;
|
|
76353
77201
|
}
|
|
@@ -76355,28 +77203,28 @@ async function runConnectAi(opts) {
|
|
|
76355
77203
|
const probe2 = await probeAnthropic(apiKey);
|
|
76356
77204
|
if (!probe2.ok) {
|
|
76357
77205
|
console.error(`
|
|
76358
|
-
${
|
|
76359
|
-
console.error(
|
|
77206
|
+
${import_picocolors29.default.red("\u2717 Anthropic key check failed:")} ${probe2.detail}`);
|
|
77207
|
+
console.error(import_picocolors29.default.dim(" Fix the key and retry, or pass --no-test to skip."));
|
|
76360
77208
|
return 1;
|
|
76361
77209
|
}
|
|
76362
77210
|
console.log(`
|
|
76363
|
-
${
|
|
77211
|
+
${import_picocolors29.default.green("\u2713")} anthropic ${import_picocolors29.default.dim(`(${probe2.detail})`)}`);
|
|
76364
77212
|
}
|
|
76365
77213
|
if (opts.model) aiBlock["anthropic"] = { model: opts.model };
|
|
76366
77214
|
storedKey = apiKey;
|
|
76367
77215
|
} else {
|
|
76368
77216
|
if (!installed.includes(provider)) {
|
|
76369
77217
|
console.error(
|
|
76370
|
-
|
|
77218
|
+
import_picocolors29.default.red(`${provider} CLI not found on PATH.`) + import_picocolors29.default.dim(`
|
|
76371
77219
|
Install it first, or choose anthropic.`)
|
|
76372
77220
|
);
|
|
76373
77221
|
return 1;
|
|
76374
77222
|
}
|
|
76375
77223
|
const desc2 = DEFAULT_LOCAL_PROVIDER_REGISTRY.get(provider);
|
|
76376
77224
|
console.log(`
|
|
76377
|
-
${
|
|
77225
|
+
${import_picocolors29.default.green("\u2713")} ${desc2?.displayName ?? provider} ${import_picocolors29.default.dim("detected on PATH")}`);
|
|
76378
77226
|
console.log(
|
|
76379
|
-
|
|
77227
|
+
import_picocolors29.default.dim(
|
|
76380
77228
|
" Note: `investigate --ai` currently uses the Anthropic API for narrative.\n Local-CLI narrative generation is recorded as your preference for future use."
|
|
76381
77229
|
)
|
|
76382
77230
|
);
|
|
@@ -76394,15 +77242,15 @@ ${import_picocolors27.default.green("\u2713")} ${desc2?.displayName ?? provider}
|
|
|
76394
77242
|
});
|
|
76395
77243
|
}
|
|
76396
77244
|
console.log(`
|
|
76397
|
-
${
|
|
76398
|
-
console.log(
|
|
77245
|
+
${import_picocolors29.default.green("\u2713")} ${import_picocolors29.default.bold("ai")} provider saved \u2192 ${import_picocolors29.default.dim(configPath)}`);
|
|
77246
|
+
console.log(import_picocolors29.default.dim(` provider: ${provider}`));
|
|
76399
77247
|
if (storedKey && secretsPathStr) {
|
|
76400
|
-
console.log(
|
|
77248
|
+
console.log(import_picocolors29.default.dim(` anthropic key: ${redactKey(storedKey)} \u2192 ${secretsPathStr} (gitignored)`));
|
|
76401
77249
|
}
|
|
76402
|
-
console.log(
|
|
77250
|
+
console.log(import_picocolors29.default.dim(' run: horus investigate "<hint>" --ai'));
|
|
76403
77251
|
return 0;
|
|
76404
77252
|
} catch (err) {
|
|
76405
|
-
console.error(
|
|
77253
|
+
console.error(import_picocolors29.default.red(err instanceof Error ? err.message : String(err)));
|
|
76406
77254
|
return 1;
|
|
76407
77255
|
}
|
|
76408
77256
|
}
|
|
@@ -76434,7 +77282,7 @@ async function runConnect(type, opts) {
|
|
|
76434
77282
|
}
|
|
76435
77283
|
if (!SUPPORTED.includes(type)) {
|
|
76436
77284
|
console.error(
|
|
76437
|
-
|
|
77285
|
+
import_picocolors30.default.red(`Unknown connector type: ${type}`) + import_picocolors30.default.dim(`
|
|
76438
77286
|
supported: ${SUPPORTED.join(", ")}, ai`)
|
|
76439
77287
|
);
|
|
76440
77288
|
return 1;
|
|
@@ -76465,20 +77313,20 @@ async function runConnect(type, opts) {
|
|
|
76465
77313
|
if (!probeResult.ok) {
|
|
76466
77314
|
console.error(
|
|
76467
77315
|
`
|
|
76468
|
-
${
|
|
77316
|
+
${import_picocolors30.default.red(`\u2717 Could not reach ${connectorType}:`)} ${probeResult.detail}` + import_picocolors30.default.dim("\n Fix the connection and retry, or pass --no-test to skip.")
|
|
76469
77317
|
);
|
|
76470
77318
|
return 1;
|
|
76471
77319
|
}
|
|
76472
77320
|
console.log(
|
|
76473
77321
|
`
|
|
76474
|
-
${
|
|
77322
|
+
${import_picocolors30.default.green("\u2713")} ${connectorType} reachable ${import_picocolors30.default.dim(`(${probeResult.detail})`)}`
|
|
76475
77323
|
);
|
|
76476
77324
|
}
|
|
76477
77325
|
const hasLiteralCredentials = filled.url !== void 0 || filled.password !== void 0 || filled.username !== void 0;
|
|
76478
77326
|
if (hasLiteralCredentials) {
|
|
76479
77327
|
if (isGitTracked(configPath, root)) {
|
|
76480
77328
|
console.error(
|
|
76481
|
-
|
|
77329
|
+
import_picocolors30.default.red(".horus/config.json is already tracked by Git.") + "\nStoring credentials here would expose them in the repository.\n" + import_picocolors30.default.dim(
|
|
76482
77330
|
" 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."
|
|
76483
77331
|
)
|
|
76484
77332
|
);
|
|
@@ -76488,18 +77336,18 @@ ${import_picocolors28.default.green("\u2713")} ${connectorType} reachable ${impo
|
|
|
76488
77336
|
}
|
|
76489
77337
|
patchLocalConnector(configPath, connectorType, patch, filled.env);
|
|
76490
77338
|
console.log(
|
|
76491
|
-
`${
|
|
77339
|
+
`${import_picocolors30.default.green("\u2713")} ${import_picocolors30.default.bold(connectorType)} connector saved \u2192 ${import_picocolors30.default.dim(configPath)}`
|
|
76492
77340
|
);
|
|
76493
77341
|
printSummary(connectorType, filled);
|
|
76494
|
-
console.log(
|
|
77342
|
+
console.log(import_picocolors30.default.dim(`
|
|
76495
77343
|
run: horus investigate "<hint>"`));
|
|
76496
77344
|
return 0;
|
|
76497
77345
|
} catch (err) {
|
|
76498
77346
|
if (err instanceof ExitPromptError) {
|
|
76499
|
-
console.error(
|
|
77347
|
+
console.error(import_picocolors30.default.red("Cancelled."));
|
|
76500
77348
|
return 1;
|
|
76501
77349
|
}
|
|
76502
|
-
console.error(
|
|
77350
|
+
console.error(import_picocolors30.default.red(err.message));
|
|
76503
77351
|
return 1;
|
|
76504
77352
|
}
|
|
76505
77353
|
}
|
|
@@ -76508,7 +77356,7 @@ async function fillInteractive(type, opts) {
|
|
|
76508
77356
|
if (!needsInteraction) return opts;
|
|
76509
77357
|
console.log(
|
|
76510
77358
|
`
|
|
76511
|
-
${
|
|
77359
|
+
${import_picocolors30.default.bold(`Connect ${type}`)} ${import_picocolors30.default.dim("(press Enter to skip optional fields)")}
|
|
76512
77360
|
`
|
|
76513
77361
|
);
|
|
76514
77362
|
const filled = { ...opts };
|
|
@@ -76563,7 +77411,7 @@ ${import_picocolors28.default.bold(`Connect ${type}`)} ${import_picocolors28.def
|
|
|
76563
77411
|
break;
|
|
76564
77412
|
case "redis": {
|
|
76565
77413
|
console.log(
|
|
76566
|
-
|
|
77414
|
+
import_picocolors30.default.dim(
|
|
76567
77415
|
" Tip: embed credentials directly in the URL \u2014 redis://:password@host:6379\n or enter the URL and password separately below."
|
|
76568
77416
|
)
|
|
76569
77417
|
);
|
|
@@ -76601,8 +77449,8 @@ function missingRequired(type, opts) {
|
|
|
76601
77449
|
}
|
|
76602
77450
|
function ask(label, placeholder = "", required = true) {
|
|
76603
77451
|
return new Promise((resolve8) => {
|
|
76604
|
-
const hint = placeholder ?
|
|
76605
|
-
const suffix = required ? "" :
|
|
77452
|
+
const hint = placeholder ? import_picocolors30.default.dim(` (${placeholder})`) : "";
|
|
77453
|
+
const suffix = required ? "" : import_picocolors30.default.dim(" [optional]");
|
|
76606
77454
|
process.stdout.write(` ${label}${suffix}${hint}: `);
|
|
76607
77455
|
const rl = (0, import_node_readline.createInterface)({
|
|
76608
77456
|
input: process.stdin,
|
|
@@ -76619,7 +77467,7 @@ function askPassword(label) {
|
|
|
76619
77467
|
return new Promise((resolve8) => {
|
|
76620
77468
|
const stdin = process.stdin;
|
|
76621
77469
|
if (typeof stdin.setRawMode === "function") {
|
|
76622
|
-
process.stdout.write(` ${label}${
|
|
77470
|
+
process.stdout.write(` ${label}${import_picocolors30.default.dim(" [optional]")}: `);
|
|
76623
77471
|
stdin.setRawMode(true);
|
|
76624
77472
|
stdin.resume();
|
|
76625
77473
|
let value = "";
|
|
@@ -76719,15 +77567,15 @@ function describeProbe(p) {
|
|
|
76719
77567
|
return `${p.keyCount} keys \xB7 ${p.suggestedRoles.join("/")}${examples ? ` \xB7 ${examples}` : ""}`;
|
|
76720
77568
|
}
|
|
76721
77569
|
async function discoverAndSelectDbs(url, bullmqPrefix) {
|
|
76722
|
-
console.log(
|
|
77570
|
+
console.log(import_picocolors30.default.dim("\n Scanning DBs 0-15 (read-only, sampled)\u2026"));
|
|
76723
77571
|
const probes = await probeRedisDatabases(url, { bullmqPrefix });
|
|
76724
77572
|
const nonEmpty = probes.filter((p) => p.reachable && p.keyCount > 0);
|
|
76725
77573
|
if (nonEmpty.length === 0) {
|
|
76726
|
-
console.log(
|
|
77574
|
+
console.log(import_picocolors30.default.dim(" No populated DBs found."));
|
|
76727
77575
|
return [];
|
|
76728
77576
|
}
|
|
76729
77577
|
for (const p of nonEmpty) {
|
|
76730
|
-
console.log(` ${
|
|
77578
|
+
console.log(` ${import_picocolors30.default.bold(`DB ${p.db}`)} ${import_picocolors30.default.dim("\xB7 " + describeProbe(p))}`);
|
|
76731
77579
|
}
|
|
76732
77580
|
const byLabel = /* @__PURE__ */ new Map();
|
|
76733
77581
|
const choices = nonEmpty.map((p) => {
|
|
@@ -76838,7 +77686,7 @@ function printSummary(type, opts) {
|
|
|
76838
77686
|
} else if (opts.dashboard) {
|
|
76839
77687
|
lines.push(` dashboard: ${opts.dashboard}`);
|
|
76840
77688
|
}
|
|
76841
|
-
if (lines.length > 0) console.log(
|
|
77689
|
+
if (lines.length > 0) console.log(import_picocolors30.default.dim(lines.join("\n")));
|
|
76842
77690
|
}
|
|
76843
77691
|
function isGitTracked(filePath, cwd) {
|
|
76844
77692
|
try {
|
|
@@ -76877,11 +77725,11 @@ async function askIndexSelection(indices) {
|
|
|
76877
77725
|
const shown = indices.slice(0, MAX_DISPLAY);
|
|
76878
77726
|
console.log("\n Available Elasticsearch indexes/data streams:");
|
|
76879
77727
|
shown.forEach((name, i) => {
|
|
76880
|
-
console.log(` ${
|
|
77728
|
+
console.log(` ${import_picocolors30.default.dim(`[${i + 1}]`)} ${name}`);
|
|
76881
77729
|
});
|
|
76882
77730
|
if (indices.length > MAX_DISPLAY) {
|
|
76883
77731
|
console.log(
|
|
76884
|
-
|
|
77732
|
+
import_picocolors30.default.dim(
|
|
76885
77733
|
` \u2026 and ${indices.length - MAX_DISPLAY} more (type a pattern manually to match all)`
|
|
76886
77734
|
)
|
|
76887
77735
|
);
|
|
@@ -76925,11 +77773,11 @@ async function askDashboardSelection(dashboards) {
|
|
|
76925
77773
|
const shown = dashboards.slice(0, MAX_DISPLAY);
|
|
76926
77774
|
console.log("\n Available Grafana dashboards:");
|
|
76927
77775
|
shown.forEach((d, i) => {
|
|
76928
|
-
const folder = d.folderTitle ?
|
|
76929
|
-
console.log(` ${
|
|
77776
|
+
const folder = d.folderTitle ? import_picocolors30.default.dim(` (${d.folderTitle})`) : "";
|
|
77777
|
+
console.log(` ${import_picocolors30.default.dim(`[${i + 1}]`)} ${d.title}${folder}`);
|
|
76930
77778
|
});
|
|
76931
77779
|
if (dashboards.length > MAX_DISPLAY) {
|
|
76932
|
-
console.log(
|
|
77780
|
+
console.log(import_picocolors30.default.dim(` \u2026 and ${dashboards.length - MAX_DISPLAY} more`));
|
|
76933
77781
|
}
|
|
76934
77782
|
const input = (await ask(
|
|
76935
77783
|
` Select dashboards to use (e.g. 1,2 or Enter to type uid manually)`,
|
|
@@ -76980,12 +77828,12 @@ function redactUrl(raw) {
|
|
|
76980
77828
|
// ../../packages/cli/src/commands/stop.ts
|
|
76981
77829
|
init_cjs_shims();
|
|
76982
77830
|
var import_node_child_process7 = require("child_process");
|
|
76983
|
-
var
|
|
77831
|
+
var import_node_fs9 = require("fs");
|
|
76984
77832
|
var import_node_util4 = require("util");
|
|
76985
|
-
var
|
|
76986
|
-
var
|
|
77833
|
+
var import_node_path11 = require("path");
|
|
77834
|
+
var import_picocolors31 = __toESM(require_picocolors(), 1);
|
|
76987
77835
|
var execFileAsync = (0, import_node_util4.promisify)(import_node_child_process7.execFile);
|
|
76988
|
-
var unlinkAsync = (0, import_node_util4.promisify)(
|
|
77836
|
+
var unlinkAsync = (0, import_node_util4.promisify)(import_node_fs9.unlink);
|
|
76989
77837
|
var SPAWNED_HOST_FILE2 = "spawned-host.json";
|
|
76990
77838
|
var START_TIME_TOLERANCE_S = 60;
|
|
76991
77839
|
var STOP_WAIT_MS = 5e3;
|
|
@@ -77000,30 +77848,30 @@ async function runStop(opts) {
|
|
|
77000
77848
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
77001
77849
|
const hostUrl = readSourceHostUrl(root);
|
|
77002
77850
|
if (!hostUrl) {
|
|
77003
|
-
console.log(
|
|
77851
|
+
console.log(import_picocolors31.default.dim("No source-intelligence host found for this repo (.horus/source/host.json absent)."));
|
|
77004
77852
|
return 0;
|
|
77005
77853
|
}
|
|
77006
77854
|
return await stopHost(root, hostUrl);
|
|
77007
77855
|
} catch (err) {
|
|
77008
|
-
console.error(
|
|
77856
|
+
console.error(import_picocolors31.default.red(err.message));
|
|
77009
77857
|
return 1;
|
|
77010
77858
|
}
|
|
77011
77859
|
}
|
|
77012
77860
|
async function stopHost(root, hostUrl) {
|
|
77013
77861
|
const alive = await isHostHealthy(hostUrl);
|
|
77014
77862
|
if (!alive) {
|
|
77015
|
-
console.log(
|
|
77863
|
+
console.log(import_picocolors31.default.dim(`Host ${hostUrl} is already stopped.`));
|
|
77016
77864
|
return 0;
|
|
77017
77865
|
}
|
|
77018
77866
|
const port = extractPort(hostUrl);
|
|
77019
77867
|
if (port === null) {
|
|
77020
|
-
console.error(
|
|
77868
|
+
console.error(import_picocolors31.default.red(`Cannot determine port from host URL: ${hostUrl}`));
|
|
77021
77869
|
return 1;
|
|
77022
77870
|
}
|
|
77023
77871
|
const spawned = readSpawnedHost(root);
|
|
77024
77872
|
if (spawned === null) {
|
|
77025
77873
|
console.error(
|
|
77026
|
-
|
|
77874
|
+
import_picocolors31.default.red(
|
|
77027
77875
|
`No ownership record found (.horus/${SPAWNED_HOST_FILE2} absent). Horus will not stop a host it did not spawn.`
|
|
77028
77876
|
)
|
|
77029
77877
|
);
|
|
@@ -77031,12 +77879,12 @@ async function stopHost(root, hostUrl) {
|
|
|
77031
77879
|
}
|
|
77032
77880
|
const recordError = validateSpawnedRecord(spawned);
|
|
77033
77881
|
if (recordError !== null) {
|
|
77034
|
-
console.error(
|
|
77882
|
+
console.error(import_picocolors31.default.red(`Ownership record is malformed: ${recordError}. Aborting for safety.`));
|
|
77035
77883
|
return 1;
|
|
77036
77884
|
}
|
|
77037
77885
|
if (spawned.port !== port) {
|
|
77038
77886
|
console.error(
|
|
77039
|
-
|
|
77887
|
+
import_picocolors31.default.red(
|
|
77040
77888
|
`Ownership record port (${spawned.port}) does not match host URL port (${port}). Record may be stale.`
|
|
77041
77889
|
)
|
|
77042
77890
|
);
|
|
@@ -77044,7 +77892,7 @@ async function stopHost(root, hostUrl) {
|
|
|
77044
77892
|
}
|
|
77045
77893
|
if (spawned.root !== root) {
|
|
77046
77894
|
console.error(
|
|
77047
|
-
|
|
77895
|
+
import_picocolors31.default.red(
|
|
77048
77896
|
`Ownership record root (${spawned.root}) does not match resolved root (${root}). Record may be stale.`
|
|
77049
77897
|
)
|
|
77050
77898
|
);
|
|
@@ -77052,9 +77900,9 @@ async function stopHost(root, hostUrl) {
|
|
|
77052
77900
|
}
|
|
77053
77901
|
const info = await getProcessInfo(spawned.pid);
|
|
77054
77902
|
if (info === null) {
|
|
77055
|
-
console.log(
|
|
77903
|
+
console.log(import_picocolors31.default.dim(`Process pid ${spawned.pid} is no longer running \u2014 already stopped.`));
|
|
77056
77904
|
try {
|
|
77057
|
-
await unlinkAsync((0,
|
|
77905
|
+
await unlinkAsync((0, import_node_path11.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
|
|
77058
77906
|
} catch {
|
|
77059
77907
|
}
|
|
77060
77908
|
return 0;
|
|
@@ -77065,7 +77913,7 @@ async function stopHost(root, hostUrl) {
|
|
|
77065
77913
|
);
|
|
77066
77914
|
if (!hostPortRe.test(info.args)) {
|
|
77067
77915
|
console.error(
|
|
77068
|
-
|
|
77916
|
+
import_picocolors31.default.red(
|
|
77069
77917
|
`Pid ${spawned.pid} args do not match "horus-source host --port ${portStr}". Got: "${info.args.slice(0, 120)}". Aborting for safety.`
|
|
77070
77918
|
)
|
|
77071
77919
|
);
|
|
@@ -77074,12 +77922,12 @@ async function stopHost(root, hostUrl) {
|
|
|
77074
77922
|
const startTs = new Date(spawned.startedAt).getTime();
|
|
77075
77923
|
const recordedAgeS = Math.round((Date.now() - startTs) / 1e3);
|
|
77076
77924
|
if (!Number.isFinite(info.etimeSeconds)) {
|
|
77077
|
-
console.error(
|
|
77925
|
+
console.error(import_picocolors31.default.red(`Could not read elapsed time for pid ${spawned.pid}. Aborting for safety.`));
|
|
77078
77926
|
return 1;
|
|
77079
77927
|
}
|
|
77080
77928
|
if (Math.abs(info.etimeSeconds - recordedAgeS) > START_TIME_TOLERANCE_S) {
|
|
77081
77929
|
console.error(
|
|
77082
|
-
|
|
77930
|
+
import_picocolors31.default.red(
|
|
77083
77931
|
`Pid ${spawned.pid} age mismatch: record says ~${recordedAgeS}s, process reports ${info.etimeSeconds}s elapsed. Possible PID reuse \u2014 aborting for safety.`
|
|
77084
77932
|
)
|
|
77085
77933
|
);
|
|
@@ -77091,9 +77939,9 @@ async function stopHost(root, hostUrl) {
|
|
|
77091
77939
|
signaled = true;
|
|
77092
77940
|
} catch (err) {
|
|
77093
77941
|
if (err.code === "ESRCH") {
|
|
77094
|
-
console.log(
|
|
77942
|
+
console.log(import_picocolors31.default.dim(`Process pid ${spawned.pid} exited before signal \u2014 already stopped.`));
|
|
77095
77943
|
} else {
|
|
77096
|
-
console.error(
|
|
77944
|
+
console.error(import_picocolors31.default.red(`Failed to signal pid ${spawned.pid}: ${err.message}`));
|
|
77097
77945
|
return 1;
|
|
77098
77946
|
}
|
|
77099
77947
|
}
|
|
@@ -77109,18 +77957,18 @@ async function stopHost(root, hostUrl) {
|
|
|
77109
77957
|
}
|
|
77110
77958
|
if (!exited) {
|
|
77111
77959
|
console.error(
|
|
77112
|
-
|
|
77960
|
+
import_picocolors31.default.red(
|
|
77113
77961
|
`Host pid ${spawned.pid} did not exit within ${STOP_WAIT_MS / 1e3}s after SIGTERM.`
|
|
77114
77962
|
)
|
|
77115
77963
|
);
|
|
77116
77964
|
return 1;
|
|
77117
77965
|
}
|
|
77118
77966
|
console.log(
|
|
77119
|
-
`${
|
|
77967
|
+
`${import_picocolors31.default.green("\u2713")} Stopped source-intelligence host ` + import_picocolors31.default.dim(`(pid ${spawned.pid}, port ${port})`) + ` for ${root}`
|
|
77120
77968
|
);
|
|
77121
77969
|
}
|
|
77122
77970
|
try {
|
|
77123
|
-
await unlinkAsync((0,
|
|
77971
|
+
await unlinkAsync((0, import_node_path11.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
|
|
77124
77972
|
} catch {
|
|
77125
77973
|
}
|
|
77126
77974
|
return 0;
|
|
@@ -77129,7 +77977,7 @@ async function stopAll() {
|
|
|
77129
77977
|
const registry = readRegistry();
|
|
77130
77978
|
const projects2 = Object.entries(registry.projects);
|
|
77131
77979
|
if (projects2.length === 0) {
|
|
77132
|
-
console.log(
|
|
77980
|
+
console.log(import_picocolors31.default.dim("No registered projects."));
|
|
77133
77981
|
return 0;
|
|
77134
77982
|
}
|
|
77135
77983
|
let stopped = 0;
|
|
@@ -77139,17 +77987,17 @@ async function stopAll() {
|
|
|
77139
77987
|
if (!hostUrl) continue;
|
|
77140
77988
|
const alive = await isHostHealthy(hostUrl);
|
|
77141
77989
|
if (!alive) continue;
|
|
77142
|
-
console.log(` Stopping ${
|
|
77990
|
+
console.log(` Stopping ${import_picocolors31.default.bold(name)} ${import_picocolors31.default.dim(`(${hostUrl})`)}`);
|
|
77143
77991
|
const code = await stopHost(entry2.root, hostUrl);
|
|
77144
77992
|
if (code === 0) stopped++;
|
|
77145
77993
|
else failed++;
|
|
77146
77994
|
}
|
|
77147
77995
|
if (stopped === 0 && failed === 0) {
|
|
77148
|
-
console.log(
|
|
77996
|
+
console.log(import_picocolors31.default.dim("No running source-intelligence hosts found."));
|
|
77149
77997
|
} else {
|
|
77150
77998
|
console.log(
|
|
77151
77999
|
`
|
|
77152
|
-
Stopped ${stopped} host(s)${failed > 0 ?
|
|
78000
|
+
Stopped ${stopped} host(s)${failed > 0 ? import_picocolors31.default.red(`, ${failed} failed`) : ""}.`
|
|
77153
78001
|
);
|
|
77154
78002
|
}
|
|
77155
78003
|
return failed > 0 ? 1 : 0;
|
|
@@ -77201,12 +78049,12 @@ function extractPort(hostUrl) {
|
|
|
77201
78049
|
|
|
77202
78050
|
// ../../packages/cli/src/commands/hosts.ts
|
|
77203
78051
|
init_cjs_shims();
|
|
77204
|
-
var
|
|
78052
|
+
var import_picocolors32 = __toESM(require_picocolors(), 1);
|
|
77205
78053
|
async function runHosts() {
|
|
77206
78054
|
const registry = readRegistry();
|
|
77207
78055
|
const projects2 = Object.entries(registry.projects);
|
|
77208
78056
|
if (projects2.length === 0) {
|
|
77209
|
-
console.log(
|
|
78057
|
+
console.log(import_picocolors32.default.dim("No registered projects. Run `horus index` in a repo first."));
|
|
77210
78058
|
return 0;
|
|
77211
78059
|
}
|
|
77212
78060
|
const rows = [];
|
|
@@ -77223,29 +78071,29 @@ async function runHosts() {
|
|
|
77223
78071
|
});
|
|
77224
78072
|
const anyHost = rows.some((r) => r.hostUrl !== null);
|
|
77225
78073
|
if (!anyHost) {
|
|
77226
|
-
console.log(
|
|
78074
|
+
console.log(import_picocolors32.default.dim("No source-intelligence hosts found. Run `horus index` to start one."));
|
|
77227
78075
|
return 0;
|
|
77228
78076
|
}
|
|
77229
78077
|
console.log("");
|
|
77230
78078
|
for (const row of rows) {
|
|
77231
78079
|
if (row.hostUrl === null) continue;
|
|
77232
|
-
const status = row.healthy ?
|
|
78080
|
+
const status = row.healthy ? import_picocolors32.default.green("\u25CF running") : import_picocolors32.default.red("\u25CF stopped");
|
|
77233
78081
|
const port = extractPort2(row.hostUrl) ?? "?";
|
|
77234
78082
|
console.log(
|
|
77235
|
-
` ${status} ${
|
|
78083
|
+
` ${status} ${import_picocolors32.default.bold(row.name.padEnd(24))} port ${String(port).padEnd(6)} ${import_picocolors32.default.dim(row.root)}`
|
|
77236
78084
|
);
|
|
77237
78085
|
}
|
|
77238
78086
|
console.log("");
|
|
77239
78087
|
const noHost = rows.filter((r) => r.hostUrl === null);
|
|
77240
78088
|
if (noHost.length > 0) {
|
|
77241
78089
|
for (const row of noHost) {
|
|
77242
|
-
console.log(` ${
|
|
78090
|
+
console.log(` ${import_picocolors32.default.dim("\u25CB no host")} ${import_picocolors32.default.dim(row.name)}`);
|
|
77243
78091
|
}
|
|
77244
78092
|
console.log("");
|
|
77245
78093
|
}
|
|
77246
78094
|
const running = rows.filter((r) => r.healthy).length;
|
|
77247
78095
|
console.log(
|
|
77248
|
-
|
|
78096
|
+
import_picocolors32.default.dim(
|
|
77249
78097
|
`${running} running \xB7 horus stop to reap \xB7 horus stop --all to stop everything`
|
|
77250
78098
|
)
|
|
77251
78099
|
);
|
|
@@ -77262,12 +78110,12 @@ function extractPort2(hostUrl) {
|
|
|
77262
78110
|
|
|
77263
78111
|
// ../../packages/cli/src/commands/doctor.ts
|
|
77264
78112
|
init_cjs_shims();
|
|
77265
|
-
var
|
|
78113
|
+
var import_picocolors33 = __toESM(require_picocolors(), 1);
|
|
77266
78114
|
var DEFAULT_DB_URL4 = "postgresql://horus:horus@localhost:5433/horus";
|
|
77267
78115
|
function mark2(status) {
|
|
77268
|
-
if (status === "pass") return
|
|
77269
|
-
if (status === "warn") return
|
|
77270
|
-
return
|
|
78116
|
+
if (status === "pass") return import_picocolors33.default.green("\u2713");
|
|
78117
|
+
if (status === "warn") return import_picocolors33.default.yellow("~");
|
|
78118
|
+
return import_picocolors33.default.red("\u2717");
|
|
77271
78119
|
}
|
|
77272
78120
|
async function runDoctor(opts) {
|
|
77273
78121
|
const cwd = opts?.cwd ?? process.cwd();
|
|
@@ -77494,11 +78342,11 @@ async function runDoctor(opts) {
|
|
|
77494
78342
|
write(JSON.stringify(output, null, 2));
|
|
77495
78343
|
return hasFailure ? 1 : 0;
|
|
77496
78344
|
}
|
|
77497
|
-
write(
|
|
78345
|
+
write(import_picocolors33.default.bold("\nHorus readiness check\n"));
|
|
77498
78346
|
for (const check of checks) {
|
|
77499
|
-
write(` ${mark2(check.status)} ${
|
|
78347
|
+
write(` ${mark2(check.status)} ${import_picocolors33.default.bold(check.label.padEnd(26))} ${import_picocolors33.default.dim(check.detail)}`);
|
|
77500
78348
|
if (check.next) {
|
|
77501
|
-
write(` ${
|
|
78349
|
+
write(` ${import_picocolors33.default.dim("\u2192 " + check.next)}`);
|
|
77502
78350
|
}
|
|
77503
78351
|
}
|
|
77504
78352
|
write("");
|
|
@@ -77508,16 +78356,16 @@ async function runDoctor(opts) {
|
|
|
77508
78356
|
// ../../packages/cli/src/commands/providers-doctor.ts
|
|
77509
78357
|
init_cjs_shims();
|
|
77510
78358
|
var import_node_child_process8 = require("child_process");
|
|
77511
|
-
var
|
|
78359
|
+
var import_picocolors34 = __toESM(require_picocolors(), 1);
|
|
77512
78360
|
function statusMark(status) {
|
|
77513
|
-
if (status === "ready") return
|
|
77514
|
-
if (status === "installed") return
|
|
77515
|
-
return
|
|
78361
|
+
if (status === "ready") return import_picocolors34.default.green("\u2713");
|
|
78362
|
+
if (status === "installed") return import_picocolors34.default.yellow("~");
|
|
78363
|
+
return import_picocolors34.default.red("\u2717");
|
|
77516
78364
|
}
|
|
77517
78365
|
function statusLabel(status) {
|
|
77518
|
-
if (status === "ready") return
|
|
77519
|
-
if (status === "installed") return
|
|
77520
|
-
return
|
|
78366
|
+
if (status === "ready") return import_picocolors34.default.green("ready");
|
|
78367
|
+
if (status === "installed") return import_picocolors34.default.yellow("installed (not configured)");
|
|
78368
|
+
return import_picocolors34.default.dim("not found on PATH");
|
|
77521
78369
|
}
|
|
77522
78370
|
function detectBinary(id) {
|
|
77523
78371
|
const result = (0, import_node_child_process8.spawnSync)(id, ["--version"], { stdio: "pipe", timeout: 2e3 });
|
|
@@ -77534,7 +78382,7 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
77534
78382
|
const registry = opts?.registry ?? DEFAULT_LOCAL_PROVIDER_REGISTRY;
|
|
77535
78383
|
const write = opts?.write ?? ((line2) => console.log(line2));
|
|
77536
78384
|
const results = buildProviderResults(registry, opts?._detect);
|
|
77537
|
-
write(
|
|
78385
|
+
write(import_picocolors34.default.bold("\nLocal AI providers\n"));
|
|
77538
78386
|
for (const result of results) {
|
|
77539
78387
|
const descriptor = registry.get(result.id);
|
|
77540
78388
|
const name = descriptor?.displayName ?? result.id;
|
|
@@ -77542,11 +78390,11 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
77542
78390
|
` ${statusMark(result.status)} ${result.id.padEnd(8)} ${name.padEnd(22)} ${statusLabel(result.status)}`
|
|
77543
78391
|
);
|
|
77544
78392
|
if (result.status !== "ready" && result.detail) {
|
|
77545
|
-
write(` ${
|
|
78393
|
+
write(` ${import_picocolors34.default.dim("\u2192 " + result.detail)}`);
|
|
77546
78394
|
}
|
|
77547
78395
|
}
|
|
77548
78396
|
write("");
|
|
77549
|
-
write(
|
|
78397
|
+
write(import_picocolors34.default.bold("Cloud AI providers\n"));
|
|
77550
78398
|
let source = null;
|
|
77551
78399
|
if (opts?._anthropicKey !== void 0) {
|
|
77552
78400
|
source = opts._anthropicKey ? "env" : null;
|
|
@@ -77561,18 +78409,18 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
77561
78409
|
}
|
|
77562
78410
|
if (source === "config") {
|
|
77563
78411
|
write(
|
|
77564
|
-
` ${
|
|
78412
|
+
` ${import_picocolors34.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors34.default.green("configured (local .horus config)")}`
|
|
77565
78413
|
);
|
|
77566
78414
|
} else if (source === "env") {
|
|
77567
78415
|
write(
|
|
77568
|
-
` ${
|
|
78416
|
+
` ${import_picocolors34.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors34.default.green("configured (ANTHROPIC_API_KEY env)")}`
|
|
77569
78417
|
);
|
|
77570
78418
|
} else {
|
|
77571
78419
|
write(
|
|
77572
|
-
` ${
|
|
78420
|
+
` ${import_picocolors34.default.red("\u2717")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors34.default.dim("not configured")}`
|
|
77573
78421
|
);
|
|
77574
78422
|
write(
|
|
77575
|
-
` ${
|
|
78423
|
+
` ${import_picocolors34.default.dim("\u2192 run `horus connect ai` (or set ANTHROPIC_API_KEY) to enable `horus investigate --ai`")}`
|
|
77576
78424
|
);
|
|
77577
78425
|
}
|
|
77578
78426
|
write("");
|
|
@@ -77581,9 +78429,9 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
77581
78429
|
|
|
77582
78430
|
// ../../packages/cli/src/commands/generate-config.ts
|
|
77583
78431
|
init_cjs_shims();
|
|
77584
|
-
var
|
|
77585
|
-
var
|
|
77586
|
-
var
|
|
78432
|
+
var import_node_fs10 = require("fs");
|
|
78433
|
+
var import_node_path12 = require("path");
|
|
78434
|
+
var import_picocolors35 = __toESM(require_picocolors(), 1);
|
|
77587
78435
|
function configTemplate(name, repoPath) {
|
|
77588
78436
|
return `export default {
|
|
77589
78437
|
database: {
|
|
@@ -77631,39 +78479,39 @@ async function runGenerateConfig(opts) {
|
|
|
77631
78479
|
const defaults = localConfigPath2 != null ? projectDefaults(localConfigPath2) : null;
|
|
77632
78480
|
const hasLocalConfig = defaults != null;
|
|
77633
78481
|
const defaultOut = hasLocalConfig ? "horus.config.example.js" : "horus.config.js";
|
|
77634
|
-
const outPath = (0,
|
|
78482
|
+
const outPath = (0, import_node_path12.resolve)(cwd, opts.out ?? defaultOut);
|
|
77635
78483
|
const name = opts.name ?? defaults?.name ?? "my-project";
|
|
77636
78484
|
const repoPath = opts.repo ?? defaults?.repoPath ?? `/path/to/${name}`;
|
|
77637
|
-
if ((0,
|
|
77638
|
-
log(`${
|
|
77639
|
-
log(
|
|
78485
|
+
if ((0, import_node_fs10.existsSync)(outPath) && !opts.force) {
|
|
78486
|
+
log(`${import_picocolors35.default.red("\u2717")} ${outPath} already exists`);
|
|
78487
|
+
log(import_picocolors35.default.dim(" pass --force to overwrite"));
|
|
77640
78488
|
return 1;
|
|
77641
78489
|
}
|
|
77642
78490
|
try {
|
|
77643
|
-
(0,
|
|
77644
|
-
(0,
|
|
78491
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path12.dirname)(outPath), { recursive: true });
|
|
78492
|
+
(0, import_node_fs10.writeFileSync)(outPath, configTemplate(name, repoPath), "utf8");
|
|
77645
78493
|
} catch (err) {
|
|
77646
|
-
log(`${
|
|
78494
|
+
log(`${import_picocolors35.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
|
|
77647
78495
|
return 1;
|
|
77648
78496
|
}
|
|
77649
|
-
log(`${
|
|
77650
|
-
log(
|
|
77651
|
-
log(
|
|
78497
|
+
log(`${import_picocolors35.default.green("\u2713")} Created ${outPath}`);
|
|
78498
|
+
log(import_picocolors35.default.dim(` project: ${name}`));
|
|
78499
|
+
log(import_picocolors35.default.dim(` repo: ${repoPath}`));
|
|
77652
78500
|
if (hasLocalConfig && localConfigPath2 != null) {
|
|
77653
78501
|
log("");
|
|
77654
|
-
log(
|
|
78502
|
+
log(import_picocolors35.default.yellow("Note:") + ` an initialized Horus project config exists at ${localConfigPath2}`);
|
|
77655
78503
|
log(" \u2022 .horus/config.json \u2014 project config used by `horus investigate` from this repo");
|
|
77656
78504
|
log(" \u2022 horus.config.js \u2014 standalone/global config used with `horus doctor --config <path>`");
|
|
77657
|
-
log(
|
|
78505
|
+
log(import_picocolors35.default.dim(` next: review ${outPath} and copy/adapt it as needed`));
|
|
77658
78506
|
} else {
|
|
77659
|
-
log(
|
|
78507
|
+
log(import_picocolors35.default.dim(` next: horus doctor --config ${outPath}`));
|
|
77660
78508
|
}
|
|
77661
78509
|
return 0;
|
|
77662
78510
|
}
|
|
77663
78511
|
|
|
77664
78512
|
// ../../packages/cli/src/commands/readiness.ts
|
|
77665
78513
|
init_cjs_shims();
|
|
77666
|
-
var
|
|
78514
|
+
var import_picocolors36 = __toESM(require_picocolors(), 1);
|
|
77667
78515
|
var READINESS_AI_CONTRACT = `Provide a clearly separated AI readiness summary with:
|
|
77668
78516
|
|
|
77669
78517
|
Overall assessment
|
|
@@ -77686,9 +78534,9 @@ Confidence / gaps
|
|
|
77686
78534
|
- Which checks Horus could verify vs. which require manual confirmation`;
|
|
77687
78535
|
var DEFAULT_DB_URL5 = "postgresql://horus:horus@localhost:5433/horus";
|
|
77688
78536
|
function mark3(status) {
|
|
77689
|
-
if (status === "pass") return
|
|
77690
|
-
if (status === "warn") return
|
|
77691
|
-
return
|
|
78537
|
+
if (status === "pass") return import_picocolors36.default.green("\u2713");
|
|
78538
|
+
if (status === "warn") return import_picocolors36.default.yellow("~");
|
|
78539
|
+
return import_picocolors36.default.red("\u2717");
|
|
77692
78540
|
}
|
|
77693
78541
|
async function runReadiness(opts) {
|
|
77694
78542
|
const cwd = opts?.cwd ?? process.cwd();
|
|
@@ -77843,20 +78691,20 @@ async function runReadiness(opts) {
|
|
|
77843
78691
|
}
|
|
77844
78692
|
const blockingChecks = checks.filter((c) => c.blocking);
|
|
77845
78693
|
const optionalChecks = checks.filter((c) => !c.blocking);
|
|
77846
|
-
write(
|
|
77847
|
-
write(
|
|
78694
|
+
write(import_picocolors36.default.bold("\nHorus release readiness\n"));
|
|
78695
|
+
write(import_picocolors36.default.bold(" Blocking"));
|
|
77848
78696
|
for (const check of blockingChecks) {
|
|
77849
|
-
write(` ${mark3(check.status)} ${
|
|
78697
|
+
write(` ${mark3(check.status)} ${import_picocolors36.default.bold(check.label.padEnd(22))} ${import_picocolors36.default.dim(check.detail)}`);
|
|
77850
78698
|
if (check.next) {
|
|
77851
|
-
write(` ${
|
|
78699
|
+
write(` ${import_picocolors36.default.dim("\u2192 " + check.next)}`);
|
|
77852
78700
|
}
|
|
77853
78701
|
}
|
|
77854
78702
|
write("");
|
|
77855
|
-
write(
|
|
78703
|
+
write(import_picocolors36.default.bold(" Optional"));
|
|
77856
78704
|
for (const check of optionalChecks) {
|
|
77857
|
-
write(` ${mark3(check.status)} ${
|
|
78705
|
+
write(` ${mark3(check.status)} ${import_picocolors36.default.bold(check.label.padEnd(22))} ${import_picocolors36.default.dim(check.detail)}`);
|
|
77858
78706
|
if (check.next) {
|
|
77859
|
-
write(` ${
|
|
78707
|
+
write(` ${import_picocolors36.default.dim("\u2192 " + check.next)}`);
|
|
77860
78708
|
}
|
|
77861
78709
|
}
|
|
77862
78710
|
write("");
|
|
@@ -77864,21 +78712,21 @@ async function runReadiness(opts) {
|
|
|
77864
78712
|
const optionalWarns = optionalChecks.filter((c) => c.status === "warn").length;
|
|
77865
78713
|
if (blockingFails.length === 0) {
|
|
77866
78714
|
if (optionalWarns === 0) {
|
|
77867
|
-
write(
|
|
78715
|
+
write(import_picocolors36.default.green(" Ready for demo/release."));
|
|
77868
78716
|
} else {
|
|
77869
78717
|
write(
|
|
77870
|
-
|
|
78718
|
+
import_picocolors36.default.yellow(
|
|
77871
78719
|
` Ready for a basic demo. ${optionalWarns} optional item(s) not configured \u2014 investigation evidence will be limited.`
|
|
77872
78720
|
)
|
|
77873
78721
|
);
|
|
77874
78722
|
}
|
|
77875
78723
|
} else {
|
|
77876
78724
|
write(
|
|
77877
|
-
|
|
78725
|
+
import_picocolors36.default.red(
|
|
77878
78726
|
` Not ready. ${blockingFails.length} blocking item(s) must be resolved before demo/release.`
|
|
77879
78727
|
)
|
|
77880
78728
|
);
|
|
77881
|
-
write(
|
|
78729
|
+
write(import_picocolors36.default.dim(" Re-run `horus readiness` after resolving the items above."));
|
|
77882
78730
|
}
|
|
77883
78731
|
write("");
|
|
77884
78732
|
if (opts?.ai) {
|
|
@@ -77893,7 +78741,7 @@ async function runReadiness(opts) {
|
|
|
77893
78741
|
});
|
|
77894
78742
|
console.log("\n" + renderInterpretation(result));
|
|
77895
78743
|
if (!result.ok) {
|
|
77896
|
-
console.error(
|
|
78744
|
+
console.error(import_picocolors36.default.yellow(`[ai] ${result.warning}`));
|
|
77897
78745
|
}
|
|
77898
78746
|
}
|
|
77899
78747
|
return blockingFails.length > 0 ? 1 : 0;
|
|
@@ -77901,46 +78749,79 @@ async function runReadiness(opts) {
|
|
|
77901
78749
|
|
|
77902
78750
|
// ../../packages/cli/src/commands/skill.ts
|
|
77903
78751
|
init_cjs_shims();
|
|
77904
|
-
var
|
|
77905
|
-
var
|
|
77906
|
-
var
|
|
77907
|
-
var
|
|
78752
|
+
var import_node_fs11 = require("fs");
|
|
78753
|
+
var import_node_path13 = require("path");
|
|
78754
|
+
var import_node_os3 = require("os");
|
|
78755
|
+
var import_promises = require("readline/promises");
|
|
78756
|
+
var import_picocolors37 = __toESM(require_picocolors(), 1);
|
|
77908
78757
|
var SUPPORTED_TARGETS = ["claude", "codex", "gemini", "cursor", "generic"];
|
|
77909
78758
|
function isValidTarget(target) {
|
|
77910
78759
|
return SUPPORTED_TARGETS.includes(target);
|
|
77911
78760
|
}
|
|
78761
|
+
function getLegacyClaudeSkillPath(opts) {
|
|
78762
|
+
const base2 = opts.global ? (0, import_node_os3.homedir)() : opts.cwd ?? process.cwd();
|
|
78763
|
+
return (0, import_node_path13.join)(base2, ".claude", "skills", "horus.md");
|
|
78764
|
+
}
|
|
77912
78765
|
function getSkillInstallPath(target, opts) {
|
|
77913
|
-
const base2 = opts.global ? (0,
|
|
77914
|
-
|
|
77915
|
-
|
|
78766
|
+
const base2 = opts.global ? (0, import_node_os3.homedir)() : opts.cwd ?? process.cwd();
|
|
78767
|
+
switch (target) {
|
|
78768
|
+
case "claude":
|
|
78769
|
+
return (0, import_node_path13.join)(base2, ".claude", "skills", "horus", "SKILL.md");
|
|
78770
|
+
case "gemini":
|
|
78771
|
+
return opts.global ? (0, import_node_path13.join)(base2, ".gemini", "GEMINI.md") : (0, import_node_path13.join)(base2, "GEMINI.md");
|
|
78772
|
+
case "cursor":
|
|
78773
|
+
return opts.global ? (0, import_node_path13.join)(base2, ".cursorrules") : (0, import_node_path13.join)(base2, ".cursor", "rules", "horus.mdc");
|
|
78774
|
+
case "codex":
|
|
78775
|
+
return opts.global ? (0, import_node_path13.join)(base2, ".codex", "AGENTS.md") : (0, import_node_path13.join)(base2, "AGENTS.md");
|
|
78776
|
+
case "generic":
|
|
78777
|
+
default:
|
|
78778
|
+
return (0, import_node_path13.join)(base2, ".horus", "skills", `horus-${target}.md`);
|
|
77916
78779
|
}
|
|
77917
|
-
return (0, import_node_path11.join)(base2, ".horus", "skills", `horus-${target}.md`);
|
|
77918
78780
|
}
|
|
77919
|
-
var BASE_SKILL = `# Horus
|
|
78781
|
+
var BASE_SKILL = `# Horus Grounded Investigation Skill
|
|
78782
|
+
|
|
78783
|
+
When working in a repo with Horus configured, use Horus before guessing about production behavior, runtime state, ownership, recent changes, or codebase logic.
|
|
77920
78784
|
|
|
77921
|
-
|
|
78785
|
+
Horus is a read-only evidence layer. It helps collect and connect source, runtime, metrics, queue, log, ownership, and change evidence. It does not mutate production systems.
|
|
77922
78786
|
|
|
77923
78787
|
## When to use Horus
|
|
77924
78788
|
|
|
77925
78789
|
Use Horus when the user asks:
|
|
77926
|
-
- why something is broken, slow, or failing
|
|
77927
|
-
- about a production incident or outage
|
|
77928
|
-
- what changed recently
|
|
77929
|
-
- who owns a component or service
|
|
77930
|
-
- about blast radius or impact of a change
|
|
77931
|
-
- about queues, workers, Redis/BullMQ, logs, metrics, MongoDB state, or source paths
|
|
77932
|
-
- to verify a fix or check for regressions after a change
|
|
77933
78790
|
|
|
77934
|
-
|
|
78791
|
+
* why something is broken, slow, failing, flaky, or inconsistent
|
|
78792
|
+
* about a production incident, outage, regression, or degraded service
|
|
78793
|
+
* what changed recently
|
|
78794
|
+
* what caused a behavior change
|
|
78795
|
+
* who owns a component, service, queue, worker, route, or module
|
|
78796
|
+
* about blast radius or impact of a change
|
|
78797
|
+
* about queues, workers, Redis, BullMQ, NATS, logs, metrics, MongoDB state, or source paths
|
|
78798
|
+
* to verify a fix or check for regressions after a change
|
|
78799
|
+
* whether a feature behaves a certain way
|
|
78800
|
+
* where business logic lives
|
|
78801
|
+
* whether a condition, flag, enum, status, or branch is handled
|
|
78802
|
+
* whether code paths are connected or only coincidentally similar
|
|
78803
|
+
|
|
78804
|
+
## Default first steps
|
|
78805
|
+
|
|
78806
|
+
Start small. Do not shotgun every command.
|
|
77935
78807
|
|
|
77936
78808
|
\`\`\`bash
|
|
77937
78809
|
horus status
|
|
77938
|
-
horus
|
|
77939
|
-
|
|
78810
|
+
horus investigate "<incident, behavior, or debugging hint>"
|
|
78811
|
+
\`\`\`
|
|
78812
|
+
|
|
78813
|
+
If Horus returns a saved investigation ID, use that ID for follow-up reasoning:
|
|
78814
|
+
|
|
78815
|
+
\`\`\`bash
|
|
78816
|
+
horus ask <id> "what evidence is missing?"
|
|
78817
|
+
horus ask <id> "what evidence contradicts the likely root cause?"
|
|
78818
|
+
horus score <id>
|
|
77940
78819
|
\`\`\`
|
|
77941
78820
|
|
|
77942
78821
|
## Runtime evidence
|
|
77943
78822
|
|
|
78823
|
+
Use runtime commands when the question depends on live or recent runtime behavior:
|
|
78824
|
+
|
|
77944
78825
|
\`\`\`bash
|
|
77945
78826
|
horus logs [service]
|
|
77946
78827
|
horus state
|
|
@@ -77948,9 +78829,21 @@ horus metrics "<hint>"
|
|
|
77948
78829
|
horus queues --live
|
|
77949
78830
|
\`\`\`
|
|
77950
78831
|
|
|
78832
|
+
Examples:
|
|
78833
|
+
|
|
78834
|
+
\`\`\`bash
|
|
78835
|
+
horus logs api
|
|
78836
|
+
horus metrics "checkout latency"
|
|
78837
|
+
horus queues --live
|
|
78838
|
+
horus state
|
|
78839
|
+
\`\`\`
|
|
78840
|
+
|
|
77951
78841
|
## Source reasoning
|
|
77952
78842
|
|
|
78843
|
+
Use source commands when the question depends on code behavior, ownership, blast radius, or recent code changes:
|
|
78844
|
+
|
|
77953
78845
|
\`\`\`bash
|
|
78846
|
+
horus explain <symbol-or-hint>
|
|
77954
78847
|
horus blast-radius <symbol>
|
|
77955
78848
|
horus owner <symbol>
|
|
77956
78849
|
horus changes <base> [compare]
|
|
@@ -77958,9 +78851,61 @@ horus timeline
|
|
|
77958
78851
|
horus what-changed
|
|
77959
78852
|
\`\`\`
|
|
77960
78853
|
|
|
78854
|
+
Examples:
|
|
78855
|
+
|
|
78856
|
+
\`\`\`bash
|
|
78857
|
+
horus explain "brandType MANUAL product auto draft"
|
|
78858
|
+
horus blast-radius ProductDraftService
|
|
78859
|
+
horus owner ProductDraftService
|
|
78860
|
+
horus what-changed
|
|
78861
|
+
\`\`\`
|
|
78862
|
+
|
|
78863
|
+
## Logic and behavior questions
|
|
78864
|
+
|
|
78865
|
+
For questions like:
|
|
78866
|
+
|
|
78867
|
+
* "Do we auto draft products for brands where brandType===MANUAL?"
|
|
78868
|
+
* "Where is this status handled?"
|
|
78869
|
+
* "Does this worker retry failed jobs?"
|
|
78870
|
+
* "What happens when this integration sync fails?"
|
|
78871
|
+
* "Is this queue idempotent?"
|
|
78872
|
+
* "Does this change affect Shopify imports?"
|
|
78873
|
+
|
|
78874
|
+
Prefer:
|
|
78875
|
+
|
|
78876
|
+
\`\`\`bash
|
|
78877
|
+
horus explain "<symbol-or-behavior-hint>"
|
|
78878
|
+
horus investigate "<behavior question>"
|
|
78879
|
+
\`\`\`
|
|
78880
|
+
|
|
78881
|
+
Then ask follow-ups against the saved investigation:
|
|
78882
|
+
|
|
78883
|
+
\`\`\`bash
|
|
78884
|
+
horus ask <id> "what source evidence proves this behavior?"
|
|
78885
|
+
horus ask <id> "what source evidence disproves this behavior?"
|
|
78886
|
+
horus ask <id> "what evidence is missing?"
|
|
78887
|
+
\`\`\`
|
|
78888
|
+
|
|
78889
|
+
Correct \`horus ask\` usage:
|
|
78890
|
+
|
|
78891
|
+
\`\`\`bash
|
|
78892
|
+
horus ask <id> "do we auto draft products for brands where brandType===MANUAL?"
|
|
78893
|
+
\`\`\`
|
|
78894
|
+
|
|
78895
|
+
Incorrect usage:
|
|
78896
|
+
|
|
78897
|
+
\`\`\`bash
|
|
78898
|
+
horus ask "do we auto draft products for brands where brandType===MANUAL?"
|
|
78899
|
+
\`\`\`
|
|
78900
|
+
|
|
78901
|
+
\`horus ask\` requires an investigation ID and a directive.
|
|
78902
|
+
|
|
77961
78903
|
## Saved investigations
|
|
77962
78904
|
|
|
78905
|
+
Use saved investigations to continue from existing evidence instead of restarting randomly:
|
|
78906
|
+
|
|
77963
78907
|
\`\`\`bash
|
|
78908
|
+
horus investigations
|
|
77964
78909
|
horus replay <id>
|
|
77965
78910
|
horus ask <id> "what evidence is missing?"
|
|
77966
78911
|
horus ask <id> "what evidence contradicts <hypothesis>?"
|
|
@@ -77968,67 +78913,224 @@ horus postmortem <id>
|
|
|
77968
78913
|
horus score <id>
|
|
77969
78914
|
\`\`\`
|
|
77970
78915
|
|
|
78916
|
+
## New codebase orientation
|
|
78917
|
+
|
|
78918
|
+
When entering an unfamiliar area of the repo:
|
|
78919
|
+
|
|
78920
|
+
\`\`\`bash
|
|
78921
|
+
horus doctor
|
|
78922
|
+
horus onboard [area]
|
|
78923
|
+
horus explain <symbol-or-hint>
|
|
78924
|
+
horus owner <symbol-or-hint>
|
|
78925
|
+
\`\`\`
|
|
78926
|
+
|
|
78927
|
+
## Change and fix workflow
|
|
78928
|
+
|
|
78929
|
+
For write or fix tasks:
|
|
78930
|
+
|
|
78931
|
+
1. Use Horus before editing to understand evidence, ownership, and blast radius.
|
|
78932
|
+
2. Make code changes using normal filesystem tools.
|
|
78933
|
+
3. Use Horus after editing to validate blast radius and relevant runtime/source assumptions.
|
|
78934
|
+
4. Run normal project tests, typechecks, linters, or smoke checks as appropriate.
|
|
78935
|
+
|
|
78936
|
+
Example:
|
|
78937
|
+
|
|
78938
|
+
\`\`\`bash
|
|
78939
|
+
horus investigate "<bug or behavior>"
|
|
78940
|
+
horus blast-radius <symbol>
|
|
78941
|
+
# edit files normally
|
|
78942
|
+
horus blast-radius <changed-symbol>
|
|
78943
|
+
horus ask <id> "does the proposed fix address the collected evidence?"
|
|
78944
|
+
\`\`\`
|
|
78945
|
+
|
|
78946
|
+
## Reporting format
|
|
78947
|
+
|
|
78948
|
+
When answering after using Horus, report:
|
|
78949
|
+
|
|
78950
|
+
* Conclusion
|
|
78951
|
+
* Horus evidence
|
|
78952
|
+
* Source locations
|
|
78953
|
+
* Missing, weak, stale, or ambient evidence
|
|
78954
|
+
* Conflicts between Horus evidence and filesystem/source inspection
|
|
78955
|
+
* Blast radius or risk
|
|
78956
|
+
* Recommended next action
|
|
78957
|
+
|
|
77971
78958
|
## Rules
|
|
77972
78959
|
|
|
77973
|
-
|
|
77974
|
-
|
|
77975
|
-
|
|
77976
|
-
|
|
77977
|
-
|
|
77978
|
-
|
|
77979
|
-
|
|
77980
|
-
|
|
78960
|
+
* Treat Horus evidence as grounded but not infallible.
|
|
78961
|
+
* Mention when Horus evidence is missing, stale, ambient, incomplete, or not structurally linked.
|
|
78962
|
+
* Do not invent runtime evidence Horus did not collect.
|
|
78963
|
+
* Prefer exact source locations from Horus over guesses.
|
|
78964
|
+
* If Horus output conflicts with filesystem or source inspection, report the discrepancy.
|
|
78965
|
+
* For write or fix tasks, use Horus to validate blast radius and rerun relevant checks after changes.
|
|
78966
|
+
* Horus is read-only evidence collection. Never use Horus to mutate production systems.
|
|
78967
|
+
* Do not hallucinate evidence. If Horus returns nothing, say so explicitly.
|
|
78968
|
+
* Do not use Horus as a substitute for tests, typechecks, or direct source inspection.
|
|
78969
|
+
* Do not overuse Horus for trivial syntax edits, formatting-only changes, or obvious local refactors unless blast radius matters.
|
|
77981
78970
|
`;
|
|
77982
|
-
|
|
78971
|
+
function skillFrontmatter(target) {
|
|
78972
|
+
const description = "Use Horus as the grounded evidence layer for production incident investigation.";
|
|
78973
|
+
switch (target) {
|
|
78974
|
+
case "claude":
|
|
78975
|
+
return `---
|
|
78976
|
+
name: horus
|
|
78977
|
+
description: ${description}
|
|
78978
|
+
---
|
|
77983
78979
|
|
|
77984
|
-
|
|
78980
|
+
`;
|
|
78981
|
+
case "cursor":
|
|
78982
|
+
return `---
|
|
78983
|
+
description: ${description}
|
|
78984
|
+
globs: *
|
|
78985
|
+
alwaysApply: true
|
|
78986
|
+
---
|
|
77985
78987
|
|
|
77986
|
-
Useful starting points:
|
|
77987
|
-
- \`horus investigations\` \u2014 list saved investigation IDs
|
|
77988
|
-
- \`horus doctor\` \u2014 check system health
|
|
77989
|
-
- \`horus onboard [area]\` \u2014 get oriented in a new codebase area
|
|
77990
78988
|
`;
|
|
78989
|
+
default:
|
|
78990
|
+
return "";
|
|
78991
|
+
}
|
|
78992
|
+
}
|
|
78993
|
+
var PROVIDER_NOTES = {
|
|
78994
|
+
claude: `## Claude Code notes
|
|
78995
|
+
|
|
78996
|
+
This skill is loaded automatically when present at:
|
|
78997
|
+
|
|
78998
|
+
\`\`\`bash
|
|
78999
|
+
.claude/skills/horus/SKILL.md
|
|
79000
|
+
\`\`\`
|
|
79001
|
+
|
|
79002
|
+
Useful starting points:
|
|
79003
|
+
|
|
79004
|
+
\`\`\`bash
|
|
79005
|
+
horus investigations
|
|
79006
|
+
horus doctor
|
|
79007
|
+
horus onboard [area]
|
|
79008
|
+
horus status
|
|
79009
|
+
\`\`\`
|
|
79010
|
+
`,
|
|
79011
|
+
gemini: `## Gemini CLI notes
|
|
79012
|
+
|
|
79013
|
+
This context file is loaded automatically by Gemini CLI when present at:
|
|
79014
|
+
|
|
79015
|
+
\`\`\`bash
|
|
79016
|
+
GEMINI.md
|
|
79017
|
+
\`\`\`
|
|
79018
|
+
|
|
79019
|
+
Useful starting points:
|
|
79020
|
+
|
|
79021
|
+
\`\`\`bash
|
|
79022
|
+
horus investigations
|
|
79023
|
+
horus doctor
|
|
79024
|
+
horus onboard [area]
|
|
79025
|
+
horus status
|
|
79026
|
+
\`\`\`
|
|
79027
|
+
`,
|
|
79028
|
+
cursor: `## Cursor notes
|
|
79029
|
+
|
|
79030
|
+
This rule is loaded automatically when present at \`.cursor/rules/horus.mdc\`.
|
|
79031
|
+
|
|
79032
|
+
Useful starting points:
|
|
79033
|
+
|
|
79034
|
+
\`\`\`bash
|
|
79035
|
+
horus investigations
|
|
79036
|
+
horus doctor
|
|
79037
|
+
horus onboard [area]
|
|
79038
|
+
horus status
|
|
79039
|
+
\`\`\`
|
|
79040
|
+
`,
|
|
79041
|
+
codex: `## Codex notes
|
|
79042
|
+
|
|
79043
|
+
This file is loaded automatically by Codex CLI as project instructions when present at \`AGENTS.md\`.
|
|
79044
|
+
|
|
79045
|
+
Useful starting points:
|
|
79046
|
+
|
|
79047
|
+
\`\`\`bash
|
|
79048
|
+
horus investigations
|
|
79049
|
+
horus doctor
|
|
79050
|
+
horus onboard [area]
|
|
79051
|
+
horus status
|
|
79052
|
+
\`\`\`
|
|
79053
|
+
`,
|
|
79054
|
+
generic: ""
|
|
79055
|
+
};
|
|
77991
79056
|
function generateSkillContent(target) {
|
|
77992
|
-
|
|
77993
|
-
|
|
79057
|
+
return skillFrontmatter(target) + BASE_SKILL + "\n" + PROVIDER_NOTES[target];
|
|
79058
|
+
}
|
|
79059
|
+
async function confirm(message) {
|
|
79060
|
+
if (!process.stdin.isTTY) {
|
|
79061
|
+
return false;
|
|
79062
|
+
}
|
|
79063
|
+
const rl = (0, import_promises.createInterface)({ input: process.stdin, output: process.stdout });
|
|
79064
|
+
try {
|
|
79065
|
+
const answer = await rl.question(`${message} [y/N] `);
|
|
79066
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
79067
|
+
} finally {
|
|
79068
|
+
rl.close();
|
|
77994
79069
|
}
|
|
77995
|
-
return BASE_SKILL;
|
|
77996
79070
|
}
|
|
77997
79071
|
async function runSkillInstall(target, opts) {
|
|
77998
79072
|
const log = opts.write ?? ((line2) => console.log(line2));
|
|
77999
79073
|
if (!isValidTarget(target)) {
|
|
78000
|
-
log(`${
|
|
78001
|
-
log(
|
|
79074
|
+
log(`${import_picocolors37.default.red("\u2717")} Unknown target: ${import_picocolors37.default.bold(target)}`);
|
|
79075
|
+
log(import_picocolors37.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
|
|
78002
79076
|
return 1;
|
|
78003
79077
|
}
|
|
78004
79078
|
const outPath = getSkillInstallPath(target, { global: opts.global, cwd: opts.cwd });
|
|
78005
79079
|
const content = generateSkillContent(target);
|
|
78006
|
-
if (
|
|
78007
|
-
|
|
78008
|
-
|
|
79080
|
+
if (target === "claude") {
|
|
79081
|
+
const legacyPath = getLegacyClaudeSkillPath({ global: opts.global, cwd: opts.cwd });
|
|
79082
|
+
if ((0, import_node_fs11.existsSync)(legacyPath)) {
|
|
79083
|
+
if (!opts.force) {
|
|
79084
|
+
const confirmed = await (opts.confirm ?? confirm)(
|
|
79085
|
+
`Found old flat skill file at ${legacyPath}. Replace it with the new directory layout?`
|
|
79086
|
+
);
|
|
79087
|
+
if (!confirmed) {
|
|
79088
|
+
log(`${import_picocolors37.default.yellow("!")} Migration cancelled; existing skill left untouched.`);
|
|
79089
|
+
log(import_picocolors37.default.dim(" Pass --force to replace without prompting."));
|
|
79090
|
+
return 1;
|
|
79091
|
+
}
|
|
79092
|
+
}
|
|
79093
|
+
try {
|
|
79094
|
+
(0, import_node_fs11.rmSync)(legacyPath);
|
|
79095
|
+
log(`${import_picocolors37.default.yellow("!")} Replaced old flat skill file: ${legacyPath}`);
|
|
79096
|
+
} catch (err) {
|
|
79097
|
+
log(`${import_picocolors37.default.red("\u2717")} Could not remove legacy skill file ${legacyPath}: ${err.message}`);
|
|
79098
|
+
return 1;
|
|
79099
|
+
}
|
|
79100
|
+
}
|
|
79101
|
+
}
|
|
79102
|
+
if ((0, import_node_fs11.existsSync)(outPath) && !opts.force) {
|
|
79103
|
+
log(`${import_picocolors37.default.red("\u2717")} ${outPath} already exists`);
|
|
79104
|
+
log(import_picocolors37.default.dim(" pass --force to overwrite"));
|
|
78009
79105
|
return 1;
|
|
78010
79106
|
}
|
|
78011
79107
|
try {
|
|
78012
|
-
(0,
|
|
78013
|
-
(0,
|
|
79108
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path13.dirname)(outPath), { recursive: true });
|
|
79109
|
+
(0, import_node_fs11.writeFileSync)(outPath, content, "utf8");
|
|
78014
79110
|
} catch (err) {
|
|
78015
|
-
log(`${
|
|
79111
|
+
log(`${import_picocolors37.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
|
|
78016
79112
|
return 1;
|
|
78017
79113
|
}
|
|
78018
|
-
log(`${
|
|
79114
|
+
log(`${import_picocolors37.default.green("\u2713")} Horus skill installed \u2192 ${outPath}`);
|
|
78019
79115
|
if (target === "claude") {
|
|
78020
|
-
log(
|
|
78021
|
-
log(
|
|
79116
|
+
log(import_picocolors37.default.dim(" Claude Code will load this skill automatically."));
|
|
79117
|
+
log(import_picocolors37.default.dim(" Invoke it with: /horus"));
|
|
79118
|
+
} else if (target === "gemini") {
|
|
79119
|
+
log(import_picocolors37.default.dim(" Gemini CLI will load this context file automatically."));
|
|
79120
|
+
} else if (target === "cursor") {
|
|
79121
|
+
log(import_picocolors37.default.dim(" Cursor will load this rule automatically."));
|
|
79122
|
+
} else if (target === "codex") {
|
|
79123
|
+
log(import_picocolors37.default.dim(" Codex CLI will load this file automatically."));
|
|
78022
79124
|
} else {
|
|
78023
|
-
log(
|
|
79125
|
+
log(import_picocolors37.default.dim(` Copy or reference this file in your ${target} agent instructions.`));
|
|
78024
79126
|
}
|
|
78025
79127
|
return 0;
|
|
78026
79128
|
}
|
|
78027
79129
|
async function runSkillPrint(target, opts) {
|
|
78028
79130
|
const log = opts.write ?? ((line2) => console.log(line2));
|
|
78029
79131
|
if (!isValidTarget(target)) {
|
|
78030
|
-
log(`${
|
|
78031
|
-
log(
|
|
79132
|
+
log(`${import_picocolors37.default.red("\u2717")} Unknown target: ${import_picocolors37.default.bold(target)}`);
|
|
79133
|
+
log(import_picocolors37.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
|
|
78032
79134
|
return 1;
|
|
78033
79135
|
}
|
|
78034
79136
|
process.stdout.write(generateSkillContent(target));
|
|
@@ -78037,8 +79139,8 @@ async function runSkillPrint(target, opts) {
|
|
|
78037
79139
|
async function runSkillPath(target, opts) {
|
|
78038
79140
|
const log = opts.write ?? ((line2) => console.log(line2));
|
|
78039
79141
|
if (!isValidTarget(target)) {
|
|
78040
|
-
log(`${
|
|
78041
|
-
log(
|
|
79142
|
+
log(`${import_picocolors37.default.red("\u2717")} Unknown target: ${import_picocolors37.default.bold(target)}`);
|
|
79143
|
+
log(import_picocolors37.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
|
|
78042
79144
|
return 1;
|
|
78043
79145
|
}
|
|
78044
79146
|
const outPath = getSkillInstallPath(target, { global: opts.global, cwd: opts.cwd });
|
|
@@ -78048,11 +79150,11 @@ async function runSkillPath(target, opts) {
|
|
|
78048
79150
|
|
|
78049
79151
|
// ../../packages/cli/src/commands/update.ts
|
|
78050
79152
|
init_cjs_shims();
|
|
78051
|
-
var
|
|
78052
|
-
var
|
|
78053
|
-
var
|
|
79153
|
+
var import_node_fs12 = require("fs");
|
|
79154
|
+
var import_node_path14 = require("path");
|
|
79155
|
+
var import_node_os4 = require("os");
|
|
78054
79156
|
var import_node_crypto2 = require("crypto");
|
|
78055
|
-
var
|
|
79157
|
+
var import_picocolors38 = __toESM(require_picocolors(), 1);
|
|
78056
79158
|
var RELEASES_API = "https://api.github.com/repos/meritt-dev/horus/releases/latest";
|
|
78057
79159
|
function parseVersion(v) {
|
|
78058
79160
|
const m = v.replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
@@ -78083,7 +79185,7 @@ function resolveBinaryPath() {
|
|
|
78083
79185
|
const raw = process.argv[1];
|
|
78084
79186
|
if (!raw) throw new Error("Cannot determine binary path from process.argv[1]");
|
|
78085
79187
|
try {
|
|
78086
|
-
return (0,
|
|
79188
|
+
return (0, import_node_fs12.realpathSync)(raw);
|
|
78087
79189
|
} catch {
|
|
78088
79190
|
return raw;
|
|
78089
79191
|
}
|
|
@@ -78093,33 +79195,33 @@ async function runUpdate(opts) {
|
|
|
78093
79195
|
const _fetch = opts._fetch ?? fetch;
|
|
78094
79196
|
const currentVersion = opts._currentVersion ?? HORUS_VERSION;
|
|
78095
79197
|
write(`
|
|
78096
|
-
${
|
|
79198
|
+
${import_picocolors38.default.bold("Horus update")}
|
|
78097
79199
|
`);
|
|
78098
|
-
write(` Current version: ${
|
|
79200
|
+
write(` Current version: ${import_picocolors38.default.cyan(currentVersion)}`);
|
|
78099
79201
|
let release;
|
|
78100
79202
|
try {
|
|
78101
79203
|
release = await fetchLatestRelease(_fetch);
|
|
78102
79204
|
} catch (e) {
|
|
78103
|
-
write(` ${
|
|
79205
|
+
write(` ${import_picocolors38.default.red("\u2717")} Could not reach GitHub: ${e.message}`);
|
|
78104
79206
|
return 1;
|
|
78105
79207
|
}
|
|
78106
79208
|
const latest = release.tag_name.replace(/^v/, "");
|
|
78107
|
-
write(` Latest version: ${
|
|
79209
|
+
write(` Latest version: ${import_picocolors38.default.cyan(latest)}`);
|
|
78108
79210
|
const newer = isNewer(latest, currentVersion);
|
|
78109
79211
|
if (opts.check) {
|
|
78110
79212
|
if (newer) {
|
|
78111
79213
|
write(`
|
|
78112
|
-
${
|
|
78113
|
-
write(` Run ${
|
|
79214
|
+
${import_picocolors38.default.yellow("\u2192")} ${import_picocolors38.default.bold(latest)} is available.`);
|
|
79215
|
+
write(` Run ${import_picocolors38.default.bold("horus update")} to upgrade.`);
|
|
78114
79216
|
} else {
|
|
78115
79217
|
write(`
|
|
78116
|
-
${
|
|
79218
|
+
${import_picocolors38.default.green("\u2713")} Already on the latest version.`);
|
|
78117
79219
|
}
|
|
78118
79220
|
return 0;
|
|
78119
79221
|
}
|
|
78120
79222
|
if (!newer && !opts.force) {
|
|
78121
79223
|
write(`
|
|
78122
|
-
${
|
|
79224
|
+
${import_picocolors38.default.green("\u2713")} Already on the latest version.`);
|
|
78123
79225
|
return 0;
|
|
78124
79226
|
}
|
|
78125
79227
|
const tag = release.tag_name;
|
|
@@ -78128,8 +79230,8 @@ ${import_picocolors36.default.bold("Horus update")}
|
|
|
78128
79230
|
const binaryAsset = release.assets.find((a) => a.name === assetName);
|
|
78129
79231
|
const checksumAsset = release.assets.find((a) => a.name === checksumName);
|
|
78130
79232
|
if (!binaryAsset) {
|
|
78131
|
-
write(` ${
|
|
78132
|
-
write(` Fallback: ${
|
|
79233
|
+
write(` ${import_picocolors38.default.red("\u2717")} Release ${tag} has no binary asset named '${assetName}'.`);
|
|
79234
|
+
write(` Fallback: ${import_picocolors38.default.dim("curl -fsSL https://horus.sh/install.sh | bash")}`);
|
|
78133
79235
|
return 1;
|
|
78134
79236
|
}
|
|
78135
79237
|
write(`
|
|
@@ -78139,7 +79241,7 @@ ${import_picocolors36.default.bold("Horus update")}
|
|
|
78139
79241
|
redirect: "follow"
|
|
78140
79242
|
});
|
|
78141
79243
|
if (!binRes.ok || !binRes.body) {
|
|
78142
|
-
write(` ${
|
|
79244
|
+
write(` ${import_picocolors38.default.red("\u2717")} Download failed: ${binRes.status}`);
|
|
78143
79245
|
return 1;
|
|
78144
79246
|
}
|
|
78145
79247
|
const binBuf = Buffer.from(await binRes.arrayBuffer());
|
|
@@ -78153,71 +79255,380 @@ ${import_picocolors36.default.bold("Horus update")}
|
|
|
78153
79255
|
const expectedHash = csText.trim().split(/\s+/)[0];
|
|
78154
79256
|
const actualHash = sha256hex(binBuf);
|
|
78155
79257
|
if (expectedHash !== actualHash) {
|
|
78156
|
-
write(` ${
|
|
79258
|
+
write(` ${import_picocolors38.default.red("\u2717")} Checksum mismatch \u2014 download may be corrupt.`);
|
|
78157
79259
|
write(` Expected: ${expectedHash}`);
|
|
78158
79260
|
write(` Got: ${actualHash}`);
|
|
78159
79261
|
return 1;
|
|
78160
79262
|
}
|
|
78161
|
-
write(` ${
|
|
79263
|
+
write(` ${import_picocolors38.default.green("\u2713")} Checksum verified.`);
|
|
78162
79264
|
}
|
|
78163
79265
|
}
|
|
78164
79266
|
let binaryPath;
|
|
78165
79267
|
try {
|
|
78166
79268
|
binaryPath = resolveBinaryPath();
|
|
78167
79269
|
} catch (e) {
|
|
78168
|
-
write(` ${
|
|
79270
|
+
write(` ${import_picocolors38.default.red("\u2717")} ${e.message}`);
|
|
78169
79271
|
return 1;
|
|
78170
79272
|
}
|
|
78171
|
-
if (!(0,
|
|
78172
|
-
write(` ${
|
|
79273
|
+
if (!(0, import_node_fs12.existsSync)(binaryPath)) {
|
|
79274
|
+
write(` ${import_picocolors38.default.red("\u2717")} Binary not found at ${binaryPath}`);
|
|
78173
79275
|
return 1;
|
|
78174
79276
|
}
|
|
78175
|
-
const tmpPath = (0,
|
|
79277
|
+
const tmpPath = (0, import_node_path14.join)((0, import_node_os4.tmpdir)(), `horus-update-${tag}-${process.pid}`);
|
|
78176
79278
|
const backupPath = `${binaryPath}.bak`;
|
|
78177
79279
|
try {
|
|
78178
|
-
(0,
|
|
79280
|
+
(0, import_node_fs12.writeFileSync)(tmpPath, binBuf, { mode: 493 });
|
|
78179
79281
|
} catch (e) {
|
|
78180
|
-
write(` ${
|
|
79282
|
+
write(` ${import_picocolors38.default.red("\u2717")} Could not write to temp dir: ${e.message}`);
|
|
78181
79283
|
return 1;
|
|
78182
79284
|
}
|
|
78183
79285
|
try {
|
|
78184
|
-
(0,
|
|
79286
|
+
(0, import_node_fs12.renameSync)(binaryPath, backupPath);
|
|
78185
79287
|
try {
|
|
78186
|
-
(0,
|
|
78187
|
-
(0,
|
|
79288
|
+
(0, import_node_fs12.renameSync)(tmpPath, binaryPath);
|
|
79289
|
+
(0, import_node_fs12.chmodSync)(binaryPath, 493);
|
|
78188
79290
|
} catch (e) {
|
|
78189
79291
|
try {
|
|
78190
|
-
(0,
|
|
79292
|
+
(0, import_node_fs12.renameSync)(backupPath, binaryPath);
|
|
78191
79293
|
} catch {
|
|
78192
79294
|
}
|
|
78193
79295
|
throw e;
|
|
78194
79296
|
}
|
|
78195
79297
|
try {
|
|
78196
|
-
(0,
|
|
79298
|
+
(0, import_node_fs12.unlinkSync)(backupPath);
|
|
78197
79299
|
} catch {
|
|
78198
79300
|
}
|
|
78199
79301
|
} catch (e) {
|
|
78200
79302
|
try {
|
|
78201
|
-
(0,
|
|
79303
|
+
(0, import_node_fs12.unlinkSync)(tmpPath);
|
|
78202
79304
|
} catch {
|
|
78203
79305
|
}
|
|
78204
79306
|
const msg = e.message ?? String(e);
|
|
78205
79307
|
const needsSudo = e.code === "EACCES" || e.code === "EPERM";
|
|
78206
|
-
write(` ${
|
|
79308
|
+
write(` ${import_picocolors38.default.red("\u2717")} Could not replace binary at ${binaryPath}: ${msg}`);
|
|
78207
79309
|
if (needsSudo) {
|
|
78208
79310
|
write(`
|
|
78209
79311
|
Try with sudo:`);
|
|
78210
79312
|
write(
|
|
78211
|
-
` ${
|
|
79313
|
+
` ${import_picocolors38.default.dim(`sudo curl -fsSL https://github.com/meritt-dev/horus/releases/download/${tag}/${assetName} -o ${binaryPath} && sudo chmod +x ${binaryPath}`)}`
|
|
78212
79314
|
);
|
|
78213
79315
|
}
|
|
78214
79316
|
return 1;
|
|
78215
79317
|
}
|
|
78216
|
-
write(` ${
|
|
78217
|
-
write(` ${
|
|
79318
|
+
write(` ${import_picocolors38.default.green("\u2713")} Updated: ${import_picocolors38.default.bold(currentVersion)} \u2192 ${import_picocolors38.default.bold(latest)}`);
|
|
79319
|
+
write(` ${import_picocolors38.default.dim(binaryPath)}`);
|
|
79320
|
+
return 0;
|
|
79321
|
+
}
|
|
79322
|
+
|
|
79323
|
+
// ../../packages/cli/src/commands/login.ts
|
|
79324
|
+
init_cjs_shims();
|
|
79325
|
+
var import_promises2 = require("readline/promises");
|
|
79326
|
+
var import_picocolors39 = __toESM(require_picocolors(), 1);
|
|
79327
|
+
async function promptToken() {
|
|
79328
|
+
if (!process.stdin.isTTY) return null;
|
|
79329
|
+
const rl = (0, import_promises2.createInterface)({ input: process.stdin, output: process.stdout });
|
|
79330
|
+
try {
|
|
79331
|
+
const answer = await rl.question("Paste your Horus Cloud token: ");
|
|
79332
|
+
return answer.trim() || null;
|
|
79333
|
+
} finally {
|
|
79334
|
+
rl.close();
|
|
79335
|
+
}
|
|
79336
|
+
}
|
|
79337
|
+
async function runLogin(opts = {}) {
|
|
79338
|
+
const apiBaseUrl = resolveApiBaseUrl(opts.apiUrl);
|
|
79339
|
+
const token = opts.token ?? process.env.HORUS_TOKEN ?? await promptToken();
|
|
79340
|
+
if (!token) {
|
|
79341
|
+
console.error(
|
|
79342
|
+
import_picocolors39.default.red("No token provided.") + "\n Create one in the Horus Cloud dashboard, then run " + import_picocolors39.default.bold("horus login --token <token>") + " (or set HORUS_TOKEN)."
|
|
79343
|
+
);
|
|
79344
|
+
console.error(import_picocolors39.default.dim(" Browser device-flow login is coming once the cloud device endpoints land."));
|
|
79345
|
+
return 1;
|
|
79346
|
+
}
|
|
79347
|
+
const client = new CloudClient(apiBaseUrl, token);
|
|
79348
|
+
try {
|
|
79349
|
+
const me = await client.me();
|
|
79350
|
+
writeAuth({
|
|
79351
|
+
apiBaseUrl,
|
|
79352
|
+
token,
|
|
79353
|
+
account: {
|
|
79354
|
+
userId: me.user.id,
|
|
79355
|
+
email: me.user.primaryEmail,
|
|
79356
|
+
displayName: me.user.displayName ?? void 0
|
|
79357
|
+
}
|
|
79358
|
+
});
|
|
79359
|
+
console.log(
|
|
79360
|
+
`${import_picocolors39.default.green("\u2713")} Logged in as ${import_picocolors39.default.bold(me.user.primaryEmail)}. Run ${import_picocolors39.default.bold("horus cloud link")} to connect this repo.`
|
|
79361
|
+
);
|
|
79362
|
+
return 0;
|
|
79363
|
+
} catch (err) {
|
|
79364
|
+
if (err instanceof CloudOfflineError) {
|
|
79365
|
+
console.error(import_picocolors39.default.red(err.message));
|
|
79366
|
+
return 1;
|
|
79367
|
+
}
|
|
79368
|
+
if (err instanceof CloudError && err.status === 401) {
|
|
79369
|
+
console.error(import_picocolors39.default.red("That token is not valid. Create a fresh one in the dashboard and try again."));
|
|
79370
|
+
return 1;
|
|
79371
|
+
}
|
|
79372
|
+
console.error(import_picocolors39.default.red(`Login failed: ${err.message}`));
|
|
79373
|
+
return 1;
|
|
79374
|
+
}
|
|
79375
|
+
}
|
|
79376
|
+
async function runLogout() {
|
|
79377
|
+
const auth = readAuth();
|
|
79378
|
+
if (!auth) {
|
|
79379
|
+
console.log(import_picocolors39.default.dim("Not logged in."));
|
|
79380
|
+
return 0;
|
|
79381
|
+
}
|
|
79382
|
+
const session = authedClient();
|
|
79383
|
+
if (session) {
|
|
79384
|
+
try {
|
|
79385
|
+
const prefix = auth.token.slice(0, 12);
|
|
79386
|
+
const { tokens } = await session.client.listTokens();
|
|
79387
|
+
const match = tokens.find((t) => t.prefix === prefix && !t.revokedAt);
|
|
79388
|
+
if (match) await session.client.revokeToken(match.id);
|
|
79389
|
+
} catch {
|
|
79390
|
+
}
|
|
79391
|
+
}
|
|
79392
|
+
clearAuth();
|
|
79393
|
+
console.log(`${import_picocolors39.default.green("\u2713")} Logged out. Local mode still works.`);
|
|
78218
79394
|
return 0;
|
|
78219
79395
|
}
|
|
78220
79396
|
|
|
79397
|
+
// ../../packages/cli/src/commands/cloud.ts
|
|
79398
|
+
init_cjs_shims();
|
|
79399
|
+
var import_picocolors40 = __toESM(require_picocolors(), 1);
|
|
79400
|
+
var import_promises3 = require("readline/promises");
|
|
79401
|
+
|
|
79402
|
+
// ../../packages/cli/src/lib/cloud/git.ts
|
|
79403
|
+
init_cjs_shims();
|
|
79404
|
+
var import_node_child_process9 = require("child_process");
|
|
79405
|
+
function parseRepoName(remoteUrl) {
|
|
79406
|
+
const cleaned = remoteUrl.trim().replace(/\.git$/, "");
|
|
79407
|
+
const lastSegment = cleaned.split(/[/:]/).filter(Boolean).pop() ?? "";
|
|
79408
|
+
return lastSegment;
|
|
79409
|
+
}
|
|
79410
|
+
function detectGitRemote(root) {
|
|
79411
|
+
try {
|
|
79412
|
+
const remoteUrl = (0, import_node_child_process9.execFileSync)("git", ["-C", root, "remote", "get-url", "origin"], {
|
|
79413
|
+
encoding: "utf8",
|
|
79414
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
79415
|
+
}).trim();
|
|
79416
|
+
if (!remoteUrl) return null;
|
|
79417
|
+
return { remoteUrl, repoName: parseRepoName(remoteUrl) };
|
|
79418
|
+
} catch {
|
|
79419
|
+
return null;
|
|
79420
|
+
}
|
|
79421
|
+
}
|
|
79422
|
+
|
|
79423
|
+
// ../../packages/cli/src/commands/cloud.ts
|
|
79424
|
+
async function runCloudLink(opts = {}) {
|
|
79425
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
79426
|
+
const session = authedClient();
|
|
79427
|
+
if (!session) {
|
|
79428
|
+
console.error(import_picocolors40.default.red(`Not logged in. Run ${import_picocolors40.default.bold("horus login")} first.`));
|
|
79429
|
+
return 1;
|
|
79430
|
+
}
|
|
79431
|
+
let ctx;
|
|
79432
|
+
try {
|
|
79433
|
+
ctx = await session.client.context();
|
|
79434
|
+
} catch (err) {
|
|
79435
|
+
return reportCloudError(err);
|
|
79436
|
+
}
|
|
79437
|
+
const remote = detectGitRemote(root);
|
|
79438
|
+
if (remote) console.log(import_picocolors40.default.dim(`Detected git remote: ${remote.remoteUrl}`));
|
|
79439
|
+
let target = opts.project;
|
|
79440
|
+
if (!target) {
|
|
79441
|
+
const existing = readCloudConfig(root);
|
|
79442
|
+
if (isCloudActive(existing) && existing?.organization && existing?.workspace && existing?.project) {
|
|
79443
|
+
target = `${existing.organization.slug}/${existing.workspace.slug}/${existing.project.slug}`;
|
|
79444
|
+
}
|
|
79445
|
+
}
|
|
79446
|
+
if (!target) {
|
|
79447
|
+
target = await pickProjectInteractively(ctx);
|
|
79448
|
+
}
|
|
79449
|
+
if (!target) {
|
|
79450
|
+
console.error(
|
|
79451
|
+
import_picocolors40.default.red("No project specified.") + `
|
|
79452
|
+
Pass ${import_picocolors40.default.bold("--project <org/workspace/project>")}, or run ${import_picocolors40.default.bold("horus context list")} to see options.`
|
|
79453
|
+
);
|
|
79454
|
+
return 1;
|
|
79455
|
+
}
|
|
79456
|
+
const resolved = resolveTriple(ctx, target);
|
|
79457
|
+
if (!resolved) {
|
|
79458
|
+
console.error(
|
|
79459
|
+
import_picocolors40.default.red(`You don't have access to ${import_picocolors40.default.bold(target)}.`) + `
|
|
79460
|
+
Run ${import_picocolors40.default.bold("horus context list")} to see available projects.`
|
|
79461
|
+
);
|
|
79462
|
+
return 1;
|
|
79463
|
+
}
|
|
79464
|
+
writeCloudConfig(root, {
|
|
79465
|
+
context: "cloud",
|
|
79466
|
+
organization: resolved.organization,
|
|
79467
|
+
workspace: resolved.workspace,
|
|
79468
|
+
project: resolved.project
|
|
79469
|
+
});
|
|
79470
|
+
console.log(
|
|
79471
|
+
`${import_picocolors40.default.green("\u2713")} Linked. Context for this repo is now ${import_picocolors40.default.bold(target)} ${import_picocolors40.default.dim("(cloud)")}.`
|
|
79472
|
+
);
|
|
79473
|
+
console.log(
|
|
79474
|
+
import_picocolors40.default.dim(` Investigations here save to Horus Cloud. ${import_picocolors40.default.bold("horus context use local")} to switch back.`)
|
|
79475
|
+
);
|
|
79476
|
+
return 0;
|
|
79477
|
+
}
|
|
79478
|
+
async function runCloudUnlink(opts = {}) {
|
|
79479
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
79480
|
+
const cfg = readCloudConfig(root);
|
|
79481
|
+
if (!cfg || cfg.context !== "cloud") {
|
|
79482
|
+
console.log(import_picocolors40.default.dim("This repo isn't linked to a cloud project."));
|
|
79483
|
+
return 0;
|
|
79484
|
+
}
|
|
79485
|
+
clearCloudConfig(root);
|
|
79486
|
+
console.log(`${import_picocolors40.default.green("\u2713")} Unlinked. This repo is back to ${import_picocolors40.default.bold("local")} mode.`);
|
|
79487
|
+
return 0;
|
|
79488
|
+
}
|
|
79489
|
+
async function runCloudStatus(opts = {}) {
|
|
79490
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
79491
|
+
const cfg = readCloudConfig(root);
|
|
79492
|
+
const session = authedClient();
|
|
79493
|
+
if (!isCloudActive(cfg) || !cfg?.project) {
|
|
79494
|
+
console.log(`${import_picocolors40.default.bold("Context:")} local`);
|
|
79495
|
+
console.log(`${import_picocolors40.default.bold("Storage:")} local Postgres (~/.horus)`);
|
|
79496
|
+
console.log(
|
|
79497
|
+
import_picocolors40.default.dim(`Tip: ${import_picocolors40.default.bold("horus login")} then ${import_picocolors40.default.bold("horus cloud link")} to share investigations with your team.`)
|
|
79498
|
+
);
|
|
79499
|
+
return 0;
|
|
79500
|
+
}
|
|
79501
|
+
const triple = `${cfg.organization?.slug}/${cfg.workspace?.slug}/${cfg.project.slug}`;
|
|
79502
|
+
console.log(`${import_picocolors40.default.bold("Context:")} ${triple} ${import_picocolors40.default.dim("(cloud)")}`);
|
|
79503
|
+
let projectLine = `${cfg.project.slug}`;
|
|
79504
|
+
let metaLines = [];
|
|
79505
|
+
let account = import_picocolors40.default.dim("not logged in");
|
|
79506
|
+
if (session) {
|
|
79507
|
+
account = session.auth.account.email;
|
|
79508
|
+
try {
|
|
79509
|
+
const ctx = await session.client.context();
|
|
79510
|
+
const project = ctx.projects.find((p) => p.id === cfg.project?.id);
|
|
79511
|
+
const ws = ctx.workspaces.find((w) => w.id === project?.workspaceId);
|
|
79512
|
+
const org = ctx.organizations.find((o) => o.id === project?.organizationId);
|
|
79513
|
+
if (project && ws && org) {
|
|
79514
|
+
projectLine = `${project.name} \xB7 workspace ${ws.name} \xB7 org ${org.name}`;
|
|
79515
|
+
metaLines = syncMetaLines(project);
|
|
79516
|
+
}
|
|
79517
|
+
} catch {
|
|
79518
|
+
}
|
|
79519
|
+
}
|
|
79520
|
+
console.log(`${import_picocolors40.default.bold("Project:")} ${projectLine}`);
|
|
79521
|
+
for (const line2 of metaLines) console.log(line2);
|
|
79522
|
+
console.log(`${import_picocolors40.default.bold("Account:")} ${account}`);
|
|
79523
|
+
return 0;
|
|
79524
|
+
}
|
|
79525
|
+
async function runCloudSync(opts = {}) {
|
|
79526
|
+
const root = repoRootOrCwd(opts.cwd);
|
|
79527
|
+
const cfg = readCloudConfig(root);
|
|
79528
|
+
if (!isCloudActive(cfg) || !cfg.project) {
|
|
79529
|
+
console.error(
|
|
79530
|
+
import_picocolors40.default.red("This repo isn't linked to a cloud project.") + ` Run ${import_picocolors40.default.bold("horus cloud link")} first.`
|
|
79531
|
+
);
|
|
79532
|
+
return 1;
|
|
79533
|
+
}
|
|
79534
|
+
const session = authedClient();
|
|
79535
|
+
if (!session) {
|
|
79536
|
+
console.error(import_picocolors40.default.red(`Not logged in. Run ${import_picocolors40.default.bold("horus login")} first.`));
|
|
79537
|
+
return 1;
|
|
79538
|
+
}
|
|
79539
|
+
const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
|
|
79540
|
+
const uploadable = [];
|
|
79541
|
+
let skippedNoReport = 0;
|
|
79542
|
+
try {
|
|
79543
|
+
const rows = await listInvestigationsWithReports(db, opts.limit ?? 1e3);
|
|
79544
|
+
for (const row of rows) {
|
|
79545
|
+
if (row.report && typeof row.report === "object") {
|
|
79546
|
+
uploadable.push({ id: row.id, title: row.title, report: row.report });
|
|
79547
|
+
} else {
|
|
79548
|
+
skippedNoReport++;
|
|
79549
|
+
}
|
|
79550
|
+
}
|
|
79551
|
+
} finally {
|
|
79552
|
+
await sql2.end();
|
|
79553
|
+
}
|
|
79554
|
+
const target = `${cfg.organization?.slug}/${cfg.workspace?.slug}/${cfg.project.slug}`;
|
|
79555
|
+
if (uploadable.length === 0) {
|
|
79556
|
+
console.log(
|
|
79557
|
+
import_picocolors40.default.dim(
|
|
79558
|
+
`No local investigations with reports to upload.` + (skippedNoReport ? ` (${skippedNoReport} without a report skipped)` : "")
|
|
79559
|
+
)
|
|
79560
|
+
);
|
|
79561
|
+
return 0;
|
|
79562
|
+
}
|
|
79563
|
+
console.log(import_picocolors40.default.bold(`Will upload ${uploadable.length} investigation(s) to ${target} ${import_picocolors40.default.dim("(cloud)")}:`));
|
|
79564
|
+
for (const c of uploadable.slice(0, 20)) {
|
|
79565
|
+
console.log(` ${import_picocolors40.default.dim(c.id.slice(0, 8))} ${(c.title ?? "untitled").slice(0, 70)}`);
|
|
79566
|
+
}
|
|
79567
|
+
if (uploadable.length > 20) console.log(import_picocolors40.default.dim(` \u2026and ${uploadable.length - 20} more`));
|
|
79568
|
+
if (skippedNoReport) console.log(import_picocolors40.default.dim(` (${skippedNoReport} local investigation(s) without a report will be skipped)`));
|
|
79569
|
+
if (opts.dryRun) {
|
|
79570
|
+
console.log(import_picocolors40.default.dim("Dry run \u2014 nothing uploaded. Re-run without --dry-run to upload."));
|
|
79571
|
+
return 0;
|
|
79572
|
+
}
|
|
79573
|
+
if (!opts.yes) {
|
|
79574
|
+
if (!process.stdin.isTTY) {
|
|
79575
|
+
console.error(
|
|
79576
|
+
import_picocolors40.default.yellow(`Re-run with ${import_picocolors40.default.bold("--yes")} to upload, or ${import_picocolors40.default.bold("--dry-run")} to preview only.`)
|
|
79577
|
+
);
|
|
79578
|
+
return 1;
|
|
79579
|
+
}
|
|
79580
|
+
const rl = (0, import_promises3.createInterface)({ input: process.stdin, output: process.stdout });
|
|
79581
|
+
try {
|
|
79582
|
+
const answer = (await rl.question(`Upload ${uploadable.length} investigation(s) to ${target}? (y/N) `)).trim().toLowerCase();
|
|
79583
|
+
if (answer !== "y" && answer !== "yes") {
|
|
79584
|
+
console.log(import_picocolors40.default.dim("Aborted. Nothing uploaded."));
|
|
79585
|
+
return 0;
|
|
79586
|
+
}
|
|
79587
|
+
} finally {
|
|
79588
|
+
rl.close();
|
|
79589
|
+
}
|
|
79590
|
+
}
|
|
79591
|
+
let created = 0;
|
|
79592
|
+
let failed = 0;
|
|
79593
|
+
for (const c of uploadable) {
|
|
79594
|
+
try {
|
|
79595
|
+
const refs = await uploadInvestigationToCloud(session.client, cfg, c.report);
|
|
79596
|
+
created++;
|
|
79597
|
+
console.log(`${import_picocolors40.default.green("\u2713")} ${import_picocolors40.default.dim(c.id.slice(0, 8))} \u2192 ${refs.investigationId}`);
|
|
79598
|
+
} catch (err) {
|
|
79599
|
+
failed++;
|
|
79600
|
+
console.error(`${import_picocolors40.default.red("\u2717")} ${import_picocolors40.default.dim(c.id.slice(0, 8))} ${err.message}`);
|
|
79601
|
+
}
|
|
79602
|
+
}
|
|
79603
|
+
console.log("");
|
|
79604
|
+
console.log(
|
|
79605
|
+
`${import_picocolors40.default.bold("Sync complete:")} ${import_picocolors40.default.green(`${created} uploaded`)}, ${skippedNoReport} skipped, ` + (failed ? import_picocolors40.default.red(`${failed} failed`) : "0 failed") + "."
|
|
79606
|
+
);
|
|
79607
|
+
console.log(
|
|
79608
|
+
import_picocolors40.default.dim("Local data was not modified. Re-running is safe \u2014 duplicates are deduped by idempotency key.")
|
|
79609
|
+
);
|
|
79610
|
+
return failed > 0 ? 1 : 0;
|
|
79611
|
+
}
|
|
79612
|
+
async function pickProjectInteractively(ctx) {
|
|
79613
|
+
if (!process.stdin.isTTY) return void 0;
|
|
79614
|
+
const labels = ctx.projects.map((p) => {
|
|
79615
|
+
const ws = ctx.workspaces.find((w) => w.id === p.workspaceId);
|
|
79616
|
+
const org = ctx.organizations.find((o) => o.id === p.organizationId);
|
|
79617
|
+
return ws && org ? `${org.slug}/${ws.slug}/${p.slug}` : null;
|
|
79618
|
+
}).filter((x) => !!x);
|
|
79619
|
+
if (labels.length === 0) return void 0;
|
|
79620
|
+
console.log("Select a project to link:");
|
|
79621
|
+
labels.forEach((label, i) => console.log(` ${i + 1}) ${label}`));
|
|
79622
|
+
const rl = (0, import_promises3.createInterface)({ input: process.stdin, output: process.stdout });
|
|
79623
|
+
try {
|
|
79624
|
+
const answer = (await rl.question("Number: ")).trim();
|
|
79625
|
+
const idx = Number(answer) - 1;
|
|
79626
|
+
return labels[idx];
|
|
79627
|
+
} finally {
|
|
79628
|
+
rl.close();
|
|
79629
|
+
}
|
|
79630
|
+
}
|
|
79631
|
+
|
|
78221
79632
|
// ../../packages/cli/src/index.ts
|
|
78222
79633
|
function buildProgram() {
|
|
78223
79634
|
const program2 = new Command();
|
|
@@ -78511,7 +79922,7 @@ Examples:
|
|
|
78511
79922
|
});
|
|
78512
79923
|
program2.command("logs [service]").description(
|
|
78513
79924
|
"Synthesize error evidence from logs (signatures, first/last, affected services); --raw for lines"
|
|
78514
|
-
).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").option("--ai", "append AI narration of error signatures (default mode only, not --raw or --json)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
79925
|
+
).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("--group-by <field>", "aggregate error counts by a context field, e.g. context.brand_id").option("--limit <n>", "max records (with --raw)").option("--json", "output JSON").option("--ai", "append AI narration of error signatures (default mode only, not --raw or --json)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
78515
79926
|
async (service, opts) => {
|
|
78516
79927
|
process.exitCode = await runLogs(service, opts);
|
|
78517
79928
|
}
|
|
@@ -78535,11 +79946,11 @@ Examples:
|
|
|
78535
79946
|
process.exitCode = await runSkillInstall(target, { force: opts.force, global: opts.global });
|
|
78536
79947
|
}).addHelpText("after", `
|
|
78537
79948
|
Targets:
|
|
78538
|
-
claude \u2192 .claude/skills/horus.md (Claude Code
|
|
78539
|
-
codex \u2192 .
|
|
78540
|
-
gemini \u2192 .
|
|
78541
|
-
cursor \u2192 .
|
|
78542
|
-
generic \u2192 .horus/skills/horus-generic.md
|
|
79949
|
+
claude \u2192 .claude/skills/horus/SKILL.md (Claude Code skill)
|
|
79950
|
+
codex \u2192 AGENTS.md (Codex CLI project instructions)
|
|
79951
|
+
gemini \u2192 GEMINI.md (Gemini CLI project context)
|
|
79952
|
+
cursor \u2192 .cursor/rules/horus.mdc (Cursor project rule)
|
|
79953
|
+
generic \u2192 .horus/skills/horus-generic.md (plain Markdown)
|
|
78543
79954
|
|
|
78544
79955
|
Examples:
|
|
78545
79956
|
horus skill install claude
|
|
@@ -78568,6 +79979,47 @@ Examples:
|
|
|
78568
79979
|
horus update --check # check without installing
|
|
78569
79980
|
horus update --force # re-download current version
|
|
78570
79981
|
`);
|
|
79982
|
+
program2.command("login").description("Log in to Horus Cloud; stores a CLI token in ~/.horus/auth.json (never in the repo)").option("--token <token>", "CLI token (or set HORUS_TOKEN); otherwise prompts when interactive").option("--api-url <url>", "Horus Cloud API base URL (or set HORUS_CLOUD_API_URL)").action(async (opts) => {
|
|
79983
|
+
process.exitCode = await runLogin({ token: opts.token, apiUrl: opts.apiUrl });
|
|
79984
|
+
}).addHelpText("after", `
|
|
79985
|
+
Examples:
|
|
79986
|
+
horus login --token <token>
|
|
79987
|
+
HORUS_TOKEN=<token> horus login`);
|
|
79988
|
+
program2.command("logout").description("Revoke the current CLI token and clear local credentials").action(async () => {
|
|
79989
|
+
process.exitCode = await runLogout();
|
|
79990
|
+
});
|
|
79991
|
+
const context = program2.command("context").description("Select where this repo stores investigations (local or a cloud project)");
|
|
79992
|
+
context.command("list").description("List the local context and the cloud contexts you can use").action(async () => {
|
|
79993
|
+
process.exitCode = await runContextList();
|
|
79994
|
+
});
|
|
79995
|
+
context.command("use <target>").description('Switch context: "local" or "<org>/<workspace>/<project>"').action(async (target) => {
|
|
79996
|
+
process.exitCode = await runContextUse(target);
|
|
79997
|
+
});
|
|
79998
|
+
context.command("show").description("Show the active context and the selected project's sync metadata").action(async () => {
|
|
79999
|
+
process.exitCode = await runContextShow();
|
|
80000
|
+
});
|
|
80001
|
+
const cloud = program2.command("cloud").description("Link this repo to a Horus Cloud project and inspect cloud state");
|
|
80002
|
+
cloud.command("link").description("Link the current repo to a cloud project (Git-aware)").option("--project <org/workspace/project>", "target project (skips the interactive picker)").option("-y, --yes", "skip confirmation prompts").action(async (opts) => {
|
|
80003
|
+
process.exitCode = await runCloudLink({ project: opts.project, yes: opts.yes });
|
|
80004
|
+
});
|
|
80005
|
+
cloud.command("unlink").description("Remove this repo's cloud link (returns to local mode)").action(async () => {
|
|
80006
|
+
process.exitCode = await runCloudUnlink();
|
|
80007
|
+
});
|
|
80008
|
+
cloud.command("status").description("Show the active context, linked project, sync metadata, and auth state").action(async () => {
|
|
80009
|
+
process.exitCode = await runCloudStatus();
|
|
80010
|
+
});
|
|
80011
|
+
cloud.command("sync").description("Upload existing local investigations to the linked cloud project").option("-c, --config <path>", "path to horus config for the local database").option("--limit <n>", "max local investigations to consider", (v) => Number.parseInt(v, 10)).option("--dry-run", "show what would be uploaded without uploading").option("-y, --yes", "skip the confirmation prompt").action(async (opts) => {
|
|
80012
|
+
process.exitCode = await runCloudSync({
|
|
80013
|
+
config: opts.config,
|
|
80014
|
+
limit: opts.limit,
|
|
80015
|
+
dryRun: opts.dryRun,
|
|
80016
|
+
yes: opts.yes
|
|
80017
|
+
});
|
|
80018
|
+
}).addHelpText("after", `
|
|
80019
|
+
Examples:
|
|
80020
|
+
horus cloud sync --dry-run # preview what would upload
|
|
80021
|
+
horus cloud sync # confirm, then upload
|
|
80022
|
+
horus cloud sync --yes # upload without prompting`);
|
|
78571
80023
|
return program2;
|
|
78572
80024
|
}
|
|
78573
80025
|
async function run(argv = process.argv) {
|