@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.
Files changed (2) hide show
  1. package/dist/index.cjs +1950 -498
  2. 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.18" : "dev";
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.text !== void 0 ? [{ match: { [mapping.messageField]: q.text } }] : [{ match_all: {} }];
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 ctxRaw = src["context"];
55890
- if (typeof ctxRaw === "string") {
55903
+ const ctxRaw2 = src["context"];
55904
+ if (typeof ctxRaw2 === "string") {
55891
55905
  try {
55892
- const parsed = JSON.parse(ctxRaw);
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.text !== void 0 ? [{ match: { [mapping.messageField]: q.text } }] : [{ match_all: {} }];
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
- relevance: severe ? 0.85 : 0.75,
57352
- payload: { queueName: q.queueName, failed: q.failed }
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
- relevance: 0.82,
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 this.client.pipelineHget(name, failedJobIds, "failedReason");
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
- for (const r of reasons) {
57488
- if (r === null || r === "") continue;
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
- byReason.set(key, (byReason.get(key) ?? 0) + 1);
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 join7(chunks, separator) {
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 = join7;
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((join7) => join7.alias === tableName)) {
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((join7) => join7.alias === tableName)) {
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 join7 of this.config.joins) {
63800
- const tableName2 = getTableLikeName(join7.table);
63801
- if (typeof tableName2 === "string" && !is(join7.table, SQL)) {
63802
- const fromFields = this.getTableLikeFields(join7.table);
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
- const seeds = ranked.map((r) => r.symbol);
69888
- const top = seeds[0];
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 pc37 of input.producerClasses) {
72942
- for (const m of pc37.content.matchAll(INJECT_QUEUE_RE)) {
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: pc37.name, file: pc37.filePath });
73165
+ if (queue) producers.push({ queue, symbol: pc41.name, file: pc41.filePath });
72945
73166
  }
72946
- for (const m of pc37.content.matchAll(NEW_BULL_RE)) {
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] ?? pc37.name) || baseName(pc37.filePath), file: pc37.filePath });
73171
+ producers.push({ queue, symbol: (m[1] ?? pc41.name) || baseName(pc41.filePath), file: pc41.filePath });
72951
73172
  }
72952
73173
  }
72953
- for (const m of pc37.content.matchAll(QUEUE_NAME_CONST_RE)) {
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(pc37.filePath), file: pc37.filePath });
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 { reason, count } of q.failedBreakdown.slice(0, 3)) {
74125
- console.log(import_picocolors4.default.red(` \u2717 [${count}x] ${reason}`));
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(import_picocolors5.default.bold("AI Judgment") + import_picocolors5.default.dim(` (confidence: ${(judgment.confidence * 100).toFixed(0)}%, provider: ${judgment.provider})`));
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(import_picocolors5.default.bold("Hypothesis verdicts:"));
74905
+ write(import_picocolors6.default.bold("Hypothesis verdicts:"));
74202
74906
  for (const j of judgment.hypothesisJudgments) {
74203
- const verdictColor = j.verdict === "supported" ? import_picocolors5.default.green : j.verdict === "weakened" ? import_picocolors5.default.yellow : j.verdict === "eliminated" ? import_picocolors5.default.red : import_picocolors5.default.dim;
74204
- write(` ${verdictColor(`[${j.verdict}]`)} ${j.category}` + import_picocolors5.default.dim(` (${(j.confidence * 100).toFixed(0)}%)`));
74205
- write(import_picocolors5.default.dim(` ${j.rationale}`));
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" ? import_picocolors5.default.green : rca.uncertainty === "medium" ? import_picocolors5.default.yellow : import_picocolors5.default.red;
74212
- write(import_picocolors5.default.bold("Root cause assessment:") + import_picocolors5.default.dim(` uncertainty: ${uncertaintyColor(rca.uncertainty)}`));
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(import_picocolors5.default.dim(` Cited: ${rca.citedEvidenceIds.join(", ")}`));
74919
+ write(import_picocolors6.default.dim(` Cited: ${rca.citedEvidenceIds.join(", ")}`));
74216
74920
  }
74217
74921
  write("");
74218
74922
  }
74219
- write(import_picocolors5.default.bold("What:") + " " + judgment.what);
74220
- write(import_picocolors5.default.bold("Why:") + " " + judgment.why);
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(import_picocolors5.default.bold("Next steps:"));
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(import_picocolors5.default.dim(`
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(import_picocolors5.default.red(err.message));
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
- import_picocolors5.default.red(
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
- import_picocolors5.default.red(
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(import_picocolors5.default.dim(`[ai] model: ${model}`));
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(import_picocolors5.default.yellow(`[ai] structured validation incomplete \u2014 showing raw AI narrative`));
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(import_picocolors5.default.dim("\u2500".repeat(60)));
74334
- console.log(import_picocolors5.default.bold("AI narrative ") + import_picocolors5.default.yellow("(unstructured \u2014 not validated)"));
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(import_picocolors5.default.bold("\nWhat:"), output.what.trim());
75052
+ console.log(import_picocolors6.default.bold("\nWhat:"), output.what.trim());
74337
75053
  }
74338
75054
  if (output.why?.trim()) {
74339
- console.log(import_picocolors5.default.bold("\nWhy:"), output.why.trim());
75055
+ console.log(import_picocolors6.default.bold("\nWhy:"), output.why.trim());
74340
75056
  }
74341
75057
  if (output.whereNext.length > 0) {
74342
- console.log(import_picocolors5.default.bold("\nNext:"));
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(import_picocolors5.default.yellow(`[ai] fallback to deterministic \u2014 ${reason}`));
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(import_picocolors5.default.red(err.message));
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 import_picocolors6 = __toESM(require_picocolors(), 1);
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
- import_picocolors6.default.red("Source-intelligence host unreachable \u2014 run: horus index")
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(import_picocolors6.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors6.default.red(err.message));
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 import_picocolors7 = __toESM(require_picocolors(), 1);
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(import_picocolors7.default.red(err.message));
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
- import_picocolors7.default.dim(
75193
+ import_picocolors8.default.dim(
74470
75194
  `
74471
- Showing the last 7 days (default). Widen with ${import_picocolors7.default.bold('--since "30 days ago"')}, pin a range with ${import_picocolors7.default.bold("--since <when> --until <when>")}, or see everything with ${import_picocolors7.default.bold("--all")}.`
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(import_picocolors7.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors7.default.red(err.message));
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 import_picocolors8 = __toESM(require_picocolors(), 1);
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(import_picocolors8.default.red(err.message));
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(import_picocolors8.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors8.default.red(err.message));
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 import_picocolors9 = __toESM(require_picocolors(), 1);
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
- import_picocolors9.default.red("Source-intelligence host unreachable \u2014 run: horus index")
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(import_picocolors9.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors9.default.red(err.message));
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 import_picocolors10 = __toESM(require_picocolors(), 1);
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(import_picocolors10.default.red("Source-intelligence host unreachable \u2014 run: horus index"));
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(import_picocolors10.default.dim(` Tip: use an exact class or function name, e.g. "MyService"`));
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
- import_picocolors10.default.yellow(` No exact match for "${query}"`) + import_picocolors10.default.dim(` \u2014 showing closest: "${r.seed.name}" (fuzzy match)`)
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(import_picocolors10.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors10.default.red(err.message));
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 import_picocolors11 = __toESM(require_picocolors(), 1);
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(import_picocolors11.default.dim("No repositories configured."));
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 ? import_picocolors11.default.green("\u25CF") : import_picocolors11.default.red("\u25CF");
74725
- const name = import_picocolors11.default.bold(h.repo.padEnd(20));
74726
- const hostUrl = import_picocolors11.default.cyan(h.hostUrl.padEnd(30));
74727
- const path = import_picocolors11.default.dim(h.path);
74728
- const detail = h.detail ? import_picocolors11.default.dim(`(${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 import_picocolors12 = __toESM(require_picocolors(), 1);
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 ? import_picocolors12.default.green("\u25CF") : import_picocolors12.default.red("\u25CF");
75471
+ const statusDot = result.reachable ? import_picocolors13.default.green("\u25CF") : import_picocolors13.default.red("\u25CF");
74748
75472
  console.log("");
74749
- console.log(`${statusDot} ${import_picocolors12.default.bold("## " + result.repo)} ${import_picocolors12.default.dim(result.hostUrl)}`);
75473
+ console.log(`${statusDot} ${import_picocolors13.default.bold("## " + result.repo)} ${import_picocolors13.default.dim(result.hostUrl)}`);
74750
75474
  if (!result.reachable) {
74751
- console.log(import_picocolors12.default.dim(" (unreachable)"));
75475
+ console.log(import_picocolors13.default.dim(" (unreachable)"));
74752
75476
  } else if (result.symbols.length === 0) {
74753
- console.log(import_picocolors12.default.dim(" (no matches)"));
75477
+ console.log(import_picocolors13.default.dim(" (no matches)"));
74754
75478
  } else {
74755
75479
  for (const sym of result.symbols) {
74756
- console.log(` - ${import_picocolors12.default.bold(sym.name)} ${import_picocolors12.default.dim(sym.filePath)}`);
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
- import_picocolors12.default.dim(
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 import_picocolors13 = __toESM(require_picocolors(), 1);
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(import_picocolors13.default.red("No investigation found: " + id));
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
- import_picocolors13.default.red(
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(import_picocolors13.default.dim("[ai] Stored judgment replayed. Use --refresh-ai to regenerate."));
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(import_picocolors13.default.yellow("[ai] structured validation incomplete \u2014 showing raw AI narrative"));
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(import_picocolors13.default.dim("\u2500".repeat(60)));
74867
- console.log(import_picocolors13.default.bold("AI narrative ") + import_picocolors13.default.yellow("(unstructured \u2014 not validated)"));
74868
- if (output.what?.trim()) console.log(import_picocolors13.default.bold("\nWhat:"), output.what.trim());
74869
- if (output.why?.trim()) console.log(import_picocolors13.default.bold("\nWhy:"), output.why.trim());
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(import_picocolors13.default.yellow("[ai] Provider unavailable \u2014 deterministic output shown above."));
75624
+ console.error(import_picocolors15.default.yellow("[ai] Provider unavailable \u2014 deterministic output shown above."));
74872
75625
  if (validationErrors?.length) {
74873
- console.error(import_picocolors13.default.dim(` ${validationErrors[0]}`));
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 import_picocolors14 = __toESM(require_picocolors(), 1);
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(import_picocolors14.default.red(err.message));
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(import_picocolors14.default.red(err.message));
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 import_node_fs6 = require("fs");
74918
- var import_node_path7 = require("path");
74919
- var import_picocolors15 = __toESM(require_picocolors(), 1);
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(import_picocolors15.default.red("No investigation found: " + id));
75686
+ log(import_picocolors17.default.red("No investigation found: " + id));
74934
75687
  return 1;
74935
75688
  }
74936
75689
  if (!row.report) {
74937
- log(import_picocolors15.default.red("Investigation " + id + " has no stored report."));
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, import_node_path7.resolve)(opts.output);
75030
- if ((0, import_node_fs6.existsSync)(outputPath) && !opts.force) {
75031
- log(import_picocolors15.default.red("\u2717") + " " + outputPath + " already exists");
75032
- log(import_picocolors15.default.dim(" pass --force to overwrite"));
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, import_node_fs6.writeFileSync)(outputPath, content, "utf8");
75789
+ (0, import_node_fs8.writeFileSync)(outputPath, content, "utf8");
75037
75790
  } catch (err) {
75038
- log(import_picocolors15.default.red("\u2717 Could not write " + outputPath + ": ") + err.message);
75791
+ log(import_picocolors17.default.red("\u2717 Could not write " + outputPath + ": ") + err.message);
75039
75792
  return 1;
75040
75793
  }
75041
- log(import_picocolors15.default.green("\u2713") + " Saved postmortem to " + outputPath);
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(import_picocolors15.default.red("postmortem failed: " + message));
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 import_picocolors16 = __toESM(require_picocolors(), 1);
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(import_picocolors16.default.red("No investigation found: " + id));
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(import_picocolors16.default.red("Investigation " + id + " has no stored report."));
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(import_picocolors16.default.yellow(`[ai] ${result.warning}`));
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 import_picocolors17 = __toESM(require_picocolors(), 1);
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(import_picocolors17.default.red("No investigation found: " + id));
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(import_picocolors17.default.red("Investigation " + id + " has no stored report."));
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
- const answer = answerQuestion(report, directive);
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(import_picocolors17.default.red("No investigation found: " + id));
75967
+ console.error(import_picocolors19.default.red("No investigation found: " + id));
75180
75968
  return 1;
75181
75969
  }
75182
- console.error(import_picocolors17.default.red(err instanceof Error ? err.message : String(err)));
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 import_picocolors18 = __toESM(require_picocolors(), 1);
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(import_picocolors18.default.red(err.message));
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
- import_picocolors18.default.red("Source-intelligence host unreachable \u2014 run: horus index")
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(import_picocolors18.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors18.default.red(err.message));
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 import_picocolors19 = __toESM(require_picocolors(), 1);
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
- import_picocolors19.default.red(
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(import_picocolors19.default.red(err.message));
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
- import_picocolors19.default.red("Source-intelligence host unreachable \u2014 run: horus index")
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(import_picocolors19.default.red(err.message));
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 import_picocolors20 = __toESM(require_picocolors(), 1);
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 import_picocolors20.default.red(text2);
75357
- if (level === "warn") return import_picocolors20.default.yellow(text2);
75358
- return import_picocolors20.default.dim(text2);
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(import_picocolors20.default.red(err.message));
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
- import_picocolors20.default.red(
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(import_picocolors20.default.red(`Elasticsearch unreachable: ${health.detail}`));
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
- // raw mode dumps log lines (searchLogs); only the default path calls
75390
- // analyzeErrors() which requires aggregatable event-code and service fields.
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(import_picocolors20.default.yellow(`[warn] ${w.field}: ${w.message}`));
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(import_picocolors20.default.red("Elasticsearch field mapping is incompatible with the index:"));
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(import_picocolors20.default.red(` ${e.field}: ${e.message}`));
76187
+ console.error(import_picocolors22.default.red(` ${e.field}: ${e.message}`));
75402
76188
  }
75403
76189
  console.error(
75404
- import_picocolors20.default.dim("Fix fields.* overrides in your connector config or choose the correct preset.")
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(import_picocolors20.default.dim("[warn] Elasticsearch compatibility check unavailable \u2014 proceeding."));
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(import_picocolors20.default.dim("No logs matched."));
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
- import_picocolors20.default.bold(`Error analysis`) + import_picocolors20.default.dim(
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(import_picocolors20.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
76358
+ console.log(import_picocolors22.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
75511
76359
  if (broadeningNote) {
75512
- console.log(import_picocolors20.default.yellow(` ${broadeningNote}`));
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 ${import_picocolors20.default.yellow(String(analysis.newSignatures.length))} new`
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(import_picocolors20.default.dim(" No error-level logs in the window."));
76367
+ console.log(import_picocolors22.default.dim(" No error-level logs in the window."));
75520
76368
  console.log(
75521
- import_picocolors20.default.dim(
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(import_picocolors20.default.dim(` Tip: use --since to widen the window (e.g. --since 30d).`));
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(import_picocolors20.default.red("NEW"));
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(import_picocolors20.default.yellow(`spike x${s.ratio.toFixed(1)}`));
76380
+ tags.push(import_picocolors22.default.yellow(`spike x${s.ratio.toFixed(1)}`));
75533
76381
  }
75534
- const svc = s.services.length > 0 ? import_picocolors20.default.dim(` \xB7 ${s.services.slice(0, 3).join(", ")}`) : "";
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
- ` ${import_picocolors20.default.red(String(s.count).padStart(6))} ${import_picocolors20.default.bold(s.key.padEnd(14).slice(0, 14))} ` + import_picocolors20.default.dim(`first ${shortTs(s.firstSeen)} \xB7 last ${shortTs(s.lastSeen)}`) + svc + tagStr
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(` ${import_picocolors20.default.bold("Affected services:")} ${analysis.affectedServices.join(", ")}`);
76390
+ console.log(` ${import_picocolors22.default.bold("Affected services:")} ${analysis.affectedServices.join(", ")}`);
75543
76391
  }
75544
76392
  console.log("");
75545
- console.log(import_picocolors20.default.dim(" (use --raw to see individual log lines)"));
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(import_picocolors20.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors20.default.red(err.message));
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 import_picocolors21 = __toESM(require_picocolors(), 1);
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 import_picocolors21.default.red;
76469
+ return import_picocolors23.default.red;
75622
76470
  case "queue-growth":
75623
76471
  case "change":
75624
- return import_picocolors21.default.yellow;
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
- import_picocolors21.default.red(
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(import_picocolors21.default.red(`Grafana unreachable: ${health.detail}`));
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(import_picocolors21.default.dim("No series returned."));
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
- ` ${import_picocolors21.default.cyan(label || "(no labels)")} last=${import_picocolors21.default.bold(fmtNum(summary.last))} avg=${fmtNum(summary.avg)}`
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(import_picocolors21.default.dim(`No panels matched hint "${hint}".`));
76558
+ console.log(import_picocolors23.default.dim(`No panels matched hint "${hint}".`));
75711
76559
  if (tokens.length > 0) {
75712
- console.log(import_picocolors21.default.dim(` Searched for panels containing: ${tokens.join(", ")}`));
76560
+ console.log(import_picocolors23.default.dim(` Searched for panels containing: ${tokens.join(", ")}`));
75713
76561
  }
75714
76562
  console.log(
75715
- import_picocolors21.default.dim(
75716
- ` Tip: run ${import_picocolors21.default.bold("horus metrics")} (no hint) to see all available panels, then use a panel title or keyword directly.`
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(import_picocolors21.default.dim("No panels found in configured Grafana dashboards."));
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 ? import_picocolors21.default.dim(` [matched via ${matchSources.join(", ")}]`) : "";
76574
+ const matchSuffix = matchSources.length > 0 ? import_picocolors23.default.dim(` [matched via ${matchSources.join(", ")}]`) : "";
75727
76575
  console.log(
75728
- import_picocolors21.default.bold(
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
- import_picocolors21.default.dim(
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(import_picocolors21.default.green(" No anomalies detected."));
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 !== "" ? ` ${import_picocolors21.default.dim(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(import_picocolors21.default.dim(` ${ok.length} panel series with no anomaly.`));
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(import_picocolors21.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors21.default.red(err.message));
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 import_picocolors22 = __toESM(require_picocolors(), 1);
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(import_picocolors22.default.red(err.message));
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
- import_picocolors22.default.red(
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(import_picocolors22.default.red(`MongoDB unreachable: ${health.detail}`));
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 ? import_picocolors22.default.dim(` (${analysis.collections.length} collections, auto-discovered)`) : "";
76696
+ const discoveryNote = analysis.autoDiscovered ? import_picocolors24.default.dim(` (${analysis.collections.length} collections, auto-discovered)`) : "";
75849
76697
  console.log(
75850
- import_picocolors22.default.bold("State analysis") + import_picocolors22.default.dim(` \u2014 ${renv.project}/${renv.env} \xB7 db ${analysis.database}`) + discoveryNote
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(import_picocolors22.default.yellow(`stale (last ${c.dateField}, ~${Math.round(c.ageHours ?? 0)}h)`));
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(import_picocolors22.default.red(`${a.count}\xD7 "${a.value}"`));
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 = ` ${import_picocolors22.default.bold(c.collection.padEnd(18).slice(0, 18))} ${import_picocolors22.default.dim(String(c.count).padStart(8) + " docs")}`;
75865
- console.log(flags.length > 0 ? `${head} ${flags.join(" ")}` : import_picocolors22.default.dim(head));
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
- import_picocolors22.default.yellow(" No collections discovered in this database.") + import_picocolors22.default.dim(" Verify the database name in your MongoDB connector config.")
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 ? ` ${import_picocolors22.default.bold(String(signals))} state signal(s) across ${analysis.collections.length} ${scope}` : import_picocolors22.default.dim(` No state anomalies across ${analysis.collections.length} ${scope}.`)
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
- import_picocolors22.default.dim(
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(import_picocolors22.default.yellow(`[ai] ${result.warning}`));
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(import_picocolors22.default.red(err.message));
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 import_picocolors23 = __toESM(require_picocolors(), 1);
75913
- var import_node_path8 = require("path");
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, import_node_path8.resolve)(opts.path) : findRepoRoot(cwd) ?? cwd;
75918
- const name = opts.name ?? (0, import_node_path8.basename)(root);
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(`${import_picocolors23.default.green("\u2713")} Initialized Horus project ${import_picocolors23.default.bold(name)}`);
75934
- console.log(import_picocolors23.default.dim(` config: ${configPath}`));
75935
- console.log(import_picocolors23.default.dim(` registered: horus investigate --name ${name} "<hint>"`));
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
- import_picocolors23.default.dim(" no source-intelligence host set \u2014 run `horus index` to analyze + host, or pass --source <url>")
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
- import_picocolors23.default.dim(" add runtime connectors (elasticsearch/mongodb/grafana) to .horus/config.json")
76790
+ import_picocolors25.default.dim(" add runtime connectors (elasticsearch/mongodb/grafana) to .horus/config.json")
75943
76791
  );
75944
76792
  console.log(
75945
- import_picocolors23.default.dim(" .horus/ is gitignored \u2014 local config and runtime state stay out of version control")
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(import_picocolors23.default.red(err.message));
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 import_picocolors24 = __toESM(require_picocolors(), 1);
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
- import_picocolors24.default.dim("No registered projects. Run `horus index` (or `horus init`) inside a repo.")
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(import_picocolors24.default.bold("Registered Horus projects"));
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(` ${import_picocolors24.default.bold(name.padEnd(22))} ${import_picocolors24.default.dim(entry2.root)}`);
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(import_picocolors24.default.dim(' horus investigate --name <name> "<hint>"'));
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 import_picocolors25 = __toESM(require_picocolors(), 1);
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(import_picocolors25.default.bold("\nHorus setup\n"));
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(` ${import_picocolors25.default.red("\u25CF")} Horus source-intelligence backend not found`);
76837
+ write(` ${import_picocolors27.default.red("\u25CF")} Horus source-intelligence backend not found`);
75990
76838
  write(
75991
- import_picocolors25.default.dim(
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
- ` ${import_picocolors25.default.yellow("\u25CF")} Horus source-intelligence backend version mismatch` + import_picocolors25.default.dim(` (installed: ${backendVersion}, required: ${PINNED_SOURCE_VERSION})`)
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
- import_picocolors25.default.dim(
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
- ` ${import_picocolors25.default.green("\u25CF")} Horus source-intelligence backend ` + import_picocolors25.default.dim(`(${backendVersion})`)
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(` ${import_picocolors25.default.green("\u25CF")} Postgres reachable ${import_picocolors25.default.dim(`(${db.schemaDetail})`)}`);
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
- import_picocolors25.default.dim(
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(` ${import_picocolors25.default.red("\u25CF")} Postgres unreachable`);
76883
+ write(` ${import_picocolors27.default.red("\u25CF")} Postgres unreachable`);
76036
76884
  write(
76037
- import_picocolors25.default.dim(
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
- ` ${import_picocolors25.default.red("\u25CF")} Source intelligence host unreachable for ${import_picocolors25.default.bold(repo.name)} ` + import_picocolors25.default.dim(`(${repoHostUrl})`)
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
- import_picocolors25.default.dim(
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
- ` ${import_picocolors25.default.yellow("\u25CF")} Source intelligence host running but ${import_picocolors25.default.bold(repo.name)} is not indexed`
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
- import_picocolors25.default.dim(
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
- ` ${import_picocolors25.default.green("\u25CF")} ${import_picocolors25.default.bold(repo.name)} \u2014 ${count} nodes indexed ` + import_picocolors25.default.dim(`(${repoHostUrl})`)
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
- import_picocolors25.default.green("Ready.") + ' Next: `cd` into a repo and run `horus index`, then `horus investigate "<hint>"`.'
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(import_picocolors25.default.yellow("Resolve the items above, then re-run `horus setup`."));
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 import_picocolors28 = __toESM(require_picocolors(), 1);
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 import_picocolors26 = __toESM(require_picocolors(), 1);
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
- `? ${import_picocolors26.default.bold(message)} ${import_picocolors26.default.dim("(\u2191\u2193 navigate \u2022 space toggle \u2022 enter confirm \u2022 type filter)")}`
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 ? import_picocolors26.default.green("\u25CF") : " ";
77026
+ const marker = isSelected ? import_picocolors28.default.green("\u25CF") : " ";
76179
77027
  const prefix = `[${marker}]`;
76180
- const label = isCursor ? import_picocolors26.default.cyan(choice) : choice;
76181
- const pointer = isCursor ? import_picocolors26.default.cyan("\u276F") : " ";
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(import_picocolors26.default.dim(" No matches"));
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(import_picocolors26.default.dim(` ${statusParts.join(" \xB7 ")}`));
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(", ") : import_picocolors26.default.dim("none");
77046
+ const summary = values2.length > 0 ? values2.join(", ") : import_picocolors28.default.dim("none");
76199
77047
  output.write(`
76200
- ? ${import_picocolors26.default.bold(message)} ${summary}
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 import_picocolors27 = __toESM(require_picocolors(), 1);
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(import_picocolors27.default.bold("\nAI providers"));
77168
+ console.log(import_picocolors29.default.bold("\nAI providers"));
76321
77169
  console.log(
76322
- import_picocolors27.default.dim(
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(import_picocolors27.default.dim(" Cloud: anthropic (Anthropic Claude API \u2014 used by `investigate --ai`)\n"));
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(import_picocolors27.default.red(`Unknown AI provider: ${provider}`) + import_picocolors27.default.dim(`
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
- import_picocolors27.default.red("No API key provided.") + import_picocolors27.default.dim("\n Pass --api-key sk-ant-\u2026 or set ANTHROPIC_API_KEY and re-run.")
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
- ${import_picocolors27.default.red("\u2717 Anthropic key check failed:")} ${probe2.detail}`);
76359
- console.error(import_picocolors27.default.dim(" Fix the key and retry, or pass --no-test to skip."));
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
- ${import_picocolors27.default.green("\u2713")} anthropic ${import_picocolors27.default.dim(`(${probe2.detail})`)}`);
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
- import_picocolors27.default.red(`${provider} CLI not found on PATH.`) + import_picocolors27.default.dim(`
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
- ${import_picocolors27.default.green("\u2713")} ${desc2?.displayName ?? provider} ${import_picocolors27.default.dim("detected on PATH")}`);
77225
+ ${import_picocolors29.default.green("\u2713")} ${desc2?.displayName ?? provider} ${import_picocolors29.default.dim("detected on PATH")}`);
76378
77226
  console.log(
76379
- import_picocolors27.default.dim(
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
- ${import_picocolors27.default.green("\u2713")} ${import_picocolors27.default.bold("ai")} provider saved \u2192 ${import_picocolors27.default.dim(configPath)}`);
76398
- console.log(import_picocolors27.default.dim(` provider: ${provider}`));
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(import_picocolors27.default.dim(` anthropic key: ${redactKey(storedKey)} \u2192 ${secretsPathStr} (gitignored)`));
77248
+ console.log(import_picocolors29.default.dim(` anthropic key: ${redactKey(storedKey)} \u2192 ${secretsPathStr} (gitignored)`));
76401
77249
  }
76402
- console.log(import_picocolors27.default.dim(' run: horus investigate "<hint>" --ai'));
77250
+ console.log(import_picocolors29.default.dim(' run: horus investigate "<hint>" --ai'));
76403
77251
  return 0;
76404
77252
  } catch (err) {
76405
- console.error(import_picocolors27.default.red(err instanceof Error ? err.message : String(err)));
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
- import_picocolors28.default.red(`Unknown connector type: ${type}`) + import_picocolors28.default.dim(`
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
- ${import_picocolors28.default.red(`\u2717 Could not reach ${connectorType}:`)} ${probeResult.detail}` + import_picocolors28.default.dim("\n Fix the connection and retry, or pass --no-test to skip.")
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
- ${import_picocolors28.default.green("\u2713")} ${connectorType} reachable ${import_picocolors28.default.dim(`(${probeResult.detail})`)}`
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
- import_picocolors28.default.red(".horus/config.json is already tracked by Git.") + "\nStoring credentials here would expose them in the repository.\n" + import_picocolors28.default.dim(
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
- `${import_picocolors28.default.green("\u2713")} ${import_picocolors28.default.bold(connectorType)} connector saved \u2192 ${import_picocolors28.default.dim(configPath)}`
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(import_picocolors28.default.dim(`
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(import_picocolors28.default.red("Cancelled."));
77347
+ console.error(import_picocolors30.default.red("Cancelled."));
76500
77348
  return 1;
76501
77349
  }
76502
- console.error(import_picocolors28.default.red(err.message));
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
- ${import_picocolors28.default.bold(`Connect ${type}`)} ${import_picocolors28.default.dim("(press Enter to skip optional fields)")}
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
- import_picocolors28.default.dim(
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 ? import_picocolors28.default.dim(` (${placeholder})`) : "";
76605
- const suffix = required ? "" : import_picocolors28.default.dim(" [optional]");
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}${import_picocolors28.default.dim(" [optional]")}: `);
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(import_picocolors28.default.dim("\n Scanning DBs 0-15 (read-only, sampled)\u2026"));
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(import_picocolors28.default.dim(" No populated DBs found."));
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(` ${import_picocolors28.default.bold(`DB ${p.db}`)} ${import_picocolors28.default.dim("\xB7 " + describeProbe(p))}`);
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(import_picocolors28.default.dim(lines.join("\n")));
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(` ${import_picocolors28.default.dim(`[${i + 1}]`)} ${name}`);
77728
+ console.log(` ${import_picocolors30.default.dim(`[${i + 1}]`)} ${name}`);
76881
77729
  });
76882
77730
  if (indices.length > MAX_DISPLAY) {
76883
77731
  console.log(
76884
- import_picocolors28.default.dim(
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 ? import_picocolors28.default.dim(` (${d.folderTitle})`) : "";
76929
- console.log(` ${import_picocolors28.default.dim(`[${i + 1}]`)} ${d.title}${folder}`);
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(import_picocolors28.default.dim(` \u2026 and ${dashboards.length - MAX_DISPLAY} more`));
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 import_node_fs7 = require("fs");
77831
+ var import_node_fs9 = require("fs");
76984
77832
  var import_node_util4 = require("util");
76985
- var import_node_path9 = require("path");
76986
- var import_picocolors29 = __toESM(require_picocolors(), 1);
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)(import_node_fs7.unlink);
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(import_picocolors29.default.dim("No source-intelligence host found for this repo (.horus/source/host.json absent)."));
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(import_picocolors29.default.red(err.message));
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(import_picocolors29.default.dim(`Host ${hostUrl} is already stopped.`));
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(import_picocolors29.default.red(`Cannot determine port from host URL: ${hostUrl}`));
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
- import_picocolors29.default.red(
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(import_picocolors29.default.red(`Ownership record is malformed: ${recordError}. Aborting for safety.`));
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
- import_picocolors29.default.red(
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
- import_picocolors29.default.red(
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(import_picocolors29.default.dim(`Process pid ${spawned.pid} is no longer running \u2014 already stopped.`));
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, import_node_path9.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
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
- import_picocolors29.default.red(
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(import_picocolors29.default.red(`Could not read elapsed time for pid ${spawned.pid}. Aborting for safety.`));
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
- import_picocolors29.default.red(
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(import_picocolors29.default.dim(`Process pid ${spawned.pid} exited before signal \u2014 already stopped.`));
77942
+ console.log(import_picocolors31.default.dim(`Process pid ${spawned.pid} exited before signal \u2014 already stopped.`));
77095
77943
  } else {
77096
- console.error(import_picocolors29.default.red(`Failed to signal pid ${spawned.pid}: ${err.message}`));
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
- import_picocolors29.default.red(
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
- `${import_picocolors29.default.green("\u2713")} Stopped source-intelligence host ` + import_picocolors29.default.dim(`(pid ${spawned.pid}, port ${port})`) + ` for ${root}`
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, import_node_path9.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
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(import_picocolors29.default.dim("No registered projects."));
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 ${import_picocolors29.default.bold(name)} ${import_picocolors29.default.dim(`(${hostUrl})`)}`);
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(import_picocolors29.default.dim("No running source-intelligence hosts found."));
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 ? import_picocolors29.default.red(`, ${failed} failed`) : ""}.`
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 import_picocolors30 = __toESM(require_picocolors(), 1);
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(import_picocolors30.default.dim("No registered projects. Run `horus index` in a repo first."));
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(import_picocolors30.default.dim("No source-intelligence hosts found. Run `horus index` to start one."));
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 ? import_picocolors30.default.green("\u25CF running") : import_picocolors30.default.red("\u25CF stopped");
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} ${import_picocolors30.default.bold(row.name.padEnd(24))} port ${String(port).padEnd(6)} ${import_picocolors30.default.dim(row.root)}`
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(` ${import_picocolors30.default.dim("\u25CB no host")} ${import_picocolors30.default.dim(row.name)}`);
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
- import_picocolors30.default.dim(
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 import_picocolors31 = __toESM(require_picocolors(), 1);
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 import_picocolors31.default.green("\u2713");
77269
- if (status === "warn") return import_picocolors31.default.yellow("~");
77270
- return import_picocolors31.default.red("\u2717");
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(import_picocolors31.default.bold("\nHorus readiness check\n"));
78345
+ write(import_picocolors33.default.bold("\nHorus readiness check\n"));
77498
78346
  for (const check of checks) {
77499
- write(` ${mark2(check.status)} ${import_picocolors31.default.bold(check.label.padEnd(26))} ${import_picocolors31.default.dim(check.detail)}`);
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(` ${import_picocolors31.default.dim("\u2192 " + check.next)}`);
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 import_picocolors32 = __toESM(require_picocolors(), 1);
78359
+ var import_picocolors34 = __toESM(require_picocolors(), 1);
77512
78360
  function statusMark(status) {
77513
- if (status === "ready") return import_picocolors32.default.green("\u2713");
77514
- if (status === "installed") return import_picocolors32.default.yellow("~");
77515
- return import_picocolors32.default.red("\u2717");
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 import_picocolors32.default.green("ready");
77519
- if (status === "installed") return import_picocolors32.default.yellow("installed (not configured)");
77520
- return import_picocolors32.default.dim("not found on PATH");
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(import_picocolors32.default.bold("\nLocal AI providers\n"));
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(` ${import_picocolors32.default.dim("\u2192 " + result.detail)}`);
78393
+ write(` ${import_picocolors34.default.dim("\u2192 " + result.detail)}`);
77546
78394
  }
77547
78395
  }
77548
78396
  write("");
77549
- write(import_picocolors32.default.bold("Cloud AI providers\n"));
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
- ` ${import_picocolors32.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.green("configured (local .horus config)")}`
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
- ` ${import_picocolors32.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.green("configured (ANTHROPIC_API_KEY env)")}`
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
- ` ${import_picocolors32.default.red("\u2717")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.dim("not configured")}`
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
- ` ${import_picocolors32.default.dim("\u2192 run `horus connect ai` (or set ANTHROPIC_API_KEY) to enable `horus investigate --ai`")}`
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 import_node_fs8 = require("fs");
77585
- var import_node_path10 = require("path");
77586
- var import_picocolors33 = __toESM(require_picocolors(), 1);
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, import_node_path10.resolve)(cwd, opts.out ?? defaultOut);
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, import_node_fs8.existsSync)(outPath) && !opts.force) {
77638
- log(`${import_picocolors33.default.red("\u2717")} ${outPath} already exists`);
77639
- log(import_picocolors33.default.dim(" pass --force to overwrite"));
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, import_node_fs8.mkdirSync)((0, import_node_path10.dirname)(outPath), { recursive: true });
77644
- (0, import_node_fs8.writeFileSync)(outPath, configTemplate(name, repoPath), "utf8");
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(`${import_picocolors33.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
78494
+ log(`${import_picocolors35.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
77647
78495
  return 1;
77648
78496
  }
77649
- log(`${import_picocolors33.default.green("\u2713")} Created ${outPath}`);
77650
- log(import_picocolors33.default.dim(` project: ${name}`));
77651
- log(import_picocolors33.default.dim(` repo: ${repoPath}`));
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(import_picocolors33.default.yellow("Note:") + ` an initialized Horus project config exists at ${localConfigPath2}`);
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(import_picocolors33.default.dim(` next: review ${outPath} and copy/adapt it as needed`));
78505
+ log(import_picocolors35.default.dim(` next: review ${outPath} and copy/adapt it as needed`));
77658
78506
  } else {
77659
- log(import_picocolors33.default.dim(` next: horus doctor --config ${outPath}`));
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 import_picocolors34 = __toESM(require_picocolors(), 1);
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 import_picocolors34.default.green("\u2713");
77690
- if (status === "warn") return import_picocolors34.default.yellow("~");
77691
- return import_picocolors34.default.red("\u2717");
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(import_picocolors34.default.bold("\nHorus release readiness\n"));
77847
- write(import_picocolors34.default.bold(" Blocking"));
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)} ${import_picocolors34.default.bold(check.label.padEnd(22))} ${import_picocolors34.default.dim(check.detail)}`);
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(` ${import_picocolors34.default.dim("\u2192 " + check.next)}`);
78699
+ write(` ${import_picocolors36.default.dim("\u2192 " + check.next)}`);
77852
78700
  }
77853
78701
  }
77854
78702
  write("");
77855
- write(import_picocolors34.default.bold(" Optional"));
78703
+ write(import_picocolors36.default.bold(" Optional"));
77856
78704
  for (const check of optionalChecks) {
77857
- write(` ${mark3(check.status)} ${import_picocolors34.default.bold(check.label.padEnd(22))} ${import_picocolors34.default.dim(check.detail)}`);
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(` ${import_picocolors34.default.dim("\u2192 " + check.next)}`);
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(import_picocolors34.default.green(" Ready for demo/release."));
78715
+ write(import_picocolors36.default.green(" Ready for demo/release."));
77868
78716
  } else {
77869
78717
  write(
77870
- import_picocolors34.default.yellow(
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
- import_picocolors34.default.red(
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(import_picocolors34.default.dim(" Re-run `horus readiness` after resolving the items above."));
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(import_picocolors34.default.yellow(`[ai] ${result.warning}`));
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 import_node_fs9 = require("fs");
77905
- var import_node_path11 = require("path");
77906
- var import_node_os2 = require("os");
77907
- var import_picocolors35 = __toESM(require_picocolors(), 1);
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, import_node_os2.homedir)() : opts.cwd ?? process.cwd();
77914
- if (target === "claude") {
77915
- return (0, import_node_path11.join)(base2, ".claude", "skills", "horus.md");
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 Incident Investigation Skill
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
- When investigating a production issue in a repo with Horus configured, use Horus before guessing.
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
- ## Getting started
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 explain <symbol-or-hint>
77939
- horus investigate "<incident hint>"
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
- - Treat Horus evidence as grounded but not infallible.
77974
- - Mention when Horus evidence is missing, stale, ambient, or not structurally linked.
77975
- - Do not invent runtime evidence Horus did not collect.
77976
- - Prefer exact source locations from Horus over guesses.
77977
- - If Horus output conflicts with filesystem or source inspection, report the discrepancy.
77978
- - For write or fix tasks, use Horus to validate blast radius and rerun relevant checks after changes.
77979
- - Horus is read-only evidence collection \u2014 never use Horus to mutate production systems.
77980
- - Do not hallucinate evidence. If Horus returns nothing, say so explicitly.
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
- var CLAUDE_SUFFIX = `## Claude Code notes
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
- This skill is loaded automatically when present at \`.claude/skills/horus.md\`.
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
- if (target === "claude") {
77993
- return BASE_SKILL + "\n" + CLAUDE_SUFFIX;
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(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
78001
- log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
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 ((0, import_node_fs9.existsSync)(outPath) && !opts.force) {
78007
- log(`${import_picocolors35.default.red("\u2717")} ${outPath} already exists`);
78008
- log(import_picocolors35.default.dim(" pass --force to overwrite"));
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, import_node_fs9.mkdirSync)((0, import_node_path11.dirname)(outPath), { recursive: true });
78013
- (0, import_node_fs9.writeFileSync)(outPath, content, "utf8");
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(`${import_picocolors35.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
79111
+ log(`${import_picocolors37.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
78016
79112
  return 1;
78017
79113
  }
78018
- log(`${import_picocolors35.default.green("\u2713")} Horus skill installed \u2192 ${outPath}`);
79114
+ log(`${import_picocolors37.default.green("\u2713")} Horus skill installed \u2192 ${outPath}`);
78019
79115
  if (target === "claude") {
78020
- log(import_picocolors35.default.dim(" Claude Code will load this skill automatically."));
78021
- log(import_picocolors35.default.dim(" Invoke it with: /horus"));
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(import_picocolors35.default.dim(` Copy or reference this file in your ${target} agent instructions.`));
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(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
78031
- log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
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(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
78041
- log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
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 import_node_fs10 = require("fs");
78052
- var import_node_path12 = require("path");
78053
- var import_node_os3 = require("os");
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 import_picocolors36 = __toESM(require_picocolors(), 1);
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, import_node_fs10.realpathSync)(raw);
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
- ${import_picocolors36.default.bold("Horus update")}
79198
+ ${import_picocolors38.default.bold("Horus update")}
78097
79199
  `);
78098
- write(` Current version: ${import_picocolors36.default.cyan(currentVersion)}`);
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(` ${import_picocolors36.default.red("\u2717")} Could not reach GitHub: ${e.message}`);
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: ${import_picocolors36.default.cyan(latest)}`);
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
- ${import_picocolors36.default.yellow("\u2192")} ${import_picocolors36.default.bold(latest)} is available.`);
78113
- write(` Run ${import_picocolors36.default.bold("horus update")} to upgrade.`);
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
- ${import_picocolors36.default.green("\u2713")} Already on the latest version.`);
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
- ${import_picocolors36.default.green("\u2713")} Already on the latest version.`);
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(` ${import_picocolors36.default.red("\u2717")} Release ${tag} has no binary asset named '${assetName}'.`);
78132
- write(` Fallback: ${import_picocolors36.default.dim("curl -fsSL https://horus.sh/install.sh | bash")}`);
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(` ${import_picocolors36.default.red("\u2717")} Download failed: ${binRes.status}`);
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(` ${import_picocolors36.default.red("\u2717")} Checksum mismatch \u2014 download may be corrupt.`);
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(` ${import_picocolors36.default.green("\u2713")} Checksum verified.`);
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(` ${import_picocolors36.default.red("\u2717")} ${e.message}`);
79270
+ write(` ${import_picocolors38.default.red("\u2717")} ${e.message}`);
78169
79271
  return 1;
78170
79272
  }
78171
- if (!(0, import_node_fs10.existsSync)(binaryPath)) {
78172
- write(` ${import_picocolors36.default.red("\u2717")} Binary not found at ${binaryPath}`);
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, import_node_path12.join)((0, import_node_os3.tmpdir)(), `horus-update-${tag}-${process.pid}`);
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, import_node_fs10.writeFileSync)(tmpPath, binBuf, { mode: 493 });
79280
+ (0, import_node_fs12.writeFileSync)(tmpPath, binBuf, { mode: 493 });
78179
79281
  } catch (e) {
78180
- write(` ${import_picocolors36.default.red("\u2717")} Could not write to temp dir: ${e.message}`);
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, import_node_fs10.renameSync)(binaryPath, backupPath);
79286
+ (0, import_node_fs12.renameSync)(binaryPath, backupPath);
78185
79287
  try {
78186
- (0, import_node_fs10.renameSync)(tmpPath, binaryPath);
78187
- (0, import_node_fs10.chmodSync)(binaryPath, 493);
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, import_node_fs10.renameSync)(backupPath, binaryPath);
79292
+ (0, import_node_fs12.renameSync)(backupPath, binaryPath);
78191
79293
  } catch {
78192
79294
  }
78193
79295
  throw e;
78194
79296
  }
78195
79297
  try {
78196
- (0, import_node_fs10.unlinkSync)(backupPath);
79298
+ (0, import_node_fs12.unlinkSync)(backupPath);
78197
79299
  } catch {
78198
79300
  }
78199
79301
  } catch (e) {
78200
79302
  try {
78201
- (0, import_node_fs10.unlinkSync)(tmpPath);
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(` ${import_picocolors36.default.red("\u2717")} Could not replace binary at ${binaryPath}: ${msg}`);
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
- ` ${import_picocolors36.default.dim(`sudo curl -fsSL https://github.com/meritt-dev/horus/releases/download/${tag}/${assetName} -o ${binaryPath} && sudo chmod +x ${binaryPath}`)}`
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(` ${import_picocolors36.default.green("\u2713")} Updated: ${import_picocolors36.default.bold(currentVersion)} \u2192 ${import_picocolors36.default.bold(latest)}`);
78217
- write(` ${import_picocolors36.default.dim(binaryPath)}`);
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 \u2014 loaded automatically)
78539
- codex \u2192 .horus/skills/horus-codex.md
78540
- gemini \u2192 .horus/skills/horus-gemini.md
78541
- cursor \u2192 .horus/skills/horus-cursor.md
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) {