@merittdev/horus 0.2.2 → 0.3.0

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 +1569 -708
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -23435,9 +23435,9 @@ var require_client_metadata = __commonJS({
23435
23435
  throw new error_1.MongoInvalidArgumentError("Unable to include driverInfo name and version, metadata cannot exceed 512 bytes");
23436
23436
  }
23437
23437
  let runtimeInfo = getRuntimeInfo();
23438
- for (const { platform = "" } of driverInfoList) {
23439
- if (platform.length > 0) {
23440
- runtimeInfo = `${runtimeInfo}|${platform}`;
23438
+ for (const { platform: platform2 = "" } of driverInfoList) {
23439
+ if (platform2.length > 0) {
23440
+ runtimeInfo = `${runtimeInfo}|${platform2}`;
23441
23441
  }
23442
23442
  }
23443
23443
  if (!metadataDocument.ifItFitsItSits("platform", runtimeInfo)) {
@@ -43447,7 +43447,7 @@ var require_common3 = __commonJS({
43447
43447
  function setup(env) {
43448
43448
  createDebug.debug = createDebug;
43449
43449
  createDebug.default = createDebug;
43450
- createDebug.coerce = coerce2;
43450
+ createDebug.coerce = coerce3;
43451
43451
  createDebug.disable = disable;
43452
43452
  createDebug.enable = enable;
43453
43453
  createDebug.enabled = enabled;
@@ -43602,7 +43602,7 @@ var require_common3 = __commonJS({
43602
43602
  }
43603
43603
  return false;
43604
43604
  }
43605
- function coerce2(val) {
43605
+ function coerce3(val) {
43606
43606
  if (val instanceof Error) {
43607
43607
  return val.stack || val.message;
43608
43608
  }
@@ -57239,9 +57239,8 @@ init_cjs_shims();
57239
57239
 
57240
57240
  // ../../packages/core/src/version.ts
57241
57241
  init_cjs_shims();
57242
- var HORUS_VERSION = true ? "0.2.2" : "dev";
57243
- var PINNED_AXON_VERSION = "1.0.7";
57244
- var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
57242
+ var HORUS_VERSION = true ? "0.3.0" : "dev";
57243
+ var PINNED_SOURCE_VERSION = "1.1.1";
57245
57244
 
57246
57245
  // ../../packages/core/src/evidence.ts
57247
57246
  init_cjs_shims();
@@ -61672,7 +61671,7 @@ var horusConfigSchema = external_exports.object({
61672
61671
  projects: external_exports.array(projectSchema).default([]),
61673
61672
  axon: external_exports.object({
61674
61673
  /** Fail fast if the running Axon is not this version. */
61675
- pinnedVersion: external_exports.string().default("1.0.7")
61674
+ pinnedVersion: external_exports.string().default("1.1.1")
61676
61675
  }).default({}),
61677
61676
  database: databaseSchema,
61678
61677
  models: modelsSchema.default({}),
@@ -62052,6 +62051,73 @@ packages/core/src/git.ts @horus/core-team @horus/devex-team
62052
62051
  `.trim();
62053
62052
  var FIXTURE_CODEOWNERS_RULES = parseCodeowners(FIXTURE_CODEOWNERS);
62054
62053
 
62054
+ // ../../packages/core/src/redact.ts
62055
+ init_cjs_shims();
62056
+ var BASE_PATTERNS = [
62057
+ [/(authorization\s*[=:]\s*)(bearer\s+)[^\s,"')]+/gi, "$1$2[REDACTED]"],
62058
+ [/(authorization\s*[=:]\s*)(basic\s+)[^\s,"')]+/gi, "$1$2[REDACTED]"],
62059
+ [
62060
+ /("?(?:password|passwd|secret|token|api[_-]key|apikey|x-api-key)"?\s*[=:]\s*)"?[^"',\s)>]+/gi,
62061
+ "$1[REDACTED]"
62062
+ ],
62063
+ [/((?:cookie|set-cookie)\s*[=:]\s*)[^\s"',;>]{4,}/gi, "$1[REDACTED]"],
62064
+ [
62065
+ /((?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis(?:s)?):\/\/)[^:@/\s]+:[^@\s]+@/gi,
62066
+ "$1[REDACTED]@"
62067
+ ],
62068
+ [/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, "[REDACTED-CARD]"]
62069
+ ];
62070
+ var EXTENDED_PATTERNS = [
62071
+ // PEM private key blocks (run first — multi-line, highest risk).
62072
+ [
62073
+ /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z ]+ )?PRIVATE KEY-----/g,
62074
+ "[REDACTED-PRIVATE-KEY]"
62075
+ ],
62076
+ // JSON Web Tokens (three base64url segments starting with the `{"` header).
62077
+ [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[REDACTED-JWT]"],
62078
+ // AWS access key ids.
62079
+ [/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, "[REDACTED-AWS-KEY]"],
62080
+ // Google API keys.
62081
+ [/\bAIza[0-9A-Za-z_-]{35}\b/g, "[REDACTED-GCP-KEY]"],
62082
+ // Slack tokens.
62083
+ [/\bxox[baprs]-[0-9A-Za-z-]{10,}\b/g, "[REDACTED-SLACK-TOKEN]"],
62084
+ // GitHub tokens.
62085
+ [/\bgh[pousr]_[0-9A-Za-z]{36,}\b/g, "[REDACTED-GH-TOKEN]"],
62086
+ // Emails.
62087
+ [/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "[REDACTED-EMAIL]"],
62088
+ // IPv4 addresses.
62089
+ [/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, "[REDACTED-IP]"]
62090
+ ];
62091
+ var RESIDUAL_HIGH_RISK = [
62092
+ /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/,
62093
+ /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/,
62094
+ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/
62095
+ ];
62096
+ function apply(patterns, input) {
62097
+ let out = input;
62098
+ for (const [pattern, replacement] of patterns) {
62099
+ out = out.replace(pattern, replacement);
62100
+ }
62101
+ return out;
62102
+ }
62103
+ function redactSecrets(input) {
62104
+ return apply(BASE_PATTERNS, input);
62105
+ }
62106
+ function redactContent(input) {
62107
+ return apply(EXTENDED_PATTERNS, apply(BASE_PATTERNS, input));
62108
+ }
62109
+ function redactOrDrop(input) {
62110
+ try {
62111
+ const value = redactContent(input);
62112
+ if (RESIDUAL_HIGH_RISK.some((p) => p.test(value))) {
62113
+ return { value: null, dropped: true };
62114
+ }
62115
+ return { value, dropped: false };
62116
+ } catch {
62117
+ return { value: null, dropped: true };
62118
+ }
62119
+ }
62120
+
62055
62121
  // ../../packages/cli/src/commands/status.ts
62056
62122
  init_cjs_shims();
62057
62123
  var import_picocolors = __toESM(require_picocolors(), 1);
@@ -63135,25 +63201,8 @@ function annotateAgainstBaseline(current, baseline) {
63135
63201
  current.newSignatures = newSignatures;
63136
63202
  return current;
63137
63203
  }
63138
- var REDACTION_PATTERNS = [
63139
- // Authorization headers (Bearer / Basic / API-key tokens)
63140
- [/(authorization\s*[=:]\s*)(bearer\s+)[^\s,"')]+/gi, "$1$2[REDACTED]"],
63141
- [/(authorization\s*[=:]\s*)(basic\s+)[^\s,"')]+/gi, "$1$2[REDACTED]"],
63142
- // Standalone password/token/secret keys in KV strings, query params, JSON
63143
- [/("?(?:password|passwd|secret|token|api[_-]key|apikey|x-api-key)"?\s*[=:]\s*)"?[^"',\s)>]+/gi, "$1[REDACTED]"],
63144
- // Cookie / set-cookie header values
63145
- [/((?:cookie|set-cookie)\s*[=:]\s*)[^\s"',;>]{4,}/gi, "$1[REDACTED]"],
63146
- // Database / service connection strings with embedded credentials
63147
- [/((?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis(?:s)?):\/\/)[^:@/\s]+:[^@\s]+@/gi, "$1[REDACTED]@"],
63148
- // 16-digit card-number-like sequences (PCIDSS)
63149
- [/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, "[REDACTED-CARD]"]
63150
- ];
63151
63204
  function redactSensitiveString(s) {
63152
- let out = s;
63153
- for (const [pattern, replacement] of REDACTION_PATTERNS) {
63154
- out = out.replace(pattern, replacement);
63155
- }
63156
- return out;
63205
+ return redactSecrets(s);
63157
63206
  }
63158
63207
  function redactErrorSignature(sig) {
63159
63208
  if (sig.sampleMessage === void 0) return sig;
@@ -66196,7 +66245,7 @@ function sql(strings, ...params) {
66196
66245
  return new SQL([new StringChunk(str3)]);
66197
66246
  }
66198
66247
  sql2.raw = raw;
66199
- function join11(chunks, separator) {
66248
+ function join13(chunks, separator) {
66200
66249
  const result = [];
66201
66250
  for (const [i, chunk] of chunks.entries()) {
66202
66251
  if (i > 0 && separator !== void 0) {
@@ -66206,7 +66255,7 @@ function sql(strings, ...params) {
66206
66255
  }
66207
66256
  return new SQL(result);
66208
66257
  }
66209
- sql2.join = join11;
66258
+ sql2.join = join13;
66210
66259
  function identifier(value) {
66211
66260
  return new Name(value);
66212
66261
  }
@@ -69797,7 +69846,7 @@ var PgSelectQueryBuilderBase = class extends TypedQueryBuilder {
69797
69846
  return (table, on) => {
69798
69847
  const baseTableName = this.tableName;
69799
69848
  const tableName = getTableLikeName(table);
69800
- if (typeof tableName === "string" && this.config.joins?.some((join11) => join11.alias === tableName)) {
69849
+ if (typeof tableName === "string" && this.config.joins?.some((join13) => join13.alias === tableName)) {
69801
69850
  throw new Error(`Alias "${tableName}" is already used in this query`);
69802
69851
  }
69803
69852
  if (!this.isPartialSelect) {
@@ -70809,7 +70858,7 @@ var PgUpdateBase = class extends QueryPromise {
70809
70858
  createJoin(joinType) {
70810
70859
  return (table, on) => {
70811
70860
  const tableName = getTableLikeName(table);
70812
- if (typeof tableName === "string" && this.config.joins.some((join11) => join11.alias === tableName)) {
70861
+ if (typeof tableName === "string" && this.config.joins.some((join13) => join13.alias === tableName)) {
70813
70862
  throw new Error(`Alias "${tableName}" is already used in this query`);
70814
70863
  }
70815
70864
  if (typeof on === "function") {
@@ -70905,10 +70954,10 @@ var PgUpdateBase = class extends QueryPromise {
70905
70954
  const fromFields = this.getTableLikeFields(this.config.from);
70906
70955
  fields[tableName] = fromFields;
70907
70956
  }
70908
- for (const join11 of this.config.joins) {
70909
- const tableName2 = getTableLikeName(join11.table);
70910
- if (typeof tableName2 === "string" && !is(join11.table, SQL)) {
70911
- const fromFields = this.getTableLikeFields(join11.table);
70957
+ for (const join13 of this.config.joins) {
70958
+ const tableName2 = getTableLikeName(join13.table);
70959
+ if (typeof tableName2 === "string" && !is(join13.table, SQL)) {
70960
+ const fromFields = this.getTableLikeFields(join13.table);
70912
70961
  fields[tableName2] = fromFields;
70913
70962
  }
70914
70963
  }
@@ -77014,6 +77063,8 @@ function detectEntityFields(context, limit = 2) {
77014
77063
  }
77015
77064
  async function reseedFromLogs(hint, input, deps) {
77016
77065
  if (!deps.logs || typeof deps.logs.searchLogs !== "function") return null;
77066
+ if (!deps.code) return null;
77067
+ const code = deps.code;
77017
77068
  try {
77018
77069
  const from = logWindowFrom(input.since);
77019
77070
  const records = await deps.logs.searchLogs({
@@ -77026,7 +77077,7 @@ async function reseedFromLogs(hint, input, deps) {
77026
77077
  if (records.length === 0) return null;
77027
77078
  const candidates = [...new Set(records.map((r) => r.component).filter((c) => !!c))];
77028
77079
  for (const name of candidates) {
77029
- const syms = await deps.code.searchSymbols(name, 5);
77080
+ const syms = await code.searchSymbols(name, 5);
77030
77081
  if (syms.length > 0) {
77031
77082
  const rankedSeeds = rankSeeds(syms, [...new Set(tokenize(name))]);
77032
77083
  const first = records[0];
@@ -77077,20 +77128,21 @@ async function investigate(input, deps) {
77077
77128
  evidence2.push(ev);
77078
77129
  return ev;
77079
77130
  }
77080
- const rawSeeds = await code.searchSymbols(hint, 5);
77131
+ const degradedNoSource = !code;
77132
+ const rawSeeds = code ? await code.searchSymbols(hint, 5) : [];
77081
77133
  const hintTokens = [...new Set(tokenize(hint))];
77082
77134
  const ranked = rankSeeds(rawSeeds, hintTokens);
77083
77135
  let seeds = ranked.map((r) => r.symbol);
77084
77136
  let top = seeds[0];
77085
77137
  let logReseed = null;
77086
- if (!top) {
77138
+ if (!top && !degradedNoSource) {
77087
77139
  logReseed = await reseedFromLogs(hint, input, deps);
77088
77140
  if (logReseed !== null) {
77089
77141
  seeds = logReseed.seeds;
77090
77142
  top = seeds[0];
77091
77143
  }
77092
77144
  }
77093
- if (!top) {
77145
+ if (!top && !degradedNoSource) {
77094
77146
  const report2 = {
77095
77147
  id: globalThis.crypto.randomUUID(),
77096
77148
  input,
@@ -77114,31 +77166,6 @@ async function investigate(input, deps) {
77114
77166
  if (persistedId2) report2.id = persistedId2;
77115
77167
  return report2;
77116
77168
  }
77117
- const label = top.name === "constructor" ? `${(top.id.split(":").pop() ?? "").replace(/\.constructor$/, "") || top.name} (constructor)` : top.name;
77118
- const [ctx, impact, flows] = await Promise.all([
77119
- code.context(top.id),
77120
- code.impact(top.id, 2),
77121
- code.flowsFor(top.id)
77122
- ]);
77123
- const edges = await listQueueEdges(db, { project: input.repo });
77124
- const symbolNames = /* @__PURE__ */ new Set([
77125
- top.name,
77126
- ...ctx.callers.map((s) => s.name),
77127
- ...ctx.callees.map((s) => s.name)
77128
- ]);
77129
- const queueHits = edges.filter((e) => {
77130
- const bySymbol = e.producerSymbol !== null && symbolNames.has(e.producerSymbol) || e.workerSymbol !== null && symbolNames.has(e.workerSymbol);
77131
- const byFile = e.producerFile === top.filePath || e.workerFile === top.filePath;
77132
- return bySymbol || byFile;
77133
- });
77134
- let changes = null;
77135
- if (input.since !== void 0 && looksDiffable(input.since)) {
77136
- try {
77137
- changes = await code.detectChanges({ base: input.since, compare: "HEAD" });
77138
- } catch {
77139
- changes = null;
77140
- }
77141
- }
77142
77169
  let recentChanges;
77143
77170
  if (deps.repoPath && input.since) {
77144
77171
  try {
@@ -77146,62 +77173,102 @@ async function investigate(input, deps) {
77146
77173
  } catch {
77147
77174
  }
77148
77175
  }
77149
- const seedLoc = formatSymbolLocation(top.filePath, top.startLine, top.endLine);
77150
- const seedEv = mkEv(
77151
- "symbol",
77152
- `Seed symbol ${top.name} (${seedLoc})`,
77153
- { symbol: top, snippet: ctx.snippet ?? null },
77154
- top.startLine !== void 0 && top.startLine > 0 ? { symbolId: top.id, file: top.filePath, line: top.startLine } : { symbolId: top.id, file: top.filePath }
77155
- );
77176
+ let label = input.service ?? hint;
77177
+ let seedLoc = "";
77178
+ let ctx = null;
77179
+ let impact = null;
77180
+ let flows = [];
77181
+ let seedEv = null;
77182
+ let impactEv = null;
77156
77183
  const flowEvIds = [];
77157
- for (const flow of flows) {
77158
- const ev = mkEv(
77159
- "flow",
77160
- `Flow "${flow.name}" (${flow.steps.length} step(s))`,
77161
- { flowId: flow.id, name: flow.name, steps: flow.steps.map((s) => s.name) },
77162
- { symbolId: top.id, file: top.filePath }
77163
- );
77164
- flowEvIds.push(ev.id);
77165
- }
77166
- const impactEv = mkEv(
77167
- "impact",
77168
- `Impact of ${top.name}: ${impact.affected} affected symbol(s)`,
77169
- { affected: impact.affected },
77170
- { symbolId: top.id, file: top.filePath }
77171
- );
77184
+ let queueHits = [];
77172
77185
  const queueEvByName = /* @__PURE__ */ new Map();
77173
- for (const edge of queueHits) {
77174
- const producer = edge.producerSymbol ?? "unknown-producer";
77175
- const worker = edge.workerSymbol ?? "unknown-worker";
77176
- const ev = mkEv(
77177
- "queue-edge",
77178
- `Queue "${edge.queueName}": ${producer} -> ${worker}`,
77179
- {
77180
- queueName: edge.queueName,
77181
- producerSymbol: edge.producerSymbol,
77182
- producerFile: edge.producerFile,
77183
- workerSymbol: edge.workerSymbol,
77184
- workerFile: edge.workerFile,
77185
- source: edge.source
77186
- },
77187
- { queueName: edge.queueName }
77188
- );
77189
- const list = queueEvByName.get(edge.queueName) ?? [];
77190
- list.push(ev.id);
77191
- queueEvByName.set(edge.queueName, list);
77192
- }
77186
+ let changes = null;
77193
77187
  let changeEvId = null;
77194
- if (changes) {
77195
- const addedN = changes.added.length;
77196
- const removedN = changes.removed.length;
77197
- const modifiedN = changes.modified.length;
77198
- const ev = mkEv(
77199
- "commit",
77200
- `Change range ${input.since}..HEAD: +${addedN} -${removedN} ~${modifiedN} symbol(s)`,
77201
- { added: addedN, removed: removedN, modified: modifiedN },
77202
- {}
77188
+ if (top && code) {
77189
+ label = top.name === "constructor" ? `${(top.id.split(":").pop() ?? "").replace(/\.constructor$/, "") || top.name} (constructor)` : top.name;
77190
+ [ctx, impact, flows] = await Promise.all([
77191
+ code.context(top.id),
77192
+ code.impact(top.id, 2),
77193
+ code.flowsFor(top.id)
77194
+ ]);
77195
+ let edges = [];
77196
+ try {
77197
+ edges = await listQueueEdges(db, { project: input.repo });
77198
+ } catch {
77199
+ edges = [];
77200
+ }
77201
+ const symbolNames = /* @__PURE__ */ new Set([
77202
+ top.name,
77203
+ ...ctx.callers.map((s) => s.name),
77204
+ ...ctx.callees.map((s) => s.name)
77205
+ ]);
77206
+ queueHits = edges.filter((e) => {
77207
+ const bySymbol = e.producerSymbol !== null && symbolNames.has(e.producerSymbol) || e.workerSymbol !== null && symbolNames.has(e.workerSymbol);
77208
+ const byFile = e.producerFile === top.filePath || e.workerFile === top.filePath;
77209
+ return bySymbol || byFile;
77210
+ });
77211
+ if (input.since !== void 0 && looksDiffable(input.since)) {
77212
+ try {
77213
+ changes = await code.detectChanges({ base: input.since, compare: "HEAD" });
77214
+ } catch {
77215
+ changes = null;
77216
+ }
77217
+ }
77218
+ seedLoc = formatSymbolLocation(top.filePath, top.startLine, top.endLine);
77219
+ seedEv = mkEv(
77220
+ "symbol",
77221
+ `Seed symbol ${top.name} (${seedLoc})`,
77222
+ { symbol: top, snippet: ctx.snippet ?? null },
77223
+ top.startLine !== void 0 && top.startLine > 0 ? { symbolId: top.id, file: top.filePath, line: top.startLine } : { symbolId: top.id, file: top.filePath }
77203
77224
  );
77204
- changeEvId = ev.id;
77225
+ for (const flow of flows) {
77226
+ const ev = mkEv(
77227
+ "flow",
77228
+ `Flow "${flow.name}" (${flow.steps.length} step(s))`,
77229
+ { flowId: flow.id, name: flow.name, steps: flow.steps.map((s) => s.name) },
77230
+ { symbolId: top.id, file: top.filePath }
77231
+ );
77232
+ flowEvIds.push(ev.id);
77233
+ }
77234
+ impactEv = mkEv(
77235
+ "impact",
77236
+ `Impact of ${top.name}: ${impact.affected} affected symbol(s)`,
77237
+ { affected: impact.affected },
77238
+ { symbolId: top.id, file: top.filePath }
77239
+ );
77240
+ for (const edge of queueHits) {
77241
+ const producer = edge.producerSymbol ?? "unknown-producer";
77242
+ const worker = edge.workerSymbol ?? "unknown-worker";
77243
+ const ev = mkEv(
77244
+ "queue-edge",
77245
+ `Queue "${edge.queueName}": ${producer} -> ${worker}`,
77246
+ {
77247
+ queueName: edge.queueName,
77248
+ producerSymbol: edge.producerSymbol,
77249
+ producerFile: edge.producerFile,
77250
+ workerSymbol: edge.workerSymbol,
77251
+ workerFile: edge.workerFile,
77252
+ source: edge.source
77253
+ },
77254
+ { queueName: edge.queueName }
77255
+ );
77256
+ const list = queueEvByName.get(edge.queueName) ?? [];
77257
+ list.push(ev.id);
77258
+ queueEvByName.set(edge.queueName, list);
77259
+ }
77260
+ if (changes) {
77261
+ const addedN = changes.added.length;
77262
+ const removedN = changes.removed.length;
77263
+ const modifiedN = changes.modified.length;
77264
+ const ev = mkEv(
77265
+ "commit",
77266
+ `Change range ${input.since}..HEAD: +${addedN} -${removedN} ~${modifiedN} symbol(s)`,
77267
+ { added: addedN, removed: removedN, modified: modifiedN },
77268
+ {}
77269
+ );
77270
+ changeEvId = ev.id;
77271
+ }
77205
77272
  }
77206
77273
  if (changes === null && recentChanges !== void 0 && recentChanges.commits.length > 0) {
77207
77274
  const { commits, changedFiles, totalInsertions, totalDeletions } = recentChanges;
@@ -77235,10 +77302,8 @@ async function investigate(input, deps) {
77235
77302
  const from = logWindowFrom(input.since);
77236
77303
  analysis = await deps.logs.analyzeErrors({ service: input.service, from });
77237
77304
  logsCollected = true;
77238
- const seedBase = top.filePath.split("/").pop() ?? "";
77239
- const logSeedTerms = [
77240
- .../* @__PURE__ */ new Set([...tokenize(hint), ...tokenize(top.name), ...tokenize(seedBase)])
77241
- ];
77305
+ const seedBase = top ? top.filePath.split("/").pop() ?? "" : "";
77306
+ const logSeedTerms = top ? [.../* @__PURE__ */ new Set([...tokenize(hint), ...tokenize(top.name), ...tokenize(seedBase)])] : [...new Set(tokenize(hint))];
77242
77307
  for (const s of analysis.signatures.slice(0, 15)) {
77243
77308
  const { relevanceClass, relevanceReason } = classifyLogRelevance(
77244
77309
  s.key,
@@ -77486,10 +77551,10 @@ async function investigate(input, deps) {
77486
77551
  }
77487
77552
  }
77488
77553
  let ownershipEstimate = null;
77489
- if (deps.repoPath) {
77554
+ if (deps.repoPath && top && code) {
77490
77555
  try {
77491
77556
  ownershipEstimate = await estimateOwnership(top.name, {
77492
- code: deps.code,
77557
+ code,
77493
77558
  repoPath: deps.repoPath,
77494
77559
  symbol: top
77495
77560
  });
@@ -77515,14 +77580,16 @@ async function investigate(input, deps) {
77515
77580
  const validated = validateHypotheses(hyps, evidence2);
77516
77581
  const causeChains = buildCauseChains(validated, evidence2, graph, label);
77517
77582
  const findings2 = [];
77518
- findings2.push({
77519
- kind: "observation",
77520
- title: `Seed resolves to ${label} at ${seedLoc}`,
77521
- detail: top.signature ?? void 0,
77522
- confidence: 1,
77523
- evidenceIds: [seedEv.id]
77524
- });
77525
- if (logReseed !== null) {
77583
+ if (top && seedEv) {
77584
+ findings2.push({
77585
+ kind: "observation",
77586
+ title: `Seed resolves to ${label} at ${seedLoc}`,
77587
+ detail: top.signature ?? void 0,
77588
+ confidence: 1,
77589
+ evidenceIds: [seedEv.id]
77590
+ });
77591
+ }
77592
+ if (logReseed !== null && seedEv) {
77526
77593
  findings2.push({
77527
77594
  kind: "observation",
77528
77595
  title: logReseed.note,
@@ -77531,7 +77598,7 @@ async function investigate(input, deps) {
77531
77598
  evidenceIds: [seedEv.id]
77532
77599
  });
77533
77600
  }
77534
- if (ranked.length > 1) {
77601
+ if (ranked.length > 1 && seedEv) {
77535
77602
  const candidates = ranked.slice(0, 4).map((r) => `${r.symbol.name} [${r.role}]`).join(", ");
77536
77603
  findings2.push({
77537
77604
  kind: "observation",
@@ -77550,7 +77617,7 @@ async function investigate(input, deps) {
77550
77617
  evidenceIds: flowEvIds
77551
77618
  });
77552
77619
  }
77553
- if (impact.affected > 0) {
77620
+ if (top && impactEv && impact && impact.affected > 0) {
77554
77621
  findings2.push({
77555
77622
  kind: "observation",
77556
77623
  title: `Changing ${top.name} impacts ${impact.affected} symbol(s) (blast radius)`,
@@ -77701,7 +77768,7 @@ async function investigate(input, deps) {
77701
77768
  });
77702
77769
  }
77703
77770
  const causeInputs = [];
77704
- const blastRadius = impact.affected;
77771
+ const blastRadius = impact?.affected ?? 0;
77705
77772
  if (queueRuntimeState !== null) {
77706
77773
  for (const q of queueRuntimeState.queues) {
77707
77774
  const isStarved = q.waiting >= 10 && q.active === 0;
@@ -77730,26 +77797,25 @@ async function investigate(input, deps) {
77730
77797
  id: `cause:queue-path:${queueName}`,
77731
77798
  title: `The ${queueName} processing path (${producer} -> ${worker}) is implicated`,
77732
77799
  category: "queue-path",
77733
- sourceEvidenceIds: [...evIds, impactEv.id],
77800
+ sourceEvidenceIds: [...evIds, ...impactEv ? [impactEv.id] : []],
77734
77801
  baseScore: 0.35,
77735
77802
  metadata: { blastRadius }
77736
77803
  });
77737
77804
  }
77738
77805
  if (changeEvId) {
77739
77806
  const gitChangedFiles = recentChanges?.changedFiles ?? [];
77740
- const seedInChanges = gitChangedFiles.length > 0 && gitChangedFiles.some(
77741
- (f) => f === top.filePath || top.filePath.endsWith(f) || f.endsWith(top.filePath)
77742
- );
77807
+ const seedFile = top?.filePath;
77808
+ const seedInChanges = seedFile !== void 0 && gitChangedFiles.length > 0 && gitChangedFiles.some((f) => f === seedFile || seedFile.endsWith(f) || f.endsWith(seedFile));
77743
77809
  causeInputs.push({
77744
77810
  id: "cause:deployment-regression",
77745
- title: `Recent change to ${top.name} in ${input.since}..HEAD may have introduced the regression`,
77811
+ title: top ? `Recent change to ${top.name} in ${input.since}..HEAD may have introduced the regression` : `Recent changes in ${input.since}..HEAD may have introduced the regression`,
77746
77812
  category: "deployment-regression",
77747
- sourceEvidenceIds: [changeEvId, seedEv.id],
77813
+ sourceEvidenceIds: [changeEvId, ...seedEv ? [seedEv.id] : []],
77748
77814
  baseScore: clamp015((seedInChanges ? 0.45 : 0.25) + (queueHits.length > 0 ? 0.05 : 0)),
77749
77815
  metadata: { blastRadius }
77750
77816
  });
77751
77817
  }
77752
- if (impact.affected > 0) {
77818
+ if (top && impactEv && seedEv && impact && impact.affected > 0) {
77753
77819
  causeInputs.push({
77754
77820
  id: "cause:blast-radius",
77755
77821
  title: `${top.name} sits on a high-fan-out path (${impact.affected} affected) and may propagate the fault`,
@@ -77812,13 +77878,20 @@ async function investigate(input, deps) {
77812
77878
  );
77813
77879
  const seedResolved = seeds.length > 0 ? 1 : 0;
77814
77880
  const confidence = clamp015(0.5 * evidenceConfidence + 0.5 * seedResolved);
77815
- const area = ctx.community?.name ?? top.filePath;
77881
+ const area = ctx?.community?.name ?? top?.filePath ?? (input.service ?? "runtime evidence");
77816
77882
  const topCause = rankedCauses[0];
77817
- const summary = topCause ? `Investigation of "${hint}" resolved to ${label} (${area}). Top suspected cause: ${topCause.title}.` : `Investigation of "${hint}" resolved to ${label} (${area}). No dominant suspected cause emerged from the available structural evidence.`;
77883
+ const banner2 = degradedNoSource ? "Runtime-only (source intelligence unavailable). " : "";
77884
+ const scope = top ? `resolved to ${label} (${area})` : `over runtime evidence (${area})`;
77885
+ const summary = topCause ? `${banner2}Investigation of "${hint}" ${scope}. Top suspected cause: ${topCause.title}.` : `${banner2}Investigation of "${hint}" ${scope}. No dominant suspected cause emerged from the available ${degradedNoSource ? "runtime" : "structural"} evidence.`;
77818
77886
  const nextActions = buildNextActions(top, ctx, impact, queueHits, changes, input);
77887
+ if (degradedNoSource) {
77888
+ nextActions.unshift(
77889
+ "Source intelligence was unavailable \u2014 run `horus index` to enable code-aware analysis, then re-run for a full investigation."
77890
+ );
77891
+ }
77819
77892
  if (ownershipEstimate?.likelyMaintainer) {
77820
77893
  nextActions.unshift(
77821
- `Route to likely maintainer: ${ownershipEstimate.likelyMaintainer} (${Math.round(ownershipEstimate.maintainerShare * 100)}% of commits to ${ownershipEstimate.file ?? top.filePath})`
77894
+ `Route to likely maintainer: ${ownershipEstimate.likelyMaintainer} (${Math.round(ownershipEstimate.maintainerShare * 100)}% of commits to ${ownershipEstimate.file ?? top?.filePath ?? "the implicated file"})`
77822
77895
  );
77823
77896
  }
77824
77897
  const report = {
@@ -77839,7 +77912,8 @@ async function investigate(input, deps) {
77839
77912
  nextActions,
77840
77913
  ownership: ownershipEstimate,
77841
77914
  causeChains: causeChains.length > 0 ? causeChains : void 0,
77842
- ...recentChanges !== void 0 ? { recentChanges } : {}
77915
+ ...recentChanges !== void 0 ? { recentChanges } : {},
77916
+ ...degradedNoSource ? { degraded: { sourceIntelligence: false, reason: "source-intelligence host unreachable" } } : {}
77843
77917
  };
77844
77918
  const connectorFlags = deps.connectors ? {
77845
77919
  ...deps.connectors,
@@ -77869,6 +77943,7 @@ async function investigate(input, deps) {
77869
77943
  report.sourceStatus = buildRuntimeSourceStatus(evidence2, connectorFlags);
77870
77944
  const persistedId = await persist(db, input, report);
77871
77945
  if (persistedId) report.id = persistedId;
77946
+ report.persisted = persistedId !== null;
77872
77947
  if (persistedId !== null) {
77873
77948
  const tags = deriveTags(report);
77874
77949
  report.similarIncidents = await recallSimilar(db, tags, persistedId, input.repo ?? null);
@@ -77892,16 +77967,20 @@ function buildNextActions(top, _ctx, impact, queueHits, changes, input) {
77892
77967
  actions.push(`Inspect logs for worker ${worker} on queue ${edge.queueName}`);
77893
77968
  actions.push(`Check depth/failures of queue ${edge.queueName}`);
77894
77969
  }
77895
- if (impact.affected > 0) {
77970
+ if (top && impact && impact.affected > 0) {
77896
77971
  actions.push(`Review impact set of ${top.name} (${impact.affected} affected symbol(s))`);
77897
77972
  }
77898
- if (changes && input.since !== void 0) {
77899
- actions.push(`Diff recent commits touching ${top.filePath} in ${input.since}..HEAD`);
77900
- } else {
77901
- actions.push(`Diff recent commits touching ${top.filePath}`);
77973
+ if (top) {
77974
+ if (changes && input.since !== void 0) {
77975
+ actions.push(`Diff recent commits touching ${top.filePath} in ${input.since}..HEAD`);
77976
+ } else {
77977
+ actions.push(`Diff recent commits touching ${top.filePath}`);
77978
+ }
77902
77979
  }
77903
77980
  if (actions.length === 0) {
77904
- actions.push(`Inspect the source of ${top.name} at ${top.filePath}`);
77981
+ actions.push(
77982
+ top ? `Inspect the source of ${top.name} at ${top.filePath}` : input.service ? `Inspect runtime logs and metrics for service "${input.service}"` : "Inspect runtime logs and metrics for the affected service"
77983
+ );
77905
77984
  }
77906
77985
  return actions;
77907
77986
  }
@@ -80959,22 +81038,22 @@ function extractQueueGraph(input) {
80959
81038
  }
80960
81039
  }
80961
81040
  const producers = [];
80962
- for (const pc43 of input.producerClasses) {
80963
- for (const m of pc43.content.matchAll(INJECT_QUEUE_RE)) {
81041
+ for (const pc47 of input.producerClasses) {
81042
+ for (const m of pc47.content.matchAll(INJECT_QUEUE_RE)) {
80964
81043
  const queue = m[1] ?? "";
80965
- if (queue) producers.push({ queue, symbol: pc43.name, file: pc43.filePath });
81044
+ if (queue) producers.push({ queue, symbol: pc47.name, file: pc47.filePath });
80966
81045
  }
80967
- for (const m of pc43.content.matchAll(NEW_BULL_RE)) {
81046
+ for (const m of pc47.content.matchAll(NEW_BULL_RE)) {
80968
81047
  if (m[2] !== "Queue") continue;
80969
81048
  const queue = resolveArg(m[3] ?? "", constMap);
80970
81049
  if (queue) {
80971
- producers.push({ queue, symbol: (m[1] ?? pc43.name) || baseName(pc43.filePath), file: pc43.filePath });
81050
+ producers.push({ queue, symbol: (m[1] ?? pc47.name) || baseName(pc47.filePath), file: pc47.filePath });
80972
81051
  }
80973
81052
  }
80974
- for (const m of pc43.content.matchAll(QUEUE_NAME_CONST_RE)) {
81053
+ for (const m of pc47.content.matchAll(QUEUE_NAME_CONST_RE)) {
80975
81054
  const ident = m[1] ?? "";
80976
81055
  const queue = m[2] ?? "";
80977
- if (queue) producers.push({ queue, symbol: ident || baseName(pc43.filePath), file: pc43.filePath });
81056
+ if (queue) producers.push({ queue, symbol: ident || baseName(pc47.filePath), file: pc47.filePath });
80978
81057
  }
80979
81058
  }
80980
81059
  const workers = [];
@@ -90044,21 +90123,21 @@ var Protocol = class {
90044
90123
  * the error appropriately (e.g., by failing the task, logging, etc.). The Protocol layer
90045
90124
  * simply propagates the error.
90046
90125
  */
90047
- async _enqueueTaskMessage(taskId, message, sessionId) {
90126
+ async _enqueueTaskMessage(taskId, message, sessionId2) {
90048
90127
  if (!this._taskStore || !this._taskMessageQueue) {
90049
90128
  throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
90050
90129
  }
90051
90130
  const maxQueueSize = this._options?.maxTaskQueueSize;
90052
- await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
90131
+ await this._taskMessageQueue.enqueue(taskId, message, sessionId2, maxQueueSize);
90053
90132
  }
90054
90133
  /**
90055
90134
  * Clears the message queue for a task and rejects any pending request resolvers.
90056
90135
  * @param taskId The task ID whose queue should be cleared
90057
90136
  * @param sessionId Optional session ID for binding the operation to a specific session
90058
90137
  */
90059
- async _clearTaskQueue(taskId, sessionId) {
90138
+ async _clearTaskQueue(taskId, sessionId2) {
90060
90139
  if (this._taskMessageQueue) {
90061
- const messages2 = await this._taskMessageQueue.dequeueAll(taskId, sessionId);
90140
+ const messages2 = await this._taskMessageQueue.dequeueAll(taskId, sessionId2);
90062
90141
  for (const message of messages2) {
90063
90142
  if (message.type === "request" && isJSONRPCRequest(message.message)) {
90064
90143
  const requestId = message.message.id;
@@ -90101,7 +90180,7 @@ var Protocol = class {
90101
90180
  }, { once: true });
90102
90181
  });
90103
90182
  }
90104
- requestTaskStore(request, sessionId) {
90183
+ requestTaskStore(request, sessionId2) {
90105
90184
  const taskStore = this._taskStore;
90106
90185
  if (!taskStore) {
90107
90186
  throw new Error("No task store configured");
@@ -90114,18 +90193,18 @@ var Protocol = class {
90114
90193
  return await taskStore.createTask(taskParams, request.id, {
90115
90194
  method: request.method,
90116
90195
  params: request.params
90117
- }, sessionId);
90196
+ }, sessionId2);
90118
90197
  },
90119
90198
  getTask: async (taskId) => {
90120
- const task = await taskStore.getTask(taskId, sessionId);
90199
+ const task = await taskStore.getTask(taskId, sessionId2);
90121
90200
  if (!task) {
90122
90201
  throw new McpError(ErrorCode.InvalidParams, "Failed to retrieve task: Task not found");
90123
90202
  }
90124
90203
  return task;
90125
90204
  },
90126
90205
  storeTaskResult: async (taskId, status, result) => {
90127
- await taskStore.storeTaskResult(taskId, status, result, sessionId);
90128
- const task = await taskStore.getTask(taskId, sessionId);
90206
+ await taskStore.storeTaskResult(taskId, status, result, sessionId2);
90207
+ const task = await taskStore.getTask(taskId, sessionId2);
90129
90208
  if (task) {
90130
90209
  const notification = TaskStatusNotificationSchema.parse({
90131
90210
  method: "notifications/tasks/status",
@@ -90138,18 +90217,18 @@ var Protocol = class {
90138
90217
  }
90139
90218
  },
90140
90219
  getTaskResult: (taskId) => {
90141
- return taskStore.getTaskResult(taskId, sessionId);
90220
+ return taskStore.getTaskResult(taskId, sessionId2);
90142
90221
  },
90143
90222
  updateTaskStatus: async (taskId, status, statusMessage) => {
90144
- const task = await taskStore.getTask(taskId, sessionId);
90223
+ const task = await taskStore.getTask(taskId, sessionId2);
90145
90224
  if (!task) {
90146
90225
  throw new McpError(ErrorCode.InvalidParams, `Task "${taskId}" not found - it may have been cleaned up`);
90147
90226
  }
90148
90227
  if (isTerminal(task.status)) {
90149
90228
  throw new McpError(ErrorCode.InvalidParams, `Cannot update task "${taskId}" from terminal status "${task.status}" to "${status}". Terminal states (completed, failed, cancelled) cannot transition to other states.`);
90150
90229
  }
90151
- await taskStore.updateTaskStatus(taskId, status, statusMessage, sessionId);
90152
- const updatedTask = await taskStore.getTask(taskId, sessionId);
90230
+ await taskStore.updateTaskStatus(taskId, status, statusMessage, sessionId2);
90231
+ const updatedTask = await taskStore.getTask(taskId, sessionId2);
90153
90232
  if (updatedTask) {
90154
90233
  const notification = TaskStatusNotificationSchema.parse({
90155
90234
  method: "notifications/tasks/status",
@@ -90162,7 +90241,7 @@ var Protocol = class {
90162
90241
  }
90163
90242
  },
90164
90243
  listTasks: (cursor) => {
90165
- return taskStore.listTasks(cursor, sessionId);
90244
+ return taskStore.listTasks(cursor, sessionId2);
90166
90245
  }
90167
90246
  };
90168
90247
  }
@@ -90516,8 +90595,8 @@ var Server = class extends Protocol {
90516
90595
  this._serverInfo = _serverInfo;
90517
90596
  this._loggingLevels = /* @__PURE__ */ new Map();
90518
90597
  this.LOG_LEVEL_SEVERITY = new Map(LoggingLevelSchema.options.map((level, index2) => [level, index2]));
90519
- this.isMessageIgnored = (level, sessionId) => {
90520
- const currentLevel = this._loggingLevels.get(sessionId);
90598
+ this.isMessageIgnored = (level, sessionId2) => {
90599
+ const currentLevel = this._loggingLevels.get(sessionId2);
90521
90600
  return currentLevel ? this.LOG_LEVEL_SEVERITY.get(level) < this.LOG_LEVEL_SEVERITY.get(currentLevel) : false;
90522
90601
  };
90523
90602
  this._capabilities = options?.capabilities ?? {};
@@ -90860,9 +90939,9 @@ var Server = class extends Protocol {
90860
90939
  * @param params
90861
90940
  * @param sessionId optional for stateless and backward compatibility
90862
90941
  */
90863
- async sendLoggingMessage(params, sessionId) {
90942
+ async sendLoggingMessage(params, sessionId2) {
90864
90943
  if (this._capabilities.logging) {
90865
- if (!this.isMessageIgnored(params.level, sessionId)) {
90944
+ if (!this.isMessageIgnored(params.level, sessionId2)) {
90866
90945
  return this.notification({ method: "notifications/message", params });
90867
90946
  }
90868
90947
  }
@@ -91667,8 +91746,8 @@ var McpServer = class {
91667
91746
  * @param params
91668
91747
  * @param sessionId optional for stateless and backward compatibility
91669
91748
  */
91670
- async sendLoggingMessage(params, sessionId) {
91671
- return this.server.sendLoggingMessage(params, sessionId);
91749
+ async sendLoggingMessage(params, sessionId2) {
91750
+ return this.server.sendLoggingMessage(params, sessionId2);
91672
91751
  }
91673
91752
  /**
91674
91753
  * Sends a resource list changed event to the client, if connected.
@@ -93719,7 +93798,7 @@ function formatAge(ms) {
93719
93798
 
93720
93799
  // ../../packages/cli/src/commands/investigate.ts
93721
93800
  init_cjs_shims();
93722
- var import_picocolors8 = __toESM(require_picocolors(), 1);
93801
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
93723
93802
 
93724
93803
  // ../../packages/cli/src/lib/cloud/investigation-sync.ts
93725
93804
  init_cjs_shims();
@@ -93796,6 +93875,379 @@ async function listCloudInvestigations(client, cfg) {
93796
93875
  return result.investigations;
93797
93876
  }
93798
93877
 
93878
+ // ../../packages/cli/src/lib/telemetry/client.ts
93879
+ init_cjs_shims();
93880
+ var import_node_crypto6 = require("crypto");
93881
+ var import_node_os3 = require("os");
93882
+
93883
+ // ../../packages/cli/src/lib/telemetry/consent.ts
93884
+ init_cjs_shims();
93885
+
93886
+ // ../../packages/cli/src/lib/telemetry/store.ts
93887
+ init_cjs_shims();
93888
+ var import_node_fs13 = require("fs");
93889
+ var import_node_crypto5 = require("crypto");
93890
+
93891
+ // ../../packages/cli/src/lib/telemetry/paths.ts
93892
+ init_cjs_shims();
93893
+ var import_node_path14 = require("path");
93894
+ var TELEMETRY_FILE = "telemetry.json";
93895
+ var TELEMETRY_QUEUE_DIR = "telemetry-queue";
93896
+ function telemetryPath() {
93897
+ return (0, import_node_path14.join)(horusHome(), TELEMETRY_FILE);
93898
+ }
93899
+ function telemetryQueueDir() {
93900
+ return (0, import_node_path14.join)(horusHome(), TELEMETRY_QUEUE_DIR);
93901
+ }
93902
+
93903
+ // ../../packages/cli/src/lib/telemetry/store.ts
93904
+ var TELEMETRY_SCHEMA_VERSION = 1;
93905
+ function nowIso() {
93906
+ return (/* @__PURE__ */ new Date()).toISOString();
93907
+ }
93908
+ function defaultTelemetryState(installId = (0, import_node_crypto5.randomUUID)()) {
93909
+ const now = nowIso();
93910
+ return {
93911
+ schemaVersion: TELEMETRY_SCHEMA_VERSION,
93912
+ installId,
93913
+ tierA: { enabled: true, noticeShownAt: null },
93914
+ tierB: { enabled: false, enabledAt: null },
93915
+ createdAt: now,
93916
+ updatedAt: now
93917
+ };
93918
+ }
93919
+ function coerce2(raw) {
93920
+ if (!raw || typeof raw !== "object") return null;
93921
+ const r = raw;
93922
+ if (typeof r.installId !== "string" || r.installId.length === 0) return null;
93923
+ const tierA = r.tierA ?? {};
93924
+ const tierB = r.tierB ?? {};
93925
+ return {
93926
+ schemaVersion: typeof r.schemaVersion === "number" ? r.schemaVersion : TELEMETRY_SCHEMA_VERSION,
93927
+ installId: r.installId,
93928
+ tierA: {
93929
+ enabled: typeof tierA.enabled === "boolean" ? tierA.enabled : true,
93930
+ noticeShownAt: typeof tierA.noticeShownAt === "string" ? tierA.noticeShownAt : null
93931
+ },
93932
+ tierB: {
93933
+ enabled: typeof tierB.enabled === "boolean" ? tierB.enabled : false,
93934
+ enabledAt: typeof tierB.enabledAt === "string" ? tierB.enabledAt : null
93935
+ },
93936
+ createdAt: typeof r.createdAt === "string" ? r.createdAt : nowIso(),
93937
+ updatedAt: typeof r.updatedAt === "string" ? r.updatedAt : nowIso()
93938
+ };
93939
+ }
93940
+ function readTelemetryState() {
93941
+ const p = telemetryPath();
93942
+ if (!(0, import_node_fs13.existsSync)(p)) return null;
93943
+ try {
93944
+ return coerce2(JSON.parse((0, import_node_fs13.readFileSync)(p, "utf8")));
93945
+ } catch {
93946
+ return null;
93947
+ }
93948
+ }
93949
+ function writeTelemetryState(state) {
93950
+ (0, import_node_fs13.mkdirSync)(horusHome(), { recursive: true });
93951
+ const p = telemetryPath();
93952
+ const tmp = `${p}.${process.pid}.tmp`;
93953
+ (0, import_node_fs13.writeFileSync)(tmp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
93954
+ try {
93955
+ (0, import_node_fs13.renameSync)(tmp, p);
93956
+ } catch (err) {
93957
+ try {
93958
+ (0, import_node_fs13.rmSync)(tmp);
93959
+ } catch {
93960
+ }
93961
+ throw err;
93962
+ }
93963
+ try {
93964
+ (0, import_node_fs13.chmodSync)(p, 384);
93965
+ } catch {
93966
+ }
93967
+ }
93968
+ function loadOrInitTelemetryState() {
93969
+ const existing = readTelemetryState();
93970
+ if (existing) return existing;
93971
+ const created = defaultTelemetryState();
93972
+ writeTelemetryState(created);
93973
+ return created;
93974
+ }
93975
+ function updateTelemetryState(mutate) {
93976
+ const state = loadOrInitTelemetryState();
93977
+ mutate(state);
93978
+ state.updatedAt = nowIso();
93979
+ writeTelemetryState(state);
93980
+ return state;
93981
+ }
93982
+ function deleteTelemetryState() {
93983
+ const p = telemetryPath();
93984
+ if ((0, import_node_fs13.existsSync)(p)) (0, import_node_fs13.rmSync)(p);
93985
+ }
93986
+
93987
+ // ../../packages/cli/src/lib/telemetry/consent.ts
93988
+ var OFF_VALUES = /* @__PURE__ */ new Set(["0", "false", "off", "no", "disable", "disabled"]);
93989
+ var ON_VALUES = /* @__PURE__ */ new Set(["1", "true", "on", "yes", "enable", "enabled", "all"]);
93990
+ var CI_ENV_VARS = [
93991
+ "CI",
93992
+ "CONTINUOUS_INTEGRATION",
93993
+ "GITHUB_ACTIONS",
93994
+ "GITLAB_CI",
93995
+ "BUILDKITE",
93996
+ "CIRCLECI",
93997
+ "TRAVIS",
93998
+ "JENKINS_URL",
93999
+ "TEAMCITY_VERSION"
94000
+ ];
94001
+ function doNotTrack(env) {
94002
+ const v = env.DO_NOT_TRACK;
94003
+ if (v == null || v === "") return false;
94004
+ return !OFF_VALUES.has(v.trim().toLowerCase());
94005
+ }
94006
+ function parseEnvFlag(value) {
94007
+ if (value == null || value === "") return void 0;
94008
+ const v = value.trim().toLowerCase();
94009
+ if (OFF_VALUES.has(v)) return "off";
94010
+ if (ON_VALUES.has(v)) return "on";
94011
+ return void 0;
94012
+ }
94013
+ function isCI(env = process.env) {
94014
+ return CI_ENV_VARS.some((key) => {
94015
+ const v = env[key];
94016
+ return v != null && v !== "" && !OFF_VALUES.has(v.trim().toLowerCase());
94017
+ });
94018
+ }
94019
+ function off(source, reason) {
94020
+ return { tierA: false, tierB: false, forcedOff: source === "env", source, reason };
94021
+ }
94022
+ function resolveConsent(opts = {}) {
94023
+ const env = opts.env ?? process.env;
94024
+ if (doNotTrack(env)) return off("env", "DO_NOT_TRACK is set");
94025
+ const envFlag = parseEnvFlag(env.HORUS_TELEMETRY);
94026
+ if (envFlag === "off") return off("env", "HORUS_TELEMETRY disables telemetry");
94027
+ const state = opts.state !== void 0 ? opts.state : readTelemetryState();
94028
+ const storedTierA = state?.tierA.enabled ?? true;
94029
+ const storedTierB = state?.tierB.enabled ?? false;
94030
+ if (isCI(env) && envFlag !== "on") {
94031
+ return {
94032
+ tierA: false,
94033
+ tierB: false,
94034
+ forcedOff: false,
94035
+ source: "ci",
94036
+ reason: "CI environment detected \u2014 telemetry off by default"
94037
+ };
94038
+ }
94039
+ if (envFlag === "on") {
94040
+ return {
94041
+ tierA: true,
94042
+ tierB: storedTierB,
94043
+ forcedOff: false,
94044
+ source: "env",
94045
+ reason: "HORUS_TELEMETRY enables usage metadata"
94046
+ };
94047
+ }
94048
+ return {
94049
+ tierA: storedTierA,
94050
+ tierB: storedTierB,
94051
+ forcedOff: false,
94052
+ source: state ? "state" : "default",
94053
+ reason: state ? "from ~/.horus/telemetry.json" : "default settings (no saved preference)"
94054
+ };
94055
+ }
94056
+ function isContentSharingEnabled(env = process.env) {
94057
+ return resolveConsent({ env }).tierB;
94058
+ }
94059
+
94060
+ // ../../packages/cli/src/lib/telemetry/spool.ts
94061
+ init_cjs_shims();
94062
+ var import_node_fs14 = require("fs");
94063
+ var import_node_path15 = require("path");
94064
+ var SPOOL_FILE = "events.jsonl";
94065
+ var MAX_SPOOL_BYTES = 256 * 1024;
94066
+ function spoolFilePath() {
94067
+ return (0, import_node_path15.join)(telemetryQueueDir(), SPOOL_FILE);
94068
+ }
94069
+ function spoolEvent(event) {
94070
+ try {
94071
+ (0, import_node_fs14.mkdirSync)(telemetryQueueDir(), { recursive: true });
94072
+ const file = spoolFilePath();
94073
+ let size2 = 0;
94074
+ try {
94075
+ size2 = (0, import_node_fs14.statSync)(file).size;
94076
+ } catch {
94077
+ }
94078
+ if (size2 >= MAX_SPOOL_BYTES) return;
94079
+ (0, import_node_fs14.appendFileSync)(file, JSON.stringify(event) + "\n", { mode: 384 });
94080
+ } catch {
94081
+ }
94082
+ }
94083
+ function readSpooledEvents() {
94084
+ try {
94085
+ const file = spoolFilePath();
94086
+ if (!(0, import_node_fs14.existsSync)(file)) return [];
94087
+ return (0, import_node_fs14.readFileSync)(file, "utf8").split("\n").filter((line2) => line2.trim().length > 0).map((line2) => {
94088
+ try {
94089
+ return JSON.parse(line2);
94090
+ } catch {
94091
+ return null;
94092
+ }
94093
+ }).filter((e) => e !== null);
94094
+ } catch {
94095
+ return [];
94096
+ }
94097
+ }
94098
+ function clearSpool() {
94099
+ try {
94100
+ const file = spoolFilePath();
94101
+ if ((0, import_node_fs14.existsSync)(file)) (0, import_node_fs14.rmSync)(file);
94102
+ } catch {
94103
+ }
94104
+ }
94105
+
94106
+ // ../../packages/cli/src/lib/telemetry/events.ts
94107
+ init_cjs_shims();
94108
+ var TELEMETRY_EVENT_SCHEMA_VERSION = 1;
94109
+ var TIER_B_EVENT_TYPES = /* @__PURE__ */ new Set(["investigation.content"]);
94110
+ function tierForEventType(type) {
94111
+ return TIER_B_EVENT_TYPES.has(type) ? "B" : "A";
94112
+ }
94113
+ function extractFlagNames(argv) {
94114
+ const names = /* @__PURE__ */ new Set();
94115
+ for (const token of argv.slice(2)) {
94116
+ if (token.startsWith("--") && token.length > 2) {
94117
+ const name = token.slice(2).split("=")[0];
94118
+ if (name) names.add(name);
94119
+ }
94120
+ }
94121
+ return [...names];
94122
+ }
94123
+
94124
+ // ../../packages/cli/src/lib/telemetry/client.ts
94125
+ var sessionId = null;
94126
+ function getSessionId() {
94127
+ if (!sessionId) sessionId = (0, import_node_crypto6.randomUUID)();
94128
+ return sessionId;
94129
+ }
94130
+ function track(input) {
94131
+ try {
94132
+ const state = readTelemetryState();
94133
+ if (!state) return;
94134
+ const tier = tierForEventType(input.type);
94135
+ const decision = resolveConsent({ state });
94136
+ if (tier === "A" ? !decision.tierA : !decision.tierB) return;
94137
+ const event = {
94138
+ schemaVersion: TELEMETRY_EVENT_SCHEMA_VERSION,
94139
+ tier,
94140
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
94141
+ installId: state.installId,
94142
+ sessionId: getSessionId(),
94143
+ cliVersion: HORUS_VERSION,
94144
+ os: (0, import_node_os3.platform)(),
94145
+ arch: (0, import_node_os3.arch)(),
94146
+ ...input
94147
+ };
94148
+ spoolEvent(event);
94149
+ } catch {
94150
+ }
94151
+ }
94152
+
94153
+ // ../../packages/cli/src/lib/telemetry/feedback.ts
94154
+ init_cjs_shims();
94155
+ var import_promises = require("readline/promises");
94156
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
94157
+ var MANUAL_MINUTES = {
94158
+ "1": 5,
94159
+ // < 5 min
94160
+ "2": 30,
94161
+ // ~30 min
94162
+ "3": 120,
94163
+ // ~2 h
94164
+ "4": 480,
94165
+ // half-day+
94166
+ "5": null
94167
+ // unsure
94168
+ };
94169
+ function parseResolved(raw) {
94170
+ const v = raw.trim().toLowerCase();
94171
+ if (v.startsWith("y")) return "yes";
94172
+ if (v.startsWith("p")) return "partly";
94173
+ if (v.startsWith("n")) return "no";
94174
+ return null;
94175
+ }
94176
+ function parseManualEstimate(raw) {
94177
+ return MANUAL_MINUTES[raw.trim()] ?? null;
94178
+ }
94179
+ function submitFeedback(input) {
94180
+ track({ type: "feedback.submitted", ...input });
94181
+ }
94182
+ async function runFeedbackPrompt(investigationId, horusSeconds) {
94183
+ const rl = (0, import_promises.createInterface)({ input: process.stdin, output: process.stdout });
94184
+ try {
94185
+ console.log(import_picocolors8.default.dim("\nQuick feedback to improve Horus (press Enter to skip):"));
94186
+ const a1 = await rl.question(
94187
+ import_picocolors8.default.bold(" Did this point you at the cause? ") + import_picocolors8.default.dim("[y / partly / n] ")
94188
+ );
94189
+ if (a1.trim() === "") return false;
94190
+ const a2 = await rl.question(
94191
+ import_picocolors8.default.bold(" Manually, finding this would have taken? ") + import_picocolors8.default.dim("[1:<5m 2:~30m 3:~2h 4:half-day+ 5:unsure] ")
94192
+ );
94193
+ submitFeedback({
94194
+ investigationId,
94195
+ resolved: parseResolved(a1),
94196
+ manualEstimateMinutes: parseManualEstimate(a2),
94197
+ horusSeconds
94198
+ });
94199
+ console.log(import_picocolors8.default.dim(" Thanks \u2014 recorded."));
94200
+ return true;
94201
+ } finally {
94202
+ rl.close();
94203
+ }
94204
+ }
94205
+ async function maybePromptFeedback(opts) {
94206
+ try {
94207
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
94208
+ if (!resolveConsent().tierA) return;
94209
+ const rate = opts.sampleRate ?? 0.25;
94210
+ const rand = (opts.random ?? Math.random)();
94211
+ if (rand > rate) return;
94212
+ await runFeedbackPrompt(opts.investigationId, opts.horusSeconds ?? null);
94213
+ } catch {
94214
+ }
94215
+ }
94216
+
94217
+ // ../../packages/cli/src/lib/ensure-host.ts
94218
+ init_cjs_shims();
94219
+ function parseHostPort(hostUrl) {
94220
+ try {
94221
+ const port = parseInt(new URL(hostUrl).port, 10);
94222
+ return Number.isNaN(port) ? null : port;
94223
+ } catch {
94224
+ return null;
94225
+ }
94226
+ }
94227
+ function ensureHostReasonHint(reason) {
94228
+ switch (reason) {
94229
+ case "source-unavailable":
94230
+ return "horus-source is not installed \u2014 install it: pip install horus-source";
94231
+ case "not-analyzed":
94232
+ return "this repo has not been indexed yet \u2014 run: horus index";
94233
+ case "bad-url":
94234
+ return "the configured source host URL is not a valid URL";
94235
+ default:
94236
+ return "the host did not become healthy \u2014 run: horus index";
94237
+ }
94238
+ }
94239
+ async function ensureSourceHost(root, hostUrl, opts = {}) {
94240
+ if (await isHostHealthy(hostUrl)) return { ok: true, hostUrl };
94241
+ if (!await sourceAvailable()) return { ok: false, reason: "source-unavailable" };
94242
+ if (!isAnalyzed(root)) return { ok: false, reason: "not-analyzed" };
94243
+ const port = parseHostPort(hostUrl);
94244
+ if (port === null) return { ok: false, reason: "bad-url" };
94245
+ startHost(root, port);
94246
+ if (await waitForHost(hostUrl, opts.timeoutMs ?? 2e4)) return { ok: true, hostUrl };
94247
+ removeSpawnedHostRecord(root);
94248
+ return { ok: false, reason: "unhealthy" };
94249
+ }
94250
+
93799
94251
  // ../../packages/cli/src/commands/investigate.ts
93800
94252
  function extractEvidenceExcerpt(e) {
93801
94253
  if (!e.payload || typeof e.payload !== "object") return void 0;
@@ -93854,37 +94306,37 @@ function renderStoredAIJudgment(judgment, write = (l) => console.log(l)) {
93854
94306
  const sep = "\u2500".repeat(60);
93855
94307
  write(`
93856
94308
  ${sep}`);
93857
- write(import_picocolors8.default.bold("AI Judgment") + import_picocolors8.default.dim(` (confidence: ${(judgment.confidence * 100).toFixed(0)}%, provider: ${judgment.provider})`));
94309
+ write(import_picocolors9.default.bold("AI Judgment") + import_picocolors9.default.dim(` (confidence: ${(judgment.confidence * 100).toFixed(0)}%, provider: ${judgment.provider})`));
93858
94310
  write(sep);
93859
94311
  if (judgment.hypothesisJudgments && judgment.hypothesisJudgments.length > 0) {
93860
- write(import_picocolors8.default.bold("Hypothesis verdicts:"));
94312
+ write(import_picocolors9.default.bold("Hypothesis verdicts:"));
93861
94313
  for (const j of judgment.hypothesisJudgments) {
93862
- const verdictColor = j.verdict === "supported" ? import_picocolors8.default.green : j.verdict === "weakened" ? import_picocolors8.default.yellow : j.verdict === "eliminated" ? import_picocolors8.default.red : import_picocolors8.default.dim;
93863
- write(` ${verdictColor(`[${j.verdict}]`)} ${j.category}` + import_picocolors8.default.dim(` (${(j.confidence * 100).toFixed(0)}%)`));
93864
- write(import_picocolors8.default.dim(` ${j.rationale}`));
94314
+ const verdictColor = j.verdict === "supported" ? import_picocolors9.default.green : j.verdict === "weakened" ? import_picocolors9.default.yellow : j.verdict === "eliminated" ? import_picocolors9.default.red : import_picocolors9.default.dim;
94315
+ write(` ${verdictColor(`[${j.verdict}]`)} ${j.category}` + import_picocolors9.default.dim(` (${(j.confidence * 100).toFixed(0)}%)`));
94316
+ write(import_picocolors9.default.dim(` ${j.rationale}`));
93865
94317
  }
93866
94318
  write("");
93867
94319
  }
93868
94320
  if (judgment.rootCauseAssessment) {
93869
94321
  const rca = judgment.rootCauseAssessment;
93870
- const uncertaintyColor = rca.uncertainty === "low" ? import_picocolors8.default.green : rca.uncertainty === "medium" ? import_picocolors8.default.yellow : import_picocolors8.default.red;
93871
- write(import_picocolors8.default.bold("Root cause assessment:") + import_picocolors8.default.dim(` uncertainty: ${uncertaintyColor(rca.uncertainty)}`));
94322
+ const uncertaintyColor = rca.uncertainty === "low" ? import_picocolors9.default.green : rca.uncertainty === "medium" ? import_picocolors9.default.yellow : import_picocolors9.default.red;
94323
+ write(import_picocolors9.default.bold("Root cause assessment:") + import_picocolors9.default.dim(` uncertainty: ${uncertaintyColor(rca.uncertainty)}`));
93872
94324
  write(rca.summary);
93873
94325
  if (rca.citedEvidenceIds.length > 0) {
93874
- write(import_picocolors8.default.dim(` Cited: ${rca.citedEvidenceIds.join(", ")}`));
94326
+ write(import_picocolors9.default.dim(` Cited: ${rca.citedEvidenceIds.join(", ")}`));
93875
94327
  }
93876
94328
  write("");
93877
94329
  }
93878
- write(import_picocolors8.default.bold("What:") + " " + judgment.what);
93879
- write(import_picocolors8.default.bold("Why:") + " " + judgment.why);
94330
+ write(import_picocolors9.default.bold("What:") + " " + judgment.what);
94331
+ write(import_picocolors9.default.bold("Why:") + " " + judgment.why);
93880
94332
  if (judgment.whereNext.length > 0) {
93881
- write(import_picocolors8.default.bold("Next steps:"));
94333
+ write(import_picocolors9.default.bold("Next steps:"));
93882
94334
  for (const step of judgment.whereNext) {
93883
94335
  write(` \u2022 ${step}`);
93884
94336
  }
93885
94337
  }
93886
94338
  if (judgment.citations.length > 0) {
93887
- write(import_picocolors8.default.dim(`
94339
+ write(import_picocolors9.default.dim(`
93888
94340
  Cited evidence: ${judgment.citations.map((c) => c.evidenceId).join(", ")}`));
93889
94341
  }
93890
94342
  }
@@ -93902,6 +94354,7 @@ function classifyAIFailure(firstError) {
93902
94354
  return firstError;
93903
94355
  }
93904
94356
  async function runInvestigate(hint, opts) {
94357
+ const startedAtMs = Date.now();
93905
94358
  try {
93906
94359
  const config2 = await loadConfig(opts.config, { name: opts.name });
93907
94360
  const projectName = opts.project ?? opts.repo;
@@ -93909,7 +94362,7 @@ async function runInvestigate(hint, opts) {
93909
94362
  try {
93910
94363
  renv = resolveEnvironment(config2, { project: projectName, env: opts.env });
93911
94364
  } catch (err) {
93912
- console.error(import_picocolors8.default.red(err.message));
94365
+ console.error(import_picocolors9.default.red(err.message));
93913
94366
  return 1;
93914
94367
  }
93915
94368
  const repoRoot = repoRootOrCwd(renv.path);
@@ -93918,30 +94371,41 @@ async function runInvestigate(hint, opts) {
93918
94371
  const cloudSession = cloudActive ? authedClient() : null;
93919
94372
  if (cloudActive && !cloudSession) {
93920
94373
  console.error(
93921
- import_picocolors8.default.red(
93922
- `This repo is linked to Horus Cloud but you are not logged in. Run ${import_picocolors8.default.bold("horus login")} first.`
94374
+ import_picocolors9.default.yellow(
94375
+ `This repo is linked to Horus Cloud but you are not logged in \u2014 running locally only. Run ${import_picocolors9.default.bold("horus login")} to also sync results to the cloud.`
93923
94376
  )
93924
94377
  );
93925
- return 1;
93926
94378
  }
93927
94379
  const code = codeForEnv(renv);
93928
94380
  if (!code) {
93929
94381
  console.error(
93930
- import_picocolors8.default.red(
94382
+ import_picocolors9.default.red(
93931
94383
  `No source-intelligence connector configured for project "${renv.project}" / env "${renv.env}".`
93932
94384
  )
93933
94385
  );
93934
94386
  return 1;
93935
94387
  }
93936
- const health = await code.health();
93937
- if (!health.ok) {
93938
- const sourceUrl = renv.repositories[0]?.sourceHostUrl ?? renv.repositories[0]?.axonHostUrl;
94388
+ const sourceUrl = renv.repositories[0]?.sourceHostUrl ?? renv.repositories[0]?.axonHostUrl;
94389
+ let health = await code.health();
94390
+ if (!health.ok && sourceUrl) {
93939
94391
  console.error(
93940
- import_picocolors8.default.red(
93941
- `Source-intelligence host unreachable for ${renv.project}/${renv.env}` + (sourceUrl ? ` (${sourceUrl}) \u2014 run: horus index` : " \u2014 run: horus index")
94392
+ import_picocolors9.default.yellow(`Source-intelligence host unreachable (${sourceUrl}) \u2014 attempting to start it\u2026`)
94393
+ );
94394
+ const healed = await ensureSourceHost(renv.path, sourceUrl);
94395
+ if (healed.ok) {
94396
+ console.error(import_picocolors9.default.green(`Source-intelligence host is up at ${healed.hostUrl}.`));
94397
+ health = await code.health();
94398
+ } else {
94399
+ console.error(import_picocolors9.default.dim(` ${ensureHostReasonHint(healed.reason)}`));
94400
+ }
94401
+ }
94402
+ const runtimeOnly = !health.ok;
94403
+ if (runtimeOnly) {
94404
+ console.error(
94405
+ import_picocolors9.default.yellow(
94406
+ `Proceeding in runtime-only mode \u2014 no source intelligence. Run ${import_picocolors9.default.bold("horus index")} for a full (code-aware) investigation.`
93942
94407
  )
93943
94408
  );
93944
- return 1;
93945
94409
  }
93946
94410
  const logs = logsForEnv(renv);
93947
94411
  const mongo = mongoForEnv(renv);
@@ -93954,7 +94418,9 @@ async function runInvestigate(hint, opts) {
93954
94418
  const report = await investigate(
93955
94419
  { hint, repo: renv.project, since: opts.since, service },
93956
94420
  {
93957
- code,
94421
+ // Runtime-only degrade (HOR-319 layer-2): pass no source provider so the engine
94422
+ // skips seed resolution + structural evidence and builds from runtime evidence.
94423
+ code: runtimeOnly ? null : code,
93958
94424
  db,
93959
94425
  logs,
93960
94426
  mongo,
@@ -93982,7 +94448,7 @@ async function runInvestigate(hint, opts) {
93982
94448
  model,
93983
94449
  ...ai.anthropicApiKey !== void 0 ? { apiKey: ai.anthropicApiKey } : {}
93984
94450
  });
93985
- console.log(import_picocolors8.default.dim(`[ai] model: ${model}`));
94451
+ console.log(import_picocolors9.default.dim(`[ai] model: ${model}`));
93986
94452
  const narrativeInput = buildNarrativeInput(report);
93987
94453
  const { output, fromProvider, degraded, validationErrors } = await renderNarrative(
93988
94454
  narrativeInput,
@@ -93998,34 +94464,79 @@ async function runInvestigate(hint, opts) {
93998
94464
  renderStoredAIJudgment(stored);
93999
94465
  } else if (degraded) {
94000
94466
  if (validationErrors && validationErrors.length > 0) {
94001
- console.error(import_picocolors8.default.yellow(`[ai] structured validation incomplete \u2014 showing raw AI narrative`));
94467
+ console.error(import_picocolors9.default.yellow(`[ai] structured validation incomplete \u2014 showing raw AI narrative`));
94002
94468
  }
94003
94469
  console.log("");
94004
- console.log(import_picocolors8.default.dim("\u2500".repeat(60)));
94005
- console.log(import_picocolors8.default.bold("AI narrative ") + import_picocolors8.default.yellow("(unstructured \u2014 not validated)"));
94470
+ console.log(import_picocolors9.default.dim("\u2500".repeat(60)));
94471
+ console.log(import_picocolors9.default.bold("AI narrative ") + import_picocolors9.default.yellow("(unstructured \u2014 not validated)"));
94006
94472
  if (output.what?.trim()) {
94007
- console.log(import_picocolors8.default.bold("\nWhat:"), output.what.trim());
94473
+ console.log(import_picocolors9.default.bold("\nWhat:"), output.what.trim());
94008
94474
  }
94009
94475
  if (output.why?.trim()) {
94010
- console.log(import_picocolors8.default.bold("\nWhy:"), output.why.trim());
94476
+ console.log(import_picocolors9.default.bold("\nWhy:"), output.why.trim());
94011
94477
  }
94012
94478
  if (output.whereNext.length > 0) {
94013
- console.log(import_picocolors8.default.bold("\nNext:"));
94479
+ console.log(import_picocolors9.default.bold("\nNext:"));
94014
94480
  for (const a of output.whereNext) console.log(` - ${a}`);
94015
94481
  }
94016
94482
  } else {
94017
94483
  const reason = classifyAIFailure(validationErrors?.[0]);
94018
- console.error(import_picocolors8.default.yellow(`[ai] fallback to deterministic \u2014 ${reason}`));
94484
+ console.error(import_picocolors9.default.yellow(`[ai] fallback to deterministic \u2014 ${reason}`));
94485
+ }
94486
+ }
94487
+ track({
94488
+ type: "investigation.completed",
94489
+ investigationId: report.id,
94490
+ confidence: typeof report.confidence === "number" ? report.confidence : null,
94491
+ evidenceCount: report.evidence?.length ?? 0,
94492
+ findingCount: report.findings?.length ?? 0,
94493
+ suspectedCauseCount: report.suspectedCauses?.length ?? 0,
94494
+ degraded: Boolean(report.degraded),
94495
+ gapCount: report.gapAnalysis?.gaps?.length ?? 0,
94496
+ hasAi: Boolean(report.aiJudgment)
94497
+ });
94498
+ if (isContentSharingEnabled()) {
94499
+ const hint2 = redactOrDrop(report.input.hint ?? "");
94500
+ if (!hint2.dropped) {
94501
+ track({
94502
+ type: "investigation.content",
94503
+ investigationId: report.id,
94504
+ hint: hint2.value ?? "",
94505
+ summary: redactContent(report.summary ?? ""),
94506
+ findingTitles: (report.findings ?? []).slice(0, 20).map((f) => redactContent(f.title ?? "")),
94507
+ suspectedCauseTitles: (report.suspectedCauses ?? []).slice(0, 20).map((c) => redactContent(c.title ?? "")),
94508
+ confidence: typeof report.confidence === "number" ? report.confidence : null
94509
+ });
94019
94510
  }
94020
94511
  }
94021
94512
  if (cloudActive && cloudSession && cloudCfg) {
94022
94513
  try {
94023
94514
  const refs = await uploadInvestigationToCloud(cloudSession.client, cloudCfg, report);
94024
- console.log(import_picocolors8.default.dim(`[cloud] investigation saved: ${refs.investigationId}`));
94515
+ console.log(import_picocolors9.default.dim(`[cloud] investigation saved: ${refs.investigationId}`));
94025
94516
  } catch (err) {
94026
94517
  return reportCloudError(err);
94027
94518
  }
94028
94519
  }
94520
+ if (format !== "json") {
94521
+ if (report.persisted === false) {
94522
+ console.error(
94523
+ import_picocolors9.default.yellow(`
94524
+ \u26A0 Results were not saved \u2014 the investigation database was unreachable.`)
94525
+ );
94526
+ console.error(
94527
+ import_picocolors9.default.dim(
94528
+ ` This report is display-only; \`horus ask\` won't find it. Start the database and re-run to persist.`
94529
+ )
94530
+ );
94531
+ } else {
94532
+ console.log(import_picocolors9.default.dim(`
94533
+ Ask a follow-up: horus ask ${report.id} "<question>"`));
94534
+ }
94535
+ await maybePromptFeedback({
94536
+ investigationId: report.id,
94537
+ horusSeconds: (Date.now() - startedAtMs) / 1e3
94538
+ });
94539
+ }
94029
94540
  } finally {
94030
94541
  await sql2.end();
94031
94542
  if (mongo) await mongo.close();
@@ -94033,14 +94544,16 @@ async function runInvestigate(hint, opts) {
94033
94544
  }
94034
94545
  return 0;
94035
94546
  } catch (err) {
94036
- console.error(import_picocolors8.default.red(err.message));
94547
+ const msg = err?.message || String(err);
94548
+ console.error(import_picocolors9.default.red(msg.trim() ? msg : "Investigation failed (unknown error)."));
94549
+ if (process.env.HORUS_DEBUG) console.error(import_picocolors9.default.dim(err?.stack ?? String(err)));
94037
94550
  return 1;
94038
94551
  }
94039
94552
  }
94040
94553
 
94041
94554
  // ../../packages/cli/src/commands/changes.ts
94042
94555
  init_cjs_shims();
94043
- var import_picocolors9 = __toESM(require_picocolors(), 1);
94556
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
94044
94557
  var CHANGES_AI_CONTRACT = `Provide a clearly separated AI change-impact review with:
94045
94558
 
94046
94559
  Highest-risk changes
@@ -94065,7 +94578,7 @@ async function runChanges(base2, compare, opts) {
94065
94578
  const health = await code.health();
94066
94579
  if (!health.ok) {
94067
94580
  console.error(
94068
- import_picocolors9.default.red("Source-intelligence host unreachable \u2014 run: horus index")
94581
+ import_picocolors10.default.red("Source-intelligence host unreachable \u2014 run: horus index")
94069
94582
  );
94070
94583
  return 1;
94071
94584
  }
@@ -94087,20 +94600,20 @@ async function runChanges(base2, compare, opts) {
94087
94600
  });
94088
94601
  console.log("\n" + renderInterpretation(result));
94089
94602
  if (!result.ok) {
94090
- console.error(import_picocolors9.default.yellow(`[ai] ${result.warning}`));
94603
+ console.error(import_picocolors10.default.yellow(`[ai] ${result.warning}`));
94091
94604
  }
94092
94605
  }
94093
94606
  }
94094
94607
  return 0;
94095
94608
  } catch (err) {
94096
- console.error(import_picocolors9.default.red(err.message));
94609
+ console.error(import_picocolors10.default.red(err.message));
94097
94610
  return 1;
94098
94611
  }
94099
94612
  }
94100
94613
 
94101
94614
  // ../../packages/cli/src/commands/timeline.ts
94102
94615
  init_cjs_shims();
94103
- var import_picocolors10 = __toESM(require_picocolors(), 1);
94616
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
94104
94617
  var DEFAULT_SINCE = "7 days ago";
94105
94618
  function resolveTimelineWindow(opts) {
94106
94619
  const usingDefault = !opts.all && opts.since === void 0;
@@ -94130,7 +94643,7 @@ async function runTimeline(service, opts) {
94130
94643
  try {
94131
94644
  renv = resolveEnvironment(config2, { project: opts.repo });
94132
94645
  } catch (err) {
94133
- console.error(import_picocolors10.default.red(err.message));
94646
+ console.error(import_picocolors11.default.red(err.message));
94134
94647
  return 1;
94135
94648
  }
94136
94649
  const { code } = createConnectors(config2);
@@ -94145,9 +94658,9 @@ async function runTimeline(service, opts) {
94145
94658
  console.log(renderChangeTimeline(t));
94146
94659
  if (usingDefaultWindow) {
94147
94660
  console.log(
94148
- import_picocolors10.default.dim(
94661
+ import_picocolors11.default.dim(
94149
94662
  `
94150
- Showing the last 7 days (default). Widen with ${import_picocolors10.default.bold('--since "30 days ago"')}, pin a range with ${import_picocolors10.default.bold("--since <when> --until <when>")}, or see everything with ${import_picocolors10.default.bold("--all")}.`
94663
+ Showing the last 7 days (default). Widen with ${import_picocolors11.default.bold('--since "30 days ago"')}, pin a range with ${import_picocolors11.default.bold("--since <when> --until <when>")}, or see everything with ${import_picocolors11.default.bold("--all")}.`
94151
94664
  )
94152
94665
  );
94153
94666
  }
@@ -94164,20 +94677,20 @@ async function runTimeline(service, opts) {
94164
94677
  });
94165
94678
  console.log("\n" + renderInterpretation(result));
94166
94679
  if (!result.ok) {
94167
- console.error(import_picocolors10.default.yellow(`[ai] ${result.warning}`));
94680
+ console.error(import_picocolors11.default.yellow(`[ai] ${result.warning}`));
94168
94681
  }
94169
94682
  }
94170
94683
  }
94171
94684
  return 0;
94172
94685
  } catch (err) {
94173
- console.error(import_picocolors10.default.red(err.message));
94686
+ console.error(import_picocolors11.default.red(err.message));
94174
94687
  return 1;
94175
94688
  }
94176
94689
  }
94177
94690
 
94178
94691
  // ../../packages/cli/src/commands/what-changed.ts
94179
94692
  init_cjs_shims();
94180
- var import_picocolors11 = __toESM(require_picocolors(), 1);
94693
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
94181
94694
  var DEFAULT_SINCE2 = "7 days ago";
94182
94695
  var WHAT_CHANGED_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
94183
94696
 
@@ -94201,7 +94714,7 @@ async function runWhatChanged(service, opts) {
94201
94714
  try {
94202
94715
  renv = resolveEnvironment(config2, { project: opts.repo });
94203
94716
  } catch (err) {
94204
- console.error(import_picocolors11.default.red(err.message));
94717
+ console.error(import_picocolors12.default.red(err.message));
94205
94718
  return 1;
94206
94719
  }
94207
94720
  const { code } = createConnectors(config2);
@@ -94224,19 +94737,19 @@ async function runWhatChanged(service, opts) {
94224
94737
  });
94225
94738
  console.log("\n" + renderInterpretation(result));
94226
94739
  if (!result.ok) {
94227
- console.error(import_picocolors11.default.yellow(`[ai] ${result.warning}`));
94740
+ console.error(import_picocolors12.default.yellow(`[ai] ${result.warning}`));
94228
94741
  }
94229
94742
  }
94230
94743
  return 0;
94231
94744
  } catch (err) {
94232
- console.error(import_picocolors11.default.red(err.message));
94745
+ console.error(import_picocolors12.default.red(err.message));
94233
94746
  return 1;
94234
94747
  }
94235
94748
  }
94236
94749
 
94237
94750
  // ../../packages/cli/src/commands/architecture.ts
94238
94751
  init_cjs_shims();
94239
- var import_picocolors12 = __toESM(require_picocolors(), 1);
94752
+ var import_picocolors13 = __toESM(require_picocolors(), 1);
94240
94753
  var ARCHITECTURE_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
94241
94754
 
94242
94755
  System summary
@@ -94265,7 +94778,7 @@ async function runArchitecture(opts) {
94265
94778
  const health = await code.health();
94266
94779
  if (!health.ok) {
94267
94780
  console.error(
94268
- import_picocolors12.default.red("Source-intelligence host unreachable \u2014 run: horus index")
94781
+ import_picocolors13.default.red("Source-intelligence host unreachable \u2014 run: horus index")
94269
94782
  );
94270
94783
  return 1;
94271
94784
  }
@@ -94293,7 +94806,7 @@ async function runArchitecture(opts) {
94293
94806
  });
94294
94807
  console.log("\n" + renderInterpretation(result));
94295
94808
  if (!result.ok) {
94296
- console.error(import_picocolors12.default.yellow(`[ai] ${result.warning}`));
94809
+ console.error(import_picocolors13.default.yellow(`[ai] ${result.warning}`));
94297
94810
  }
94298
94811
  }
94299
94812
  }
@@ -94302,14 +94815,14 @@ async function runArchitecture(opts) {
94302
94815
  }
94303
94816
  return 0;
94304
94817
  } catch (err) {
94305
- console.error(import_picocolors12.default.red(err.message));
94818
+ console.error(import_picocolors13.default.red(err.message));
94306
94819
  return 1;
94307
94820
  }
94308
94821
  }
94309
94822
 
94310
94823
  // ../../packages/cli/src/commands/blast-radius.ts
94311
94824
  init_cjs_shims();
94312
- var import_picocolors13 = __toESM(require_picocolors(), 1);
94825
+ var import_picocolors14 = __toESM(require_picocolors(), 1);
94313
94826
  var BLAST_RADIUS_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
94314
94827
 
94315
94828
  Evidence used
@@ -94336,7 +94849,7 @@ async function runBlastRadius(query, opts) {
94336
94849
  const { code } = createConnectors(config2);
94337
94850
  const health = await code.health();
94338
94851
  if (!health.ok) {
94339
- console.error(import_picocolors13.default.red("Source-intelligence host unreachable \u2014 run: horus index"));
94852
+ console.error(import_picocolors14.default.red("Source-intelligence host unreachable \u2014 run: horus index"));
94340
94853
  return 1;
94341
94854
  }
94342
94855
  let project;
@@ -94349,12 +94862,12 @@ async function runBlastRadius(query, opts) {
94349
94862
  const r = await analyzeBlastRadius(query, { code, db, project }, opts.depth ?? 3);
94350
94863
  if (!r) {
94351
94864
  console.log(`No symbol found for: ${query}`);
94352
- console.log(import_picocolors13.default.dim(` Tip: use an exact class or function name, e.g. "MyService"`));
94865
+ console.log(import_picocolors14.default.dim(` Tip: use an exact class or function name, e.g. "MyService"`));
94353
94866
  return 1;
94354
94867
  }
94355
94868
  if (r.seed.name.toLowerCase() !== query.toLowerCase()) {
94356
94869
  console.log(
94357
- import_picocolors13.default.yellow(` No exact match for "${query}"`) + import_picocolors13.default.dim(` \u2014 showing closest: "${r.seed.name}" (fuzzy match)`)
94870
+ import_picocolors14.default.yellow(` No exact match for "${query}"`) + import_picocolors14.default.dim(` \u2014 showing closest: "${r.seed.name}" (fuzzy match)`)
94358
94871
  );
94359
94872
  }
94360
94873
  if (opts.json) {
@@ -94374,7 +94887,7 @@ async function runBlastRadius(query, opts) {
94374
94887
  });
94375
94888
  console.log("\n" + renderInterpretation(result));
94376
94889
  if (!result.ok) {
94377
- console.error(import_picocolors13.default.yellow(`[ai] ${result.warning}`));
94890
+ console.error(import_picocolors14.default.yellow(`[ai] ${result.warning}`));
94378
94891
  }
94379
94892
  }
94380
94893
  }
@@ -94383,28 +94896,28 @@ async function runBlastRadius(query, opts) {
94383
94896
  }
94384
94897
  return 0;
94385
94898
  } catch (err) {
94386
- console.error(import_picocolors13.default.red(err.message));
94899
+ console.error(import_picocolors14.default.red(err.message));
94387
94900
  return 1;
94388
94901
  }
94389
94902
  }
94390
94903
 
94391
94904
  // ../../packages/cli/src/commands/repos.ts
94392
94905
  init_cjs_shims();
94393
- var import_picocolors14 = __toESM(require_picocolors(), 1);
94906
+ var import_picocolors15 = __toESM(require_picocolors(), 1);
94394
94907
  async function runRepos(opts) {
94395
94908
  const config2 = await loadConfig(opts.config);
94396
94909
  const providers = repoProviders(config2);
94397
94910
  if (providers.length === 0) {
94398
- console.log(import_picocolors14.default.dim("No repositories configured."));
94911
+ console.log(import_picocolors15.default.dim("No repositories configured."));
94399
94912
  return 0;
94400
94913
  }
94401
94914
  const health = await reposHealth(providers);
94402
94915
  for (const h of health) {
94403
- const dot = h.reachable ? import_picocolors14.default.green("\u25CF") : import_picocolors14.default.red("\u25CF");
94404
- const name = import_picocolors14.default.bold(h.repo.padEnd(20));
94405
- const hostUrl = import_picocolors14.default.cyan(h.hostUrl.padEnd(30));
94406
- const path = import_picocolors14.default.dim(h.path);
94407
- const detail = h.detail ? import_picocolors14.default.dim(`(${h.detail})`) : "";
94916
+ const dot = h.reachable ? import_picocolors15.default.green("\u25CF") : import_picocolors15.default.red("\u25CF");
94917
+ const name = import_picocolors15.default.bold(h.repo.padEnd(20));
94918
+ const hostUrl = import_picocolors15.default.cyan(h.hostUrl.padEnd(30));
94919
+ const path = import_picocolors15.default.dim(h.path);
94920
+ const detail = h.detail ? import_picocolors15.default.dim(`(${h.detail})`) : "";
94408
94921
  console.log(` ${dot} ${name} ${hostUrl} ${path} ${detail}`);
94409
94922
  }
94410
94923
  return 0;
@@ -94412,7 +94925,7 @@ async function runRepos(opts) {
94412
94925
 
94413
94926
  // ../../packages/cli/src/commands/search.ts
94414
94927
  init_cjs_shims();
94415
- var import_picocolors15 = __toESM(require_picocolors(), 1);
94928
+ var import_picocolors16 = __toESM(require_picocolors(), 1);
94416
94929
  async function runSearch(query, opts) {
94417
94930
  const config2 = await loadConfig(opts.config);
94418
94931
  const providers = repoProviders(config2);
@@ -94423,16 +94936,16 @@ async function runSearch(query, opts) {
94423
94936
  }
94424
94937
  let totalMatches = 0;
94425
94938
  for (const result of results) {
94426
- const statusDot = result.reachable ? import_picocolors15.default.green("\u25CF") : import_picocolors15.default.red("\u25CF");
94939
+ const statusDot = result.reachable ? import_picocolors16.default.green("\u25CF") : import_picocolors16.default.red("\u25CF");
94427
94940
  console.log("");
94428
- console.log(`${statusDot} ${import_picocolors15.default.bold("## " + result.repo)} ${import_picocolors15.default.dim(result.hostUrl)}`);
94941
+ console.log(`${statusDot} ${import_picocolors16.default.bold("## " + result.repo)} ${import_picocolors16.default.dim(result.hostUrl)}`);
94429
94942
  if (!result.reachable) {
94430
- console.log(import_picocolors15.default.dim(" (unreachable)"));
94943
+ console.log(import_picocolors16.default.dim(" (unreachable)"));
94431
94944
  } else if (result.symbols.length === 0) {
94432
- console.log(import_picocolors15.default.dim(" (no matches)"));
94945
+ console.log(import_picocolors16.default.dim(" (no matches)"));
94433
94946
  } else {
94434
94947
  for (const sym of result.symbols) {
94435
- console.log(` - ${import_picocolors15.default.bold(sym.name)} ${import_picocolors15.default.dim(sym.filePath)}`);
94948
+ console.log(` - ${import_picocolors16.default.bold(sym.name)} ${import_picocolors16.default.dim(sym.filePath)}`);
94436
94949
  }
94437
94950
  totalMatches += result.symbols.length;
94438
94951
  }
@@ -94440,7 +94953,7 @@ async function runSearch(query, opts) {
94440
94953
  console.log("");
94441
94954
  const reachableCount = results.filter((r) => r.reachable).length;
94442
94955
  console.log(
94443
- import_picocolors15.default.dim(
94956
+ import_picocolors16.default.dim(
94444
94957
  `${totalMatches} match(es) across ${reachableCount}/${results.length} reachable repo(s) for query: "${query}"`
94445
94958
  )
94446
94959
  );
@@ -94449,7 +94962,7 @@ async function runSearch(query, opts) {
94449
94962
 
94450
94963
  // ../../packages/cli/src/commands/investigations.ts
94451
94964
  init_cjs_shims();
94452
- var import_picocolors16 = __toESM(require_picocolors(), 1);
94965
+ var import_picocolors17 = __toESM(require_picocolors(), 1);
94453
94966
 
94454
94967
  // ../../packages/cli/src/lib/format.ts
94455
94968
  init_cjs_shims();
@@ -94492,8 +95005,8 @@ async function runInvestigations(opts) {
94492
95005
  const session = authedClient();
94493
95006
  if (!session) {
94494
95007
  console.error(
94495
- import_picocolors16.default.red(
94496
- `This repo is linked to Horus Cloud but you are not logged in. Run ${import_picocolors16.default.bold("horus login")} first.`
95008
+ import_picocolors17.default.red(
95009
+ `This repo is linked to Horus Cloud but you are not logged in. Run ${import_picocolors17.default.bold("horus login")} first.`
94497
95010
  )
94498
95011
  );
94499
95012
  return 1;
@@ -94534,18 +95047,18 @@ async function runInvestigations(opts) {
94534
95047
 
94535
95048
  // ../../packages/cli/src/commands/replay.ts
94536
95049
  init_cjs_shims();
94537
- var import_picocolors17 = __toESM(require_picocolors(), 1);
95050
+ var import_picocolors18 = __toESM(require_picocolors(), 1);
94538
95051
  async function runReplay(id, opts) {
94539
95052
  const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
94540
95053
  try {
94541
95054
  const row = await getInvestigation(db, id);
94542
95055
  if (!row) {
94543
- console.error(import_picocolors17.default.red("No investigation found: " + id));
95056
+ console.error(import_picocolors18.default.red("No investigation found: " + id));
94544
95057
  return 1;
94545
95058
  }
94546
95059
  if (!row.report) {
94547
95060
  console.error(
94548
- import_picocolors17.default.red(
95061
+ import_picocolors18.default.red(
94549
95062
  "Investigation " + id + " has no stored report (run a newer investigation)."
94550
95063
  )
94551
95064
  );
@@ -94558,7 +95071,7 @@ async function runReplay(id, opts) {
94558
95071
  if (opts.ai && fmt !== "json") {
94559
95072
  if (report.aiJudgment && !opts.refreshAi) {
94560
95073
  renderStoredAIJudgment(report.aiJudgment);
94561
- console.log(import_picocolors17.default.dim("[ai] Stored judgment replayed. Use --refresh-ai to regenerate."));
95074
+ console.log(import_picocolors18.default.dim("[ai] Stored judgment replayed. Use --refresh-ai to regenerate."));
94562
95075
  } else {
94563
95076
  const narrativeInput = buildNarrativeInput(report);
94564
95077
  const { provider } = await buildNarrativeProvider({
@@ -94571,17 +95084,17 @@ async function runReplay(id, opts) {
94571
95084
  );
94572
95085
  if (!fromProvider && degraded) {
94573
95086
  if (validationErrors?.length) {
94574
- console.error(import_picocolors17.default.yellow("[ai] structured validation incomplete \u2014 showing raw AI narrative"));
95087
+ console.error(import_picocolors18.default.yellow("[ai] structured validation incomplete \u2014 showing raw AI narrative"));
94575
95088
  }
94576
95089
  console.log("");
94577
- console.log(import_picocolors17.default.dim("\u2500".repeat(60)));
94578
- console.log(import_picocolors17.default.bold("AI narrative ") + import_picocolors17.default.yellow("(unstructured \u2014 not validated)"));
94579
- if (output.what?.trim()) console.log(import_picocolors17.default.bold("\nWhat:"), output.what.trim());
94580
- if (output.why?.trim()) console.log(import_picocolors17.default.bold("\nWhy:"), output.why.trim());
95090
+ console.log(import_picocolors18.default.dim("\u2500".repeat(60)));
95091
+ console.log(import_picocolors18.default.bold("AI narrative ") + import_picocolors18.default.yellow("(unstructured \u2014 not validated)"));
95092
+ if (output.what?.trim()) console.log(import_picocolors18.default.bold("\nWhat:"), output.what.trim());
95093
+ if (output.why?.trim()) console.log(import_picocolors18.default.bold("\nWhy:"), output.why.trim());
94581
95094
  } else if (!fromProvider) {
94582
- console.error(import_picocolors17.default.yellow("[ai] Provider unavailable \u2014 deterministic output shown above."));
95095
+ console.error(import_picocolors18.default.yellow("[ai] Provider unavailable \u2014 deterministic output shown above."));
94583
95096
  if (validationErrors?.length) {
94584
- console.error(import_picocolors17.default.dim(` ${validationErrors[0]}`));
95097
+ console.error(import_picocolors18.default.dim(` ${validationErrors[0]}`));
94585
95098
  }
94586
95099
  } else {
94587
95100
  const stored = narrativeOutputToStoredJudgment(output, "anthropic");
@@ -94602,7 +95115,7 @@ async function runReplay(id, opts) {
94602
95115
 
94603
95116
  // ../../packages/cli/src/commands/owner.ts
94604
95117
  init_cjs_shims();
94605
- var import_picocolors18 = __toESM(require_picocolors(), 1);
95118
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
94606
95119
  async function runOwner(query, opts) {
94607
95120
  try {
94608
95121
  const config2 = await loadConfig(opts.config);
@@ -94610,7 +95123,7 @@ async function runOwner(query, opts) {
94610
95123
  try {
94611
95124
  renv = resolveEnvironment(config2, { project: opts.repo });
94612
95125
  } catch (err) {
94613
- console.error(import_picocolors18.default.red(err.message));
95126
+ console.error(import_picocolors19.default.red(err.message));
94614
95127
  return 1;
94615
95128
  }
94616
95129
  const code = codeForRepo(config2, renv.project);
@@ -94618,16 +95131,16 @@ async function runOwner(query, opts) {
94618
95131
  console.log(opts.json ? ownershipToJSON(o) : renderOwnership(o));
94619
95132
  return 0;
94620
95133
  } catch (err) {
94621
- console.error(import_picocolors18.default.red(err.message));
95134
+ console.error(import_picocolors19.default.red(err.message));
94622
95135
  return 1;
94623
95136
  }
94624
95137
  }
94625
95138
 
94626
95139
  // ../../packages/cli/src/commands/postmortem.ts
94627
95140
  init_cjs_shims();
94628
- var import_node_fs13 = require("fs");
94629
- var import_node_path14 = require("path");
94630
- var import_picocolors19 = __toESM(require_picocolors(), 1);
95141
+ var import_node_fs15 = require("fs");
95142
+ var import_node_path16 = require("path");
95143
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
94631
95144
  async function runPostmortem(id, opts) {
94632
95145
  const log = opts.write ?? ((line2) => console.log(line2));
94633
95146
  let sql2;
@@ -94641,11 +95154,11 @@ async function runPostmortem(id, opts) {
94641
95154
  const { db } = conn;
94642
95155
  const row = await getInvestigation(db, id);
94643
95156
  if (!row) {
94644
- log(import_picocolors19.default.red("No investigation found: " + id));
95157
+ log(import_picocolors20.default.red("No investigation found: " + id));
94645
95158
  return 1;
94646
95159
  }
94647
95160
  if (!row.report) {
94648
- log(import_picocolors19.default.red("Investigation " + id + " has no stored report."));
95161
+ log(import_picocolors20.default.red("Investigation " + id + " has no stored report."));
94649
95162
  return 1;
94650
95163
  }
94651
95164
  report = migrateReport(row.report);
@@ -94737,25 +95250,25 @@ _AI summary unavailable: ${validationErrors?.[0] ?? "provider error"}_
94737
95250
  }
94738
95251
  }
94739
95252
  if (opts.output) {
94740
- const outputPath = (0, import_node_path14.resolve)(opts.output);
94741
- if ((0, import_node_fs13.existsSync)(outputPath) && !opts.force) {
94742
- log(import_picocolors19.default.red("\u2717") + " " + outputPath + " already exists");
94743
- log(import_picocolors19.default.dim(" pass --force to overwrite"));
95253
+ const outputPath = (0, import_node_path16.resolve)(opts.output);
95254
+ if ((0, import_node_fs15.existsSync)(outputPath) && !opts.force) {
95255
+ log(import_picocolors20.default.red("\u2717") + " " + outputPath + " already exists");
95256
+ log(import_picocolors20.default.dim(" pass --force to overwrite"));
94744
95257
  return 1;
94745
95258
  }
94746
95259
  try {
94747
- (0, import_node_fs13.writeFileSync)(outputPath, content, "utf8");
95260
+ (0, import_node_fs15.writeFileSync)(outputPath, content, "utf8");
94748
95261
  } catch (err) {
94749
- log(import_picocolors19.default.red("\u2717 Could not write " + outputPath + ": ") + err.message);
95262
+ log(import_picocolors20.default.red("\u2717 Could not write " + outputPath + ": ") + err.message);
94750
95263
  return 1;
94751
95264
  }
94752
- log(import_picocolors19.default.green("\u2713") + " Saved postmortem to " + outputPath);
95265
+ log(import_picocolors20.default.green("\u2713") + " Saved postmortem to " + outputPath);
94753
95266
  } else {
94754
95267
  log(content);
94755
95268
  }
94756
95269
  } catch (err) {
94757
95270
  const message = err instanceof Error ? err.message : String(err);
94758
- log(import_picocolors19.default.red("postmortem failed: " + message));
95271
+ log(import_picocolors20.default.red("postmortem failed: " + message));
94759
95272
  return 1;
94760
95273
  } finally {
94761
95274
  if (sql2) {
@@ -94767,7 +95280,7 @@ _AI summary unavailable: ${validationErrors?.[0] ?? "provider error"}_
94767
95280
 
94768
95281
  // ../../packages/cli/src/commands/score.ts
94769
95282
  init_cjs_shims();
94770
- var import_picocolors20 = __toESM(require_picocolors(), 1);
95283
+ var import_picocolors21 = __toESM(require_picocolors(), 1);
94771
95284
  var SCORE_AI_CONTRACT = `Provide a clearly separated AI score explanation with:
94772
95285
 
94773
95286
  Why this scored this way
@@ -94786,11 +95299,11 @@ async function runScore(id, opts) {
94786
95299
  try {
94787
95300
  const row = await getInvestigation(db, id);
94788
95301
  if (!row) {
94789
- console.error(import_picocolors20.default.red("No investigation found: " + id));
95302
+ console.error(import_picocolors21.default.red("No investigation found: " + id));
94790
95303
  return 1;
94791
95304
  }
94792
95305
  if (!row.report) {
94793
- console.error(import_picocolors20.default.red("Investigation " + id + " has no stored report."));
95306
+ console.error(import_picocolors21.default.red("Investigation " + id + " has no stored report."));
94794
95307
  return 1;
94795
95308
  }
94796
95309
  const s = scoreInvestigation(migrateReport(row.report));
@@ -94811,7 +95324,7 @@ async function runScore(id, opts) {
94811
95324
  });
94812
95325
  console.log("\n" + renderInterpretation(result));
94813
95326
  if (!result.ok) {
94814
- console.error(import_picocolors20.default.yellow(`[ai] ${result.warning}`));
95327
+ console.error(import_picocolors21.default.yellow(`[ai] ${result.warning}`));
94815
95328
  }
94816
95329
  }
94817
95330
  }
@@ -94851,7 +95364,7 @@ async function runScores(opts) {
94851
95364
 
94852
95365
  // ../../packages/cli/src/commands/ask.ts
94853
95366
  init_cjs_shims();
94854
- var import_picocolors21 = __toESM(require_picocolors(), 1);
95367
+ var import_picocolors22 = __toESM(require_picocolors(), 1);
94855
95368
  function renderAnswer(report, directive, opts) {
94856
95369
  const answer = answerQuestion(report, directive);
94857
95370
  if (answer) {
@@ -94863,78 +95376,88 @@ function renderAnswer(report, directive, opts) {
94863
95376
  if (!opts.json && report.aiJudgment) {
94864
95377
  const j = report.aiJudgment;
94865
95378
  console.log("");
94866
- console.log(import_picocolors21.default.dim("\u2500".repeat(60)));
94867
- console.log(import_picocolors21.default.dim(`Stored AI judgment (${j.provider}, ${j.generatedAt}):`));
95379
+ console.log(import_picocolors22.default.dim("\u2500".repeat(60)));
95380
+ console.log(import_picocolors22.default.dim(`Stored AI judgment (${j.provider}, ${j.generatedAt}):`));
94868
95381
  if (j.rootCauseAssessment) {
94869
- console.log(import_picocolors21.default.bold("Root cause (AI):"), j.rootCauseAssessment.summary);
94870
- console.log(import_picocolors21.default.dim(`Uncertainty: ${j.rootCauseAssessment.uncertainty}`));
95382
+ console.log(import_picocolors22.default.bold("Root cause (AI):"), j.rootCauseAssessment.summary);
95383
+ console.log(import_picocolors22.default.dim(`Uncertainty: ${j.rootCauseAssessment.uncertainty}`));
94871
95384
  } else {
94872
- console.log(import_picocolors21.default.bold("AI Why:"), j.why);
95385
+ console.log(import_picocolors22.default.bold("AI Why:"), j.why);
94873
95386
  }
94874
95387
  }
94875
95388
  return 0;
94876
95389
  }
95390
+ async function lookupLocalInvestigation(id, configPath) {
95391
+ const { db, sql: sql2 } = createDb(await resolveDbUrl(configPath));
95392
+ try {
95393
+ const row = await getInvestigation(db, id);
95394
+ if (!row) return { kind: "not-found" };
95395
+ if (!row.report) return { kind: "no-report" };
95396
+ return { kind: "found", report: migrateReport(row.report) };
95397
+ } catch (err) {
95398
+ const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
95399
+ if (code === "22P02") return { kind: "not-found" };
95400
+ return { kind: "error", message: err instanceof Error ? err.message : String(err) };
95401
+ } finally {
95402
+ await sql2.end();
95403
+ }
95404
+ }
94877
95405
  async function runAsk(id, directive, opts) {
94878
95406
  const repoRoot = repoRootOrCwd();
94879
95407
  const cloudCfg = readCloudConfig(repoRoot);
94880
95408
  if (isCloudActive(cloudCfg)) {
95409
+ const local2 = await lookupLocalInvestigation(id, opts.config);
95410
+ if (local2.kind === "found") return renderAnswer(local2.report, directive, opts);
94881
95411
  const session = authedClient();
94882
95412
  if (!session) {
94883
95413
  console.error(
94884
- import_picocolors21.default.red(
94885
- `This repo is linked to Horus Cloud but you are not logged in. Run ${import_picocolors21.default.bold("horus login")} first.`
95414
+ import_picocolors22.default.red(
95415
+ `This repo is linked to Horus Cloud but you are not logged in. Run ${import_picocolors22.default.bold("horus login")} first.`
94886
95416
  )
94887
95417
  );
94888
95418
  return 1;
94889
95419
  }
94890
95420
  try {
94891
- const report = await fetchInvestigationReportFromCloud(
94892
- session.client,
94893
- cloudCfg,
94894
- id
95421
+ const report = await fetchInvestigationReportFromCloud(session.client, cloudCfg, id);
95422
+ if (report) return renderAnswer(report, directive, opts);
95423
+ console.error(
95424
+ import_picocolors22.default.red(
95425
+ `Investigation ${id} has no saved report locally or in Horus Cloud. Run ${import_picocolors22.default.bold("horus investigate")} first.`
95426
+ )
94895
95427
  );
94896
- if (!report) {
95428
+ return 1;
95429
+ } catch (err) {
95430
+ if (err instanceof CloudError && err.status === 404) {
94897
95431
  console.error(
94898
- import_picocolors21.default.red(
94899
- `Cloud investigation ${id} has no saved report. Run this investigation with ${import_picocolors21.default.bold("horus investigate")} first.`
95432
+ import_picocolors22.default.red(
95433
+ `No investigation found for "${id}" locally or in Horus Cloud. Use the id printed at the top of \`horus investigate\` output.`
94900
95434
  )
94901
95435
  );
94902
95436
  return 1;
94903
95437
  }
94904
- return renderAnswer(report, directive, opts);
94905
- } catch (err) {
94906
95438
  return reportCloudError(err);
94907
95439
  }
94908
95440
  }
94909
- const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
94910
- try {
94911
- const row = await getInvestigation(db, id);
94912
- if (!row) {
94913
- console.error(import_picocolors21.default.red("No investigation found: " + id));
95441
+ const local = await lookupLocalInvestigation(id, opts.config);
95442
+ switch (local.kind) {
95443
+ case "found":
95444
+ return renderAnswer(local.report, directive, opts);
95445
+ case "no-report":
95446
+ console.error(import_picocolors22.default.red("Investigation " + id + " has no stored report."));
94914
95447
  return 1;
94915
- }
94916
- if (!row.report) {
94917
- console.error(import_picocolors21.default.red("Investigation " + id + " has no stored report."));
95448
+ case "error":
95449
+ console.error(import_picocolors22.default.red(local.message));
94918
95450
  return 1;
94919
- }
94920
- const report = migrateReport(row.report);
94921
- return renderAnswer(report, directive, opts);
94922
- } catch (err) {
94923
- const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
94924
- if (code === "22P02") {
94925
- console.error(import_picocolors21.default.red("No investigation found: " + id));
95451
+ case "not-found":
95452
+ default:
95453
+ console.error(import_picocolors22.default.red("No investigation found: " + id));
94926
95454
  return 1;
94927
- }
94928
- console.error(import_picocolors21.default.red(err instanceof Error ? err.message : String(err)));
94929
- return 1;
94930
- } finally {
94931
- await sql2.end();
94932
95455
  }
94933
95456
  }
94934
95457
 
94935
95458
  // ../../packages/cli/src/commands/onboard.ts
94936
95459
  init_cjs_shims();
94937
- var import_picocolors22 = __toESM(require_picocolors(), 1);
95460
+ var import_picocolors23 = __toESM(require_picocolors(), 1);
94938
95461
  var ONBOARD_AI_CONTRACT = `Provide a clearly separated AI onboarding guide with:
94939
95462
 
94940
95463
  Start here
@@ -94962,7 +95485,7 @@ async function runOnboard(area, opts) {
94962
95485
  try {
94963
95486
  renv = resolveEnvironment(config2, { project: opts.repo });
94964
95487
  } catch (err) {
94965
- console.error(import_picocolors22.default.red(err.message));
95488
+ console.error(import_picocolors23.default.red(err.message));
94966
95489
  return 1;
94967
95490
  }
94968
95491
  const repo = { name: renv.project, path: renv.path };
@@ -94970,7 +95493,7 @@ async function runOnboard(area, opts) {
94970
95493
  const health = await code.health();
94971
95494
  if (!health.ok) {
94972
95495
  console.error(
94973
- import_picocolors22.default.red("Source-intelligence host unreachable \u2014 run: horus index")
95496
+ import_picocolors23.default.red("Source-intelligence host unreachable \u2014 run: horus index")
94974
95497
  );
94975
95498
  return 1;
94976
95499
  }
@@ -94997,7 +95520,7 @@ async function runOnboard(area, opts) {
94997
95520
  });
94998
95521
  console.log("\n" + renderInterpretation(result));
94999
95522
  if (!result.ok) {
95000
- console.error(import_picocolors22.default.yellow(`[ai] ${result.warning}`));
95523
+ console.error(import_picocolors23.default.yellow(`[ai] ${result.warning}`));
95001
95524
  }
95002
95525
  }
95003
95526
  }
@@ -95006,14 +95529,14 @@ async function runOnboard(area, opts) {
95006
95529
  }
95007
95530
  return 0;
95008
95531
  } catch (err) {
95009
- console.error(import_picocolors22.default.red(err.message));
95532
+ console.error(import_picocolors23.default.red(err.message));
95010
95533
  return 1;
95011
95534
  }
95012
95535
  }
95013
95536
 
95014
95537
  // ../../packages/cli/src/commands/simulate.ts
95015
95538
  init_cjs_shims();
95016
- var import_picocolors23 = __toESM(require_picocolors(), 1);
95539
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
95017
95540
  async function runSimulate(scenarioId, opts) {
95018
95541
  try {
95019
95542
  if (scenarioId === void 0) {
@@ -95023,7 +95546,7 @@ async function runSimulate(scenarioId, opts) {
95023
95546
  const scenario = getScenario(scenarioId);
95024
95547
  if (scenario === null) {
95025
95548
  console.error(
95026
- import_picocolors23.default.red(
95549
+ import_picocolors24.default.red(
95027
95550
  `Unknown scenario: "${scenarioId}". Run horus simulate to list available scenarios.`
95028
95551
  )
95029
95552
  );
@@ -95034,14 +95557,14 @@ async function runSimulate(scenarioId, opts) {
95034
95557
  try {
95035
95558
  renv = resolveEnvironment(config2, { project: opts.repo });
95036
95559
  } catch (err) {
95037
- console.error(import_picocolors23.default.red(err.message));
95560
+ console.error(import_picocolors24.default.red(err.message));
95038
95561
  return 1;
95039
95562
  }
95040
95563
  const code = codeForRepo(config2, renv.project);
95041
95564
  const health = await code.health();
95042
95565
  if (!health.ok) {
95043
95566
  console.error(
95044
- import_picocolors23.default.red("Source-intelligence host unreachable \u2014 run: horus index")
95567
+ import_picocolors24.default.red("Source-intelligence host unreachable \u2014 run: horus index")
95045
95568
  );
95046
95569
  return 1;
95047
95570
  }
@@ -95058,14 +95581,14 @@ async function runSimulate(scenarioId, opts) {
95058
95581
  }
95059
95582
  return 0;
95060
95583
  } catch (err) {
95061
- console.error(import_picocolors23.default.red(err.message));
95584
+ console.error(import_picocolors24.default.red(err.message));
95062
95585
  return 1;
95063
95586
  }
95064
95587
  }
95065
95588
 
95066
95589
  // ../../packages/cli/src/commands/logs.ts
95067
95590
  init_cjs_shims();
95068
- var import_picocolors24 = __toESM(require_picocolors(), 1);
95591
+ var import_picocolors25 = __toESM(require_picocolors(), 1);
95069
95592
  var LOGS_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
95070
95593
 
95071
95594
  Evidence used
@@ -95098,9 +95621,17 @@ function resolveRawLevel(opts) {
95098
95621
  return opts.level ?? "error";
95099
95622
  }
95100
95623
  function levelColor(level, text2) {
95101
- if (level === "error" || level === "fatal") return import_picocolors24.default.red(text2);
95102
- if (level === "warn") return import_picocolors24.default.yellow(text2);
95103
- return import_picocolors24.default.dim(text2);
95624
+ if (level === "error" || level === "fatal") return import_picocolors25.default.red(text2);
95625
+ if (level === "warn") return import_picocolors25.default.yellow(text2);
95626
+ return import_picocolors25.default.dim(text2);
95627
+ }
95628
+ function matchServiceLabel(requested, candidates) {
95629
+ if (candidates.includes(requested)) return { kind: "unique", matched: requested };
95630
+ const needle = requested.toLowerCase();
95631
+ const subs = candidates.filter((c) => c.toLowerCase().includes(needle));
95632
+ if (subs.length === 1 && subs[0] !== void 0) return { kind: "unique", matched: subs[0] };
95633
+ if (subs.length > 1) return { kind: "ambiguous", candidates: subs };
95634
+ return { kind: "none" };
95104
95635
  }
95105
95636
  async function runLogs(service, opts) {
95106
95637
  try {
@@ -95109,13 +95640,13 @@ async function runLogs(service, opts) {
95109
95640
  try {
95110
95641
  renv = resolveEnvironment(config2, { project: opts.project, env: opts.env });
95111
95642
  } catch (err) {
95112
- console.error(import_picocolors24.default.red(err.message));
95643
+ console.error(import_picocolors25.default.red(err.message));
95113
95644
  return 1;
95114
95645
  }
95115
95646
  const logs = logsForEnv(renv);
95116
95647
  if (logs === null) {
95117
95648
  console.error(
95118
- import_picocolors24.default.red(
95649
+ import_picocolors25.default.red(
95119
95650
  `No Elasticsearch connector configured for ${renv.project}/${renv.env} (set ES_URL / ES_USERNAME / ES_PASSWORD)`
95120
95651
  )
95121
95652
  );
@@ -95123,7 +95654,7 @@ async function runLogs(service, opts) {
95123
95654
  }
95124
95655
  const health = await logs.health();
95125
95656
  if (!health.ok) {
95126
- console.error(import_picocolors24.default.red(`Elasticsearch unreachable: ${health.detail}`));
95657
+ console.error(import_picocolors25.default.red(`Elasticsearch unreachable: ${health.detail}`));
95127
95658
  return 1;
95128
95659
  }
95129
95660
  const resolvedService = service ?? renv.connectors.elasticsearch?.serviceName;
@@ -95136,21 +95667,21 @@ async function runLogs(service, opts) {
95136
95667
  requiresEventCode: aggregatesEventCode
95137
95668
  });
95138
95669
  for (const w of compat.issues.filter((i) => i.severity === "warning")) {
95139
- console.warn(import_picocolors24.default.yellow(`[warn] ${w.field}: ${w.message}`));
95670
+ console.warn(import_picocolors25.default.yellow(`[warn] ${w.field}: ${w.message}`));
95140
95671
  }
95141
95672
  const errors2 = compat.issues.filter((i) => i.severity === "error");
95142
95673
  if (errors2.length > 0) {
95143
- console.error(import_picocolors24.default.red("Elasticsearch field mapping is incompatible with the index:"));
95674
+ console.error(import_picocolors25.default.red("Elasticsearch field mapping is incompatible with the index:"));
95144
95675
  for (const e of errors2) {
95145
- console.error(import_picocolors24.default.red(` ${e.field}: ${e.message}`));
95676
+ console.error(import_picocolors25.default.red(` ${e.field}: ${e.message}`));
95146
95677
  }
95147
95678
  console.error(
95148
- import_picocolors24.default.dim("Fix fields.* overrides in your connector config or choose the correct preset.")
95679
+ import_picocolors25.default.dim("Fix fields.* overrides in your connector config or choose the correct preset.")
95149
95680
  );
95150
95681
  return 1;
95151
95682
  }
95152
95683
  } catch {
95153
- console.warn(import_picocolors24.default.dim("[warn] Elasticsearch compatibility check unavailable \u2014 proceeding."));
95684
+ console.warn(import_picocolors25.default.dim("[warn] Elasticsearch compatibility check unavailable \u2014 proceeding."));
95154
95685
  }
95155
95686
  const from = sinceToIso(opts.since) ?? new Date(Date.now() - 7 * 864e5).toISOString();
95156
95687
  const fromDisplay = from.slice(0, 16).replace("T", " ");
@@ -95201,7 +95732,7 @@ async function runLogs(service, opts) {
95201
95732
  return 0;
95202
95733
  }
95203
95734
  if (records.length === 0) {
95204
- console.log(import_picocolors24.default.dim("No logs matched."));
95735
+ console.log(import_picocolors25.default.dim("No logs matched."));
95205
95736
  return 0;
95206
95737
  }
95207
95738
  for (const r of records) {
@@ -95211,7 +95742,7 @@ async function runLogs(service, opts) {
95211
95742
  const line2 = `${ts} ${lvl} ${comp} ${r.message.slice(0, 120)}`;
95212
95743
  console.log(levelColor(r.level, line2));
95213
95744
  for (const { key, value } of extractContextFields(r)) {
95214
- console.log(import_picocolors24.default.dim(` ${key}: ${value.slice(0, 200)}`));
95745
+ console.log(import_picocolors25.default.dim(` ${key}: ${value.slice(0, 200)}`));
95215
95746
  }
95216
95747
  }
95217
95748
  return 0;
@@ -95250,22 +95781,22 @@ async function runLogs(service, opts) {
95250
95781
  return 0;
95251
95782
  }
95252
95783
  console.log(
95253
- import_picocolors24.default.bold(`Top ${field}`) + import_picocolors24.default.dim(
95784
+ import_picocolors25.default.bold(`Top ${field}`) + import_picocolors25.default.dim(
95254
95785
  ` \u2014 ${renv.project}/${renv.env}` + (resolvedService ? ` \xB7 service ${resolvedService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
95255
95786
  )
95256
95787
  );
95257
- console.log(import_picocolors24.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
95788
+ console.log(import_picocolors25.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
95258
95789
  console.log("");
95259
95790
  if (buckets.length === 0) {
95260
- console.log(import_picocolors24.default.dim(` No error logs with field "${field}" in the window.`));
95261
- console.log(import_picocolors24.default.dim(` Tip: ensure "${field}" is an aggregatable (keyword) field.`));
95791
+ console.log(import_picocolors25.default.dim(` No error logs with field "${field}" in the window.`));
95792
+ console.log(import_picocolors25.default.dim(` Tip: ensure "${field}" is an aggregatable (keyword) field.`));
95262
95793
  return 0;
95263
95794
  }
95264
95795
  for (const b2 of buckets) {
95265
- console.log(` ${import_picocolors24.default.red(String(b2.count).padStart(6))} ${b2.key}`);
95796
+ console.log(` ${import_picocolors25.default.red(String(b2.count).padStart(6))} ${b2.key}`);
95266
95797
  }
95267
95798
  console.log("");
95268
- console.log(import_picocolors24.default.dim(` ${buckets.length} distinct ${field} value(s) shown (top by error count).`));
95799
+ console.log(import_picocolors25.default.dim(` ${buckets.length} distinct ${field} value(s) shown (top by error count).`));
95269
95800
  return 0;
95270
95801
  }
95271
95802
  let analysis = await logs.analyzeErrors({
@@ -95279,9 +95810,32 @@ async function runLogs(service, opts) {
95279
95810
  if (analysis.signatures.length === 0 && resolvedService !== void 0) {
95280
95811
  const broader = await logs.analyzeErrors({ from, text: opts.grep, broadText: opts.grep !== void 0 });
95281
95812
  if (broader.signatures.length > 0) {
95282
- analysis = broader;
95283
- broadeningNote = `No errors found for service "${resolvedService}" \u2014 showing all services`;
95284
- scopeService = void 0;
95813
+ const match = matchServiceLabel(resolvedService, broader.affectedServices);
95814
+ if (match.kind === "unique" && match.matched !== void 0 && match.matched !== resolvedService) {
95815
+ const scoped = await logs.analyzeErrors({
95816
+ service: match.matched,
95817
+ from,
95818
+ text: opts.grep,
95819
+ broadText: opts.grep !== void 0
95820
+ });
95821
+ if (scoped.signatures.length > 0) {
95822
+ analysis = scoped;
95823
+ scopeService = match.matched;
95824
+ broadeningNote = `No exact match for service "${resolvedService}" \u2014 matched "${match.matched}"`;
95825
+ } else {
95826
+ analysis = broader;
95827
+ scopeService = void 0;
95828
+ broadeningNote = `No errors found for service "${resolvedService}" \u2014 showing all services`;
95829
+ }
95830
+ } else if (match.kind === "ambiguous" && match.candidates !== void 0) {
95831
+ analysis = broader;
95832
+ scopeService = void 0;
95833
+ broadeningNote = `"${resolvedService}" matches multiple services: ${match.candidates.join(", ")} \u2014 showing all; re-run with a full label`;
95834
+ } else {
95835
+ analysis = broader;
95836
+ scopeService = void 0;
95837
+ broadeningNote = `No errors found for service "${resolvedService}" \u2014 showing all services`;
95838
+ }
95285
95839
  }
95286
95840
  }
95287
95841
  if (opts.json) {
@@ -95309,46 +95863,46 @@ async function runLogs(service, opts) {
95309
95863
  return 0;
95310
95864
  }
95311
95865
  console.log(
95312
- import_picocolors24.default.bold(`Error analysis`) + import_picocolors24.default.dim(
95866
+ import_picocolors25.default.bold(`Error analysis`) + import_picocolors25.default.dim(
95313
95867
  ` \u2014 ${renv.project}/${renv.env}` + (scopeService ? ` \xB7 service ${scopeService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
95314
95868
  )
95315
95869
  );
95316
- console.log(import_picocolors24.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
95870
+ console.log(import_picocolors25.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
95317
95871
  if (broadeningNote) {
95318
- console.log(import_picocolors24.default.yellow(` ${broadeningNote}`));
95872
+ console.log(import_picocolors25.default.yellow(` ${broadeningNote}`));
95319
95873
  }
95320
95874
  console.log(
95321
- ` ${analysis.totalErrors} error(s) \xB7 ${analysis.signatures.length} signature(s) \xB7 ${import_picocolors24.default.yellow(String(analysis.newSignatures.length))} new`
95875
+ ` ${analysis.totalErrors} error(s) \xB7 ${analysis.signatures.length} signature(s) \xB7 ${import_picocolors25.default.yellow(String(analysis.newSignatures.length))} new`
95322
95876
  );
95323
95877
  console.log("");
95324
95878
  if (analysis.signatures.length === 0) {
95325
- console.log(import_picocolors24.default.dim(" No error-level logs in the window."));
95879
+ console.log(import_picocolors25.default.dim(" No error-level logs in the window."));
95326
95880
  console.log(
95327
- import_picocolors24.default.dim(
95881
+ import_picocolors25.default.dim(
95328
95882
  ` Searched: ${scopeService ? `service "${scopeService}"` : "all services"} \xB7 index: ${indexPattern} \xB7 from: ${fromDisplay} UTC`
95329
95883
  )
95330
95884
  );
95331
- console.log(import_picocolors24.default.dim(` Tip: use --since to widen the window (e.g. --since 30d).`));
95885
+ console.log(import_picocolors25.default.dim(` Tip: use --since to widen the window (e.g. --since 30d).`));
95332
95886
  return 0;
95333
95887
  }
95334
95888
  for (const s of analysis.signatures) {
95335
95889
  const tags = [];
95336
- if (s.isNew) tags.push(import_picocolors24.default.red("NEW"));
95890
+ if (s.isNew) tags.push(import_picocolors25.default.red("NEW"));
95337
95891
  else if (s.ratio !== void 0 && Number.isFinite(s.ratio) && s.ratio >= 1.5) {
95338
- tags.push(import_picocolors24.default.yellow(`spike x${s.ratio.toFixed(1)}`));
95892
+ tags.push(import_picocolors25.default.yellow(`spike x${s.ratio.toFixed(1)}`));
95339
95893
  }
95340
- const svc = s.services.length > 0 ? import_picocolors24.default.dim(` \xB7 ${s.services.slice(0, 3).join(", ")}`) : "";
95894
+ const svc = s.services.length > 0 ? import_picocolors25.default.dim(` \xB7 ${s.services.slice(0, 3).join(", ")}`) : "";
95341
95895
  const tagStr = tags.length > 0 ? ` [${tags.join(", ")}]` : "";
95342
95896
  console.log(
95343
- ` ${import_picocolors24.default.red(String(s.count).padStart(6))} ${import_picocolors24.default.bold(s.key.padEnd(14).slice(0, 14))} ` + import_picocolors24.default.dim(`first ${shortTs(s.firstSeen)} \xB7 last ${shortTs(s.lastSeen)}`) + svc + tagStr
95897
+ ` ${import_picocolors25.default.red(String(s.count).padStart(6))} ${import_picocolors25.default.bold(s.key.padEnd(14).slice(0, 14))} ` + import_picocolors25.default.dim(`first ${shortTs(s.firstSeen)} \xB7 last ${shortTs(s.lastSeen)}`) + svc + tagStr
95344
95898
  );
95345
95899
  }
95346
95900
  if (analysis.affectedServices.length > 0) {
95347
95901
  console.log("");
95348
- console.log(` ${import_picocolors24.default.bold("Affected services:")} ${analysis.affectedServices.join(", ")}`);
95902
+ console.log(` ${import_picocolors25.default.bold("Affected services:")} ${analysis.affectedServices.join(", ")}`);
95349
95903
  }
95350
95904
  console.log("");
95351
- console.log(import_picocolors24.default.dim(" (use --raw to see individual log lines)"));
95905
+ console.log(import_picocolors25.default.dim(" (use --raw to see individual log lines)"));
95352
95906
  if (opts.ai) {
95353
95907
  const result = await renderAiInterpretation({
95354
95908
  command: "logs",
@@ -95367,19 +95921,19 @@ async function runLogs(service, opts) {
95367
95921
  });
95368
95922
  console.log("\n" + renderInterpretation(result));
95369
95923
  if (!result.ok) {
95370
- console.error(import_picocolors24.default.yellow(`[ai] ${result.warning}`));
95924
+ console.error(import_picocolors25.default.yellow(`[ai] ${result.warning}`));
95371
95925
  }
95372
95926
  }
95373
95927
  return 0;
95374
95928
  } catch (err) {
95375
- console.error(import_picocolors24.default.red(err.message));
95929
+ console.error(import_picocolors25.default.red(err.message));
95376
95930
  return 1;
95377
95931
  }
95378
95932
  }
95379
95933
 
95380
95934
  // ../../packages/cli/src/commands/metrics.ts
95381
95935
  init_cjs_shims();
95382
- var import_picocolors25 = __toESM(require_picocolors(), 1);
95936
+ var import_picocolors26 = __toESM(require_picocolors(), 1);
95383
95937
  var METRICS_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
95384
95938
 
95385
95939
  Evidence used
@@ -95424,10 +95978,10 @@ function anomalyColor(anomaly) {
95424
95978
  case "latency-spike":
95425
95979
  case "error-rate-change":
95426
95980
  case "throughput-drop":
95427
- return import_picocolors25.default.red;
95981
+ return import_picocolors26.default.red;
95428
95982
  case "queue-growth":
95429
95983
  case "change":
95430
- return import_picocolors25.default.yellow;
95984
+ return import_picocolors26.default.yellow;
95431
95985
  default:
95432
95986
  return (s) => s;
95433
95987
  }
@@ -95454,7 +96008,7 @@ async function runMetrics(hint, opts) {
95454
96008
  const metrics = metricsProviderFromConfig(config2);
95455
96009
  if (metrics === null) {
95456
96010
  console.error(
95457
- import_picocolors25.default.red(
96011
+ import_picocolors26.default.red(
95458
96012
  "Grafana not configured \u2014 set GRAFANA_URL + GRAFANA_USER + GRAFANA_PASSWORD"
95459
96013
  )
95460
96014
  );
@@ -95462,7 +96016,7 @@ async function runMetrics(hint, opts) {
95462
96016
  }
95463
96017
  const health = await metrics.health();
95464
96018
  if (!health.ok) {
95465
- console.error(import_picocolors25.default.red(`Grafana unreachable: ${health.detail}`));
96019
+ console.error(import_picocolors26.default.red(`Grafana unreachable: ${health.detail}`));
95466
96020
  return 1;
95467
96021
  }
95468
96022
  const stepNum = opts.step !== void 0 ? Number(opts.step) : void 0;
@@ -95491,14 +96045,14 @@ async function runMetrics(hint, opts) {
95491
96045
  return 0;
95492
96046
  }
95493
96047
  if (series.length === 0) {
95494
- console.log(import_picocolors25.default.dim("No series returned."));
96048
+ console.log(import_picocolors26.default.dim("No series returned."));
95495
96049
  return 0;
95496
96050
  }
95497
96051
  for (const s of series) {
95498
96052
  const summary = summarize(s);
95499
96053
  const label = labelStr(s.labels);
95500
96054
  console.log(
95501
- ` ${import_picocolors25.default.cyan(label || "(no labels)")} last=${import_picocolors25.default.bold(fmtNum(summary.last))} avg=${fmtNum(summary.avg)}`
96055
+ ` ${import_picocolors26.default.cyan(label || "(no labels)")} last=${import_picocolors26.default.bold(fmtNum(summary.last))} avg=${fmtNum(summary.avg)}`
95502
96056
  );
95503
96057
  }
95504
96058
  return 0;
@@ -95513,36 +96067,36 @@ async function runMetrics(hint, opts) {
95513
96067
  if (findings2.length === 0) {
95514
96068
  if (hint !== void 0) {
95515
96069
  const tokens = extractHintTokens(hint);
95516
- console.log(import_picocolors25.default.dim(`No panels matched hint "${hint}".`));
96070
+ console.log(import_picocolors26.default.dim(`No panels matched hint "${hint}".`));
95517
96071
  if (tokens.length > 0) {
95518
- console.log(import_picocolors25.default.dim(` Searched for panels containing: ${tokens.join(", ")}`));
96072
+ console.log(import_picocolors26.default.dim(` Searched for panels containing: ${tokens.join(", ")}`));
95519
96073
  }
95520
96074
  console.log(
95521
- import_picocolors25.default.dim(
95522
- ` Tip: run ${import_picocolors25.default.bold("horus metrics")} (no hint) to see all available panels, then use a panel title or keyword directly.`
96075
+ import_picocolors26.default.dim(
96076
+ ` Tip: run ${import_picocolors26.default.bold("horus metrics")} (no hint) to see all available panels, then use a panel title or keyword directly.`
95523
96077
  )
95524
96078
  );
95525
96079
  } else {
95526
- console.log(import_picocolors25.default.dim("No panels found in configured Grafana dashboards."));
96080
+ console.log(import_picocolors26.default.dim("No panels found in configured Grafana dashboards."));
95527
96081
  }
95528
96082
  return 0;
95529
96083
  }
95530
96084
  const hintSuffix = hint !== void 0 ? ` (hint: "${hint}")` : "";
95531
96085
  const matchSources = hint !== void 0 ? [...new Set(findings2.map((f) => f.matchSource).filter(Boolean))] : [];
95532
- const matchSuffix = matchSources.length > 0 ? import_picocolors25.default.dim(` [matched via ${matchSources.join(", ")}]`) : "";
96086
+ const matchSuffix = matchSources.length > 0 ? import_picocolors26.default.dim(` [matched via ${matchSources.join(", ")}]`) : "";
95533
96087
  console.log(
95534
- import_picocolors25.default.bold(
96088
+ import_picocolors26.default.bold(
95535
96089
  `Grafana metrics${hintSuffix} \u2014 ${findings2.length} series across panels`
95536
96090
  ) + matchSuffix
95537
96091
  );
95538
96092
  console.log(
95539
- import_picocolors25.default.dim(
96093
+ import_picocolors26.default.dim(
95540
96094
  ` window: ${new Date(from * 1e3).toISOString()} \u2013 ${new Date(to * 1e3).toISOString()}`
95541
96095
  )
95542
96096
  );
95543
96097
  console.log("");
95544
96098
  if (flagged.length === 0) {
95545
- console.log(import_picocolors25.default.green(" No anomalies detected."));
96099
+ console.log(import_picocolors26.default.green(" No anomalies detected."));
95546
96100
  } else {
95547
96101
  for (const f of flagged) {
95548
96102
  const color = anomalyColor(f.anomaly);
@@ -95551,14 +96105,14 @@ async function runMetrics(hint, opts) {
95551
96105
  const lblStr = labelStr(f.labels);
95552
96106
  console.log(
95553
96107
  color(
95554
- ` ${label.padEnd(22)} ${f.panelTitle.padEnd(40).slice(0, 40)} ${fmtNum(f.baselineAvg)} -> ${fmtNum(f.currentAvg)} (${ratioStr})` + (lblStr !== "" ? ` ${import_picocolors25.default.dim(lblStr)}` : "")
96108
+ ` ${label.padEnd(22)} ${f.panelTitle.padEnd(40).slice(0, 40)} ${fmtNum(f.baselineAvg)} -> ${fmtNum(f.currentAvg)} (${ratioStr})` + (lblStr !== "" ? ` ${import_picocolors26.default.dim(lblStr)}` : "")
95555
96109
  )
95556
96110
  );
95557
96111
  }
95558
96112
  }
95559
96113
  if (ok.length > 0) {
95560
96114
  console.log("");
95561
- console.log(import_picocolors25.default.dim(` ${ok.length} panel series with no anomaly.`));
96115
+ console.log(import_picocolors26.default.dim(` ${ok.length} panel series with no anomaly.`));
95562
96116
  }
95563
96117
  if (opts.ai) {
95564
96118
  const result = await renderAiInterpretation({
@@ -95573,19 +96127,19 @@ async function runMetrics(hint, opts) {
95573
96127
  });
95574
96128
  console.log("\n" + renderInterpretation(result));
95575
96129
  if (!result.ok) {
95576
- console.error(import_picocolors25.default.yellow(`[ai] ${result.warning}`));
96130
+ console.error(import_picocolors26.default.yellow(`[ai] ${result.warning}`));
95577
96131
  }
95578
96132
  }
95579
96133
  return 0;
95580
96134
  } catch (err) {
95581
- console.error(import_picocolors25.default.red(err.message));
96135
+ console.error(import_picocolors26.default.red(err.message));
95582
96136
  return 1;
95583
96137
  }
95584
96138
  }
95585
96139
 
95586
96140
  // ../../packages/cli/src/commands/state.ts
95587
96141
  init_cjs_shims();
95588
- var import_picocolors26 = __toESM(require_picocolors(), 1);
96142
+ var import_picocolors27 = __toESM(require_picocolors(), 1);
95589
96143
  var STATE_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
95590
96144
 
95591
96145
  Evidence used
@@ -95611,13 +96165,13 @@ async function runState(opts) {
95611
96165
  try {
95612
96166
  renv = resolveEnvironment(config2, { project: opts.project, env: opts.env });
95613
96167
  } catch (err) {
95614
- console.error(import_picocolors26.default.red(err.message));
96168
+ console.error(import_picocolors27.default.red(err.message));
95615
96169
  return 1;
95616
96170
  }
95617
96171
  const mongo = mongoForEnv(renv);
95618
96172
  if (mongo === null) {
95619
96173
  console.error(
95620
- import_picocolors26.default.red(
96174
+ import_picocolors27.default.red(
95621
96175
  `No MongoDB connector configured for ${renv.project}/${renv.env} (set the project's Mongo URL env var).`
95622
96176
  )
95623
96177
  );
@@ -95625,7 +96179,7 @@ async function runState(opts) {
95625
96179
  }
95626
96180
  const health = await mongo.health();
95627
96181
  if (!health.ok) {
95628
- console.error(import_picocolors26.default.red(`MongoDB unreachable: ${health.detail}`));
96182
+ console.error(import_picocolors27.default.red(`MongoDB unreachable: ${health.detail}`));
95629
96183
  await mongo.close();
95630
96184
  return 1;
95631
96185
  }
@@ -95651,38 +96205,38 @@ async function runState(opts) {
95651
96205
  );
95652
96206
  return 0;
95653
96207
  }
95654
- const discoveryNote = analysis.autoDiscovered ? import_picocolors26.default.dim(` (${analysis.collections.length} collections, auto-discovered)`) : "";
96208
+ const discoveryNote = analysis.autoDiscovered ? import_picocolors27.default.dim(` (${analysis.collections.length} collections, auto-discovered)`) : "";
95655
96209
  console.log(
95656
- import_picocolors26.default.bold("State analysis") + import_picocolors26.default.dim(` \u2014 ${renv.project}/${renv.env} \xB7 db ${analysis.database}`) + discoveryNote
96210
+ import_picocolors27.default.bold("State analysis") + import_picocolors27.default.dim(` \u2014 ${renv.project}/${renv.env} \xB7 db ${analysis.database}`) + discoveryNote
95657
96211
  );
95658
96212
  console.log("");
95659
96213
  let signals = 0;
95660
96214
  for (const c of analysis.collections) {
95661
96215
  const flags = [];
95662
96216
  if (c.isStale === true) {
95663
- flags.push(import_picocolors26.default.yellow(`stale (last ${c.dateField}, ~${Math.round(c.ageHours ?? 0)}h)`));
96217
+ flags.push(import_picocolors27.default.yellow(`stale (last ${c.dateField}, ~${Math.round(c.ageHours ?? 0)}h)`));
95664
96218
  }
95665
96219
  for (const a of c.anomalies) {
95666
- flags.push(import_picocolors26.default.red(`${a.count}\xD7 "${a.value}"`));
96220
+ flags.push(import_picocolors27.default.red(`${a.count}\xD7 "${a.value}"`));
95667
96221
  signals += 1;
95668
96222
  }
95669
96223
  if (c.isStale === true) signals += 1;
95670
- const head = ` ${import_picocolors26.default.bold(c.collection.padEnd(18).slice(0, 18))} ${import_picocolors26.default.dim(String(c.count).padStart(8) + " docs")}`;
95671
- console.log(flags.length > 0 ? `${head} ${flags.join(" ")}` : import_picocolors26.default.dim(head));
96224
+ const head = ` ${import_picocolors27.default.bold(c.collection.padEnd(18).slice(0, 18))} ${import_picocolors27.default.dim(String(c.count).padStart(8) + " docs")}`;
96225
+ console.log(flags.length > 0 ? `${head} ${flags.join(" ")}` : import_picocolors27.default.dim(head));
95672
96226
  }
95673
96227
  console.log("");
95674
96228
  if (analysis.collections.length === 0) {
95675
96229
  console.log(
95676
- import_picocolors26.default.yellow(" No collections discovered in this database.") + import_picocolors26.default.dim(" Verify the database name in your MongoDB connector config.")
96230
+ import_picocolors27.default.yellow(" No collections discovered in this database.") + import_picocolors27.default.dim(" Verify the database name in your MongoDB connector config.")
95677
96231
  );
95678
96232
  } else {
95679
96233
  const scope = analysis.autoDiscovered ? "collection(s)" : "allowlisted collection(s)";
95680
96234
  console.log(
95681
- signals > 0 ? ` ${import_picocolors26.default.bold(String(signals))} state signal(s) across ${analysis.collections.length} ${scope}` : import_picocolors26.default.dim(` No state anomalies across ${analysis.collections.length} ${scope}.`)
96235
+ signals > 0 ? ` ${import_picocolors27.default.bold(String(signals))} state signal(s) across ${analysis.collections.length} ${scope}` : import_picocolors27.default.dim(` No state anomalies across ${analysis.collections.length} ${scope}.`)
95682
96236
  );
95683
96237
  if (analysis.autoDiscovered) {
95684
96238
  console.log(
95685
- import_picocolors26.default.dim(
96239
+ import_picocolors27.default.dim(
95686
96240
  ` Tip: add a "collections" list to your MongoDB connector config to restrict analysis.`
95687
96241
  )
95688
96242
  );
@@ -95700,7 +96254,7 @@ async function runState(opts) {
95700
96254
  });
95701
96255
  console.log("\n" + renderInterpretation(result));
95702
96256
  if (!result.ok) {
95703
- console.error(import_picocolors26.default.yellow(`[ai] ${result.warning}`));
96257
+ console.error(import_picocolors27.default.yellow(`[ai] ${result.warning}`));
95704
96258
  }
95705
96259
  }
95706
96260
  return 0;
@@ -95708,20 +96262,20 @@ async function runState(opts) {
95708
96262
  await mongo.close();
95709
96263
  }
95710
96264
  } catch (err) {
95711
- console.error(import_picocolors26.default.red(err.message));
96265
+ console.error(import_picocolors27.default.red(err.message));
95712
96266
  return 1;
95713
96267
  }
95714
96268
  }
95715
96269
 
95716
96270
  // ../../packages/cli/src/commands/init.ts
95717
96271
  init_cjs_shims();
95718
- var import_picocolors27 = __toESM(require_picocolors(), 1);
95719
- var import_node_path15 = require("path");
96272
+ var import_picocolors28 = __toESM(require_picocolors(), 1);
96273
+ var import_node_path17 = require("path");
95720
96274
  async function runInit(opts) {
95721
96275
  try {
95722
96276
  const cwd = process.cwd();
95723
- const root = opts.path ? (0, import_node_path15.resolve)(opts.path) : findRepoRoot(cwd) ?? cwd;
95724
- const name = opts.name ?? (0, import_node_path15.basename)(root);
96277
+ const root = opts.path ? (0, import_node_path17.resolve)(opts.path) : findRepoRoot(cwd) ?? cwd;
96278
+ const name = opts.name ?? (0, import_node_path17.basename)(root);
95725
96279
  const envName = opts.env ?? "production";
95726
96280
  const repo = { name, path: root };
95727
96281
  if (opts.source) repo["source"] = { hostUrl: opts.source };
@@ -95736,65 +96290,65 @@ async function runInit(opts) {
95736
96290
  const configPath = writeLocalConfig(root, file);
95737
96291
  registerProject(name, root, configPath);
95738
96292
  ensureProjectGitignore(root);
95739
- console.log(`${import_picocolors27.default.green("\u2713")} Initialized Horus project ${import_picocolors27.default.bold(name)}`);
95740
- console.log(import_picocolors27.default.dim(` config: ${configPath}`));
95741
- console.log(import_picocolors27.default.dim(` registered: horus investigate --name ${name} "<hint>"`));
96293
+ console.log(`${import_picocolors28.default.green("\u2713")} Initialized Horus project ${import_picocolors28.default.bold(name)}`);
96294
+ console.log(import_picocolors28.default.dim(` config: ${configPath}`));
96295
+ console.log(import_picocolors28.default.dim(` registered: horus investigate --name ${name} "<hint>"`));
95742
96296
  if (!opts.source) {
95743
96297
  console.log(
95744
- import_picocolors27.default.dim(" no source-intelligence host set \u2014 run `horus index` to analyze + host, or pass --source <url>")
96298
+ import_picocolors28.default.dim(" no source-intelligence host set \u2014 run `horus index` to analyze + host, or pass --source <url>")
95745
96299
  );
95746
96300
  }
95747
96301
  console.log(
95748
- import_picocolors27.default.dim(" add runtime connectors (elasticsearch/mongodb/grafana) to .horus/config.json")
96302
+ import_picocolors28.default.dim(" add runtime connectors (elasticsearch/mongodb/grafana) to .horus/config.json")
95749
96303
  );
95750
96304
  console.log(
95751
- import_picocolors27.default.dim(" .horus/ is gitignored \u2014 local config and runtime state stay out of version control")
96305
+ import_picocolors28.default.dim(" .horus/ is gitignored \u2014 local config and runtime state stay out of version control")
95752
96306
  );
95753
96307
  return 0;
95754
96308
  } catch (err) {
95755
- console.error(import_picocolors27.default.red(err.message));
96309
+ console.error(import_picocolors28.default.red(err.message));
95756
96310
  return 1;
95757
96311
  }
95758
96312
  }
95759
96313
 
95760
96314
  // ../../packages/cli/src/commands/projects.ts
95761
96315
  init_cjs_shims();
95762
- var import_picocolors28 = __toESM(require_picocolors(), 1);
96316
+ var import_picocolors29 = __toESM(require_picocolors(), 1);
95763
96317
  async function runProjects() {
95764
96318
  const reg = readRegistry();
95765
96319
  const names = Object.keys(reg.projects).sort();
95766
96320
  if (names.length === 0) {
95767
96321
  console.log(
95768
- import_picocolors28.default.dim("No registered projects. Run `horus index` (or `horus init`) inside a repo.")
96322
+ import_picocolors29.default.dim("No registered projects. Run `horus index` (or `horus init`) inside a repo.")
95769
96323
  );
95770
96324
  return 0;
95771
96325
  }
95772
- console.log(import_picocolors28.default.bold("Registered Horus projects"));
96326
+ console.log(import_picocolors29.default.bold("Registered Horus projects"));
95773
96327
  console.log("");
95774
96328
  for (const name of names) {
95775
96329
  const entry2 = reg.projects[name];
95776
96330
  if (entry2 === void 0) continue;
95777
- console.log(` ${import_picocolors28.default.bold(name.padEnd(22))} ${import_picocolors28.default.dim(entry2.root)}`);
96331
+ console.log(` ${import_picocolors29.default.bold(name.padEnd(22))} ${import_picocolors29.default.dim(entry2.root)}`);
95778
96332
  }
95779
96333
  console.log("");
95780
- console.log(import_picocolors28.default.dim(' horus investigate --name <name> "<hint>"'));
96334
+ console.log(import_picocolors29.default.dim(' horus investigate --name <name> "<hint>"'));
95781
96335
  return 0;
95782
96336
  }
95783
96337
 
95784
96338
  // ../../packages/cli/src/commands/setup.ts
95785
96339
  init_cjs_shims();
95786
- var import_picocolors29 = __toESM(require_picocolors(), 1);
96340
+ var import_picocolors30 = __toESM(require_picocolors(), 1);
95787
96341
  var DEFAULT_DB_URL3 = "postgresql://horus:horus@localhost:5433/horus";
95788
96342
  async function runSetup(opts) {
95789
96343
  const write = opts.write ?? ((line2) => console.log(line2));
95790
96344
  let ok = true;
95791
- write(import_picocolors29.default.bold("\nHorus setup\n"));
96345
+ write(import_picocolors30.default.bold("\nHorus setup\n"));
95792
96346
  const backendVersion = await getSourceVersion();
95793
96347
  if (backendVersion === null) {
95794
96348
  ok = false;
95795
- write(` ${import_picocolors29.default.red("\u25CF")} Horus source-intelligence backend not found`);
96349
+ write(` ${import_picocolors30.default.red("\u25CF")} Horus source-intelligence backend not found`);
95796
96350
  write(
95797
- import_picocolors29.default.dim(
96351
+ import_picocolors30.default.dim(
95798
96352
  ` install it (Python 3.11+ required):
95799
96353
  pip install horus-source
95800
96354
  ensure ~/.local/bin is on your PATH`
@@ -95803,17 +96357,17 @@ async function runSetup(opts) {
95803
96357
  } else if (backendVersion !== PINNED_SOURCE_VERSION) {
95804
96358
  ok = false;
95805
96359
  write(
95806
- ` ${import_picocolors29.default.yellow("\u25CF")} Horus source-intelligence backend version mismatch` + import_picocolors29.default.dim(` (installed: ${backendVersion}, required: ${PINNED_SOURCE_VERSION})`)
96360
+ ` ${import_picocolors30.default.yellow("\u25CF")} Horus source-intelligence backend version mismatch` + import_picocolors30.default.dim(` (installed: ${backendVersion}, required: ${PINNED_SOURCE_VERSION})`)
95807
96361
  );
95808
96362
  write(
95809
- import_picocolors29.default.dim(
96363
+ import_picocolors30.default.dim(
95810
96364
  ` update it:
95811
96365
  pip install --upgrade horus-source`
95812
96366
  )
95813
96367
  );
95814
96368
  } else {
95815
96369
  write(
95816
- ` ${import_picocolors29.default.green("\u25CF")} Horus source-intelligence backend ` + import_picocolors29.default.dim(`(${backendVersion})`)
96370
+ ` ${import_picocolors30.default.green("\u25CF")} Horus source-intelligence backend ` + import_picocolors30.default.dim(`(${backendVersion})`)
95817
96371
  );
95818
96372
  }
95819
96373
  let dbUrl = process.env["DATABASE_URL"] ?? DEFAULT_DB_URL3;
@@ -95825,11 +96379,11 @@ async function runSetup(opts) {
95825
96379
  }
95826
96380
  const db = await checkDatabase(dbUrl);
95827
96381
  if (db.reachable) {
95828
- write(` ${import_picocolors29.default.green("\u25CF")} Postgres reachable ${import_picocolors29.default.dim(`(${db.schemaDetail})`)}`);
96382
+ write(` ${import_picocolors30.default.green("\u25CF")} Postgres reachable ${import_picocolors30.default.dim(`(${db.schemaDetail})`)}`);
95829
96383
  if (!db.schemaReady) {
95830
96384
  ok = false;
95831
96385
  write(
95832
- import_picocolors29.default.dim(
96386
+ import_picocolors30.default.dim(
95833
96387
  ` schema not applied \u2014 run migrations:
95834
96388
  from the Horus repo: pnpm db migrate
95835
96389
  or apply the schema directly: https://github.com/meritt-dev/horus/tree/master/packages/db/drizzle`
@@ -95838,9 +96392,9 @@ async function runSetup(opts) {
95838
96392
  }
95839
96393
  } else {
95840
96394
  ok = false;
95841
- write(` ${import_picocolors29.default.red("\u25CF")} Postgres unreachable`);
96395
+ write(` ${import_picocolors30.default.red("\u25CF")} Postgres unreachable`);
95842
96396
  write(
95843
- import_picocolors29.default.dim(
96397
+ import_picocolors30.default.dim(
95844
96398
  ` start a local instance:
95845
96399
  docker run -d --name horus-db \\
95846
96400
  -e POSTGRES_USER=horus -e POSTGRES_PASSWORD=horus -e POSTGRES_DB=horus \\
@@ -95865,10 +96419,10 @@ async function runSetup(opts) {
95865
96419
  if (!health.ok) {
95866
96420
  ok = false;
95867
96421
  write(
95868
- ` ${import_picocolors29.default.red("\u25CF")} Source intelligence host unreachable for ${import_picocolors29.default.bold(repo.name)} ` + import_picocolors29.default.dim(`(${repoHostUrl})`)
96422
+ ` ${import_picocolors30.default.red("\u25CF")} Source intelligence host unreachable for ${import_picocolors30.default.bold(repo.name)} ` + import_picocolors30.default.dim(`(${repoHostUrl})`)
95869
96423
  );
95870
96424
  write(
95871
- import_picocolors29.default.dim(
96425
+ import_picocolors30.default.dim(
95872
96426
  ` start the source intelligence host:
95873
96427
  cd ${repo.path}
95874
96428
  horus index`
@@ -95879,10 +96433,10 @@ async function runSetup(opts) {
95879
96433
  if (count === 0) {
95880
96434
  ok = false;
95881
96435
  write(
95882
- ` ${import_picocolors29.default.yellow("\u25CF")} Source intelligence host running but ${import_picocolors29.default.bold(repo.name)} is not indexed`
96436
+ ` ${import_picocolors30.default.yellow("\u25CF")} Source intelligence host running but ${import_picocolors30.default.bold(repo.name)} is not indexed`
95883
96437
  );
95884
96438
  write(
95885
- import_picocolors29.default.dim(
96439
+ import_picocolors30.default.dim(
95886
96440
  ` index the repo:
95887
96441
  cd ${repo.path}
95888
96442
  horus index`
@@ -95890,7 +96444,7 @@ async function runSetup(opts) {
95890
96444
  );
95891
96445
  } else {
95892
96446
  write(
95893
- ` ${import_picocolors29.default.green("\u25CF")} ${import_picocolors29.default.bold(repo.name)} \u2014 ${count} nodes indexed ` + import_picocolors29.default.dim(`(${repoHostUrl})`)
96447
+ ` ${import_picocolors30.default.green("\u25CF")} ${import_picocolors30.default.bold(repo.name)} \u2014 ${count} nodes indexed ` + import_picocolors30.default.dim(`(${repoHostUrl})`)
95894
96448
  );
95895
96449
  }
95896
96450
  }
@@ -95900,10 +96454,10 @@ async function runSetup(opts) {
95900
96454
  write("");
95901
96455
  if (ok) {
95902
96456
  write(
95903
- import_picocolors29.default.green("Ready.") + ' Next: `cd` into a repo and run `horus index`, then `horus investigate "<hint>"`.'
96457
+ import_picocolors30.default.green("Ready.") + ' Next: `cd` into a repo and run `horus index`, then `horus investigate "<hint>"`.'
95904
96458
  );
95905
96459
  } else {
95906
- write(import_picocolors29.default.yellow("Resolve the items above, then re-run `horus setup`."));
96460
+ write(import_picocolors30.default.yellow("Resolve the items above, then re-run `horus setup`."));
95907
96461
  }
95908
96462
  return ok ? 0 : 1;
95909
96463
  }
@@ -95912,12 +96466,12 @@ async function runSetup(opts) {
95912
96466
  init_cjs_shims();
95913
96467
  var import_node_readline = require("readline");
95914
96468
  var import_node_child_process6 = require("child_process");
95915
- var import_picocolors32 = __toESM(require_picocolors(), 1);
96469
+ var import_picocolors33 = __toESM(require_picocolors(), 1);
95916
96470
 
95917
96471
  // ../../packages/cli/src/lib/tty-selector.ts
95918
96472
  init_cjs_shims();
95919
96473
  var import_node_tty = require("tty");
95920
- var import_picocolors30 = __toESM(require_picocolors(), 1);
96474
+ var import_picocolors31 = __toESM(require_picocolors(), 1);
95921
96475
  var ExitPromptError = class extends Error {
95922
96476
  constructor(message = "Prompt was cancelled") {
95923
96477
  super(message);
@@ -95973,7 +96527,7 @@ async function checkboxSearch(opts) {
95973
96527
  clear();
95974
96528
  const lines = [];
95975
96529
  lines.push(
95976
- `? ${import_picocolors30.default.bold(message)} ${import_picocolors30.default.dim("(\u2191\u2193 navigate \u2022 space toggle \u2022 enter confirm \u2022 type filter)")}`
96530
+ `? ${import_picocolors31.default.bold(message)} ${import_picocolors31.default.dim("(\u2191\u2193 navigate \u2022 space toggle \u2022 enter confirm \u2022 type filter)")}`
95977
96531
  );
95978
96532
  const visible = filtered.slice(visibleOffset, visibleOffset + pageSize);
95979
96533
  for (let i = 0; i < visible.length; i++) {
@@ -95981,29 +96535,29 @@ async function checkboxSearch(opts) {
95981
96535
  const choice = visible[i];
95982
96536
  const isCursor = idx === cursor;
95983
96537
  const isSelected = selected.has(choice);
95984
- const marker = isSelected ? import_picocolors30.default.green("\u25CF") : " ";
96538
+ const marker = isSelected ? import_picocolors31.default.green("\u25CF") : " ";
95985
96539
  const prefix = `[${marker}]`;
95986
- const label = isCursor ? import_picocolors30.default.cyan(choice) : choice;
95987
- const pointer = isCursor ? import_picocolors30.default.cyan("\u276F") : " ";
96540
+ const label = isCursor ? import_picocolors31.default.cyan(choice) : choice;
96541
+ const pointer = isCursor ? import_picocolors31.default.cyan("\u276F") : " ";
95988
96542
  lines.push(` ${pointer} ${prefix} ${label}`);
95989
96543
  }
95990
96544
  if (filtered.length === 0) {
95991
- lines.push(import_picocolors30.default.dim(" No matches"));
96545
+ lines.push(import_picocolors31.default.dim(" No matches"));
95992
96546
  }
95993
96547
  const statusParts = [
95994
96548
  `${selected.size} selected`,
95995
96549
  `${filtered.length}/${choices.length} matches`
95996
96550
  ];
95997
96551
  if (filter) statusParts.push(`filter: ${filter}`);
95998
- lines.push(import_picocolors30.default.dim(` ${statusParts.join(" \xB7 ")}`));
96552
+ lines.push(import_picocolors31.default.dim(` ${statusParts.join(" \xB7 ")}`));
95999
96553
  output.write(lines.join("\n"));
96000
96554
  lastRenderLines = lines.length;
96001
96555
  }
96002
96556
  function finish(values2) {
96003
96557
  cleanup();
96004
- const summary = values2.length > 0 ? values2.join(", ") : import_picocolors30.default.dim("none");
96558
+ const summary = values2.length > 0 ? values2.join(", ") : import_picocolors31.default.dim("none");
96005
96559
  output.write(`
96006
- ? ${import_picocolors30.default.bold(message)} ${summary}
96560
+ ? ${import_picocolors31.default.bold(message)} ${summary}
96007
96561
  `);
96008
96562
  resolve8(values2);
96009
96563
  }
@@ -96095,7 +96649,7 @@ async function checkboxSearch(opts) {
96095
96649
  // ../../packages/cli/src/commands/connect-ai.ts
96096
96650
  init_cjs_shims();
96097
96651
  var import_node_child_process5 = require("child_process");
96098
- var import_picocolors31 = __toESM(require_picocolors(), 1);
96652
+ var import_picocolors32 = __toESM(require_picocolors(), 1);
96099
96653
  function cliInstalled(id) {
96100
96654
  const r = (0, import_node_child_process5.spawnSync)(id, ["--version"], { stdio: "ignore", timeout: 2e3 });
96101
96655
  return !r.error;
@@ -96123,13 +96677,13 @@ async function runConnectAi(opts) {
96123
96677
  const root = findRepoRoot(cwd) ?? cwd;
96124
96678
  const configPath = discoverLocalConfig(cwd) ?? localConfigPath(root);
96125
96679
  const installed = LOCAL_PROVIDER_IDS.filter((id) => cliInstalled(id));
96126
- console.log(import_picocolors31.default.bold("\nAI providers"));
96680
+ console.log(import_picocolors32.default.bold("\nAI providers"));
96127
96681
  console.log(
96128
- import_picocolors31.default.dim(
96682
+ import_picocolors32.default.dim(
96129
96683
  ` Local CLIs on PATH: ${installed.length > 0 ? installed.join(", ") : "none detected"}`
96130
96684
  )
96131
96685
  );
96132
- console.log(import_picocolors31.default.dim(" Cloud: anthropic (Anthropic Claude API \u2014 used by `investigate --ai`)\n"));
96686
+ console.log(import_picocolors32.default.dim(" Cloud: anthropic (Anthropic Claude API \u2014 used by `investigate --ai`)\n"));
96133
96687
  let provider = opts.provider;
96134
96688
  if (provider === void 0) {
96135
96689
  if (isInteractive()) {
@@ -96140,7 +96694,7 @@ async function runConnectAi(opts) {
96140
96694
  }
96141
96695
  }
96142
96696
  if (!AI_PROVIDERS.includes(provider)) {
96143
- console.error(import_picocolors31.default.red(`Unknown AI provider: ${provider}`) + import_picocolors31.default.dim(`
96697
+ console.error(import_picocolors32.default.red(`Unknown AI provider: ${provider}`) + import_picocolors32.default.dim(`
96144
96698
  supported: ${AI_PROVIDERS.join(", ")}`));
96145
96699
  return 1;
96146
96700
  }
@@ -96153,7 +96707,7 @@ async function runConnectAi(opts) {
96153
96707
  }
96154
96708
  if (!apiKey) {
96155
96709
  console.error(
96156
- import_picocolors31.default.red("No API key provided.") + import_picocolors31.default.dim("\n Pass --api-key sk-ant-\u2026 or set ANTHROPIC_API_KEY and re-run.")
96710
+ import_picocolors32.default.red("No API key provided.") + import_picocolors32.default.dim("\n Pass --api-key sk-ant-\u2026 or set ANTHROPIC_API_KEY and re-run.")
96157
96711
  );
96158
96712
  return 1;
96159
96713
  }
@@ -96161,28 +96715,28 @@ async function runConnectAi(opts) {
96161
96715
  const probe2 = await probeAnthropic(apiKey);
96162
96716
  if (!probe2.ok) {
96163
96717
  console.error(`
96164
- ${import_picocolors31.default.red("\u2717 Anthropic key check failed:")} ${probe2.detail}`);
96165
- console.error(import_picocolors31.default.dim(" Fix the key and retry, or pass --no-test to skip."));
96718
+ ${import_picocolors32.default.red("\u2717 Anthropic key check failed:")} ${probe2.detail}`);
96719
+ console.error(import_picocolors32.default.dim(" Fix the key and retry, or pass --no-test to skip."));
96166
96720
  return 1;
96167
96721
  }
96168
96722
  console.log(`
96169
- ${import_picocolors31.default.green("\u2713")} anthropic ${import_picocolors31.default.dim(`(${probe2.detail})`)}`);
96723
+ ${import_picocolors32.default.green("\u2713")} anthropic ${import_picocolors32.default.dim(`(${probe2.detail})`)}`);
96170
96724
  }
96171
96725
  if (opts.model) aiBlock["anthropic"] = { model: opts.model };
96172
96726
  storedKey = apiKey;
96173
96727
  } else {
96174
96728
  if (!installed.includes(provider)) {
96175
96729
  console.error(
96176
- import_picocolors31.default.red(`${provider} CLI not found on PATH.`) + import_picocolors31.default.dim(`
96730
+ import_picocolors32.default.red(`${provider} CLI not found on PATH.`) + import_picocolors32.default.dim(`
96177
96731
  Install it first, or choose anthropic.`)
96178
96732
  );
96179
96733
  return 1;
96180
96734
  }
96181
96735
  const desc2 = DEFAULT_LOCAL_PROVIDER_REGISTRY.get(provider);
96182
96736
  console.log(`
96183
- ${import_picocolors31.default.green("\u2713")} ${desc2?.displayName ?? provider} ${import_picocolors31.default.dim("detected on PATH")}`);
96737
+ ${import_picocolors32.default.green("\u2713")} ${desc2?.displayName ?? provider} ${import_picocolors32.default.dim("detected on PATH")}`);
96184
96738
  console.log(
96185
- import_picocolors31.default.dim(
96739
+ import_picocolors32.default.dim(
96186
96740
  " Note: `investigate --ai` currently uses the Anthropic API for narrative.\n Local-CLI narrative generation is recorded as your preference for future use."
96187
96741
  )
96188
96742
  );
@@ -96200,15 +96754,15 @@ ${import_picocolors31.default.green("\u2713")} ${desc2?.displayName ?? provider}
96200
96754
  });
96201
96755
  }
96202
96756
  console.log(`
96203
- ${import_picocolors31.default.green("\u2713")} ${import_picocolors31.default.bold("ai")} provider saved \u2192 ${import_picocolors31.default.dim(configPath)}`);
96204
- console.log(import_picocolors31.default.dim(` provider: ${provider}`));
96757
+ ${import_picocolors32.default.green("\u2713")} ${import_picocolors32.default.bold("ai")} provider saved \u2192 ${import_picocolors32.default.dim(configPath)}`);
96758
+ console.log(import_picocolors32.default.dim(` provider: ${provider}`));
96205
96759
  if (storedKey && secretsPathStr) {
96206
- console.log(import_picocolors31.default.dim(` anthropic key: ${redactKey(storedKey)} \u2192 ${secretsPathStr} (gitignored)`));
96760
+ console.log(import_picocolors32.default.dim(` anthropic key: ${redactKey(storedKey)} \u2192 ${secretsPathStr} (gitignored)`));
96207
96761
  }
96208
- console.log(import_picocolors31.default.dim(' run: horus investigate "<hint>" --ai'));
96762
+ console.log(import_picocolors32.default.dim(' run: horus investigate "<hint>" --ai'));
96209
96763
  return 0;
96210
96764
  } catch (err) {
96211
- console.error(import_picocolors31.default.red(err instanceof Error ? err.message : String(err)));
96765
+ console.error(import_picocolors32.default.red(err instanceof Error ? err.message : String(err)));
96212
96766
  return 1;
96213
96767
  }
96214
96768
  }
@@ -96240,7 +96794,7 @@ async function runConnect(type, opts) {
96240
96794
  }
96241
96795
  if (!SUPPORTED.includes(type)) {
96242
96796
  console.error(
96243
- import_picocolors32.default.red(`Unknown connector type: ${type}`) + import_picocolors32.default.dim(`
96797
+ import_picocolors33.default.red(`Unknown connector type: ${type}`) + import_picocolors33.default.dim(`
96244
96798
  supported: ${SUPPORTED.join(", ")}, ai`)
96245
96799
  );
96246
96800
  return 1;
@@ -96271,20 +96825,20 @@ async function runConnect(type, opts) {
96271
96825
  if (!probeResult.ok) {
96272
96826
  console.error(
96273
96827
  `
96274
- ${import_picocolors32.default.red(`\u2717 Could not reach ${connectorType}:`)} ${probeResult.detail}` + import_picocolors32.default.dim("\n Fix the connection and retry, or pass --no-test to skip.")
96828
+ ${import_picocolors33.default.red(`\u2717 Could not reach ${connectorType}:`)} ${probeResult.detail}` + import_picocolors33.default.dim("\n Fix the connection and retry, or pass --no-test to skip.")
96275
96829
  );
96276
96830
  return 1;
96277
96831
  }
96278
96832
  console.log(
96279
96833
  `
96280
- ${import_picocolors32.default.green("\u2713")} ${connectorType} reachable ${import_picocolors32.default.dim(`(${probeResult.detail})`)}`
96834
+ ${import_picocolors33.default.green("\u2713")} ${connectorType} reachable ${import_picocolors33.default.dim(`(${probeResult.detail})`)}`
96281
96835
  );
96282
96836
  }
96283
96837
  const hasLiteralCredentials = filled.url !== void 0 || filled.password !== void 0 || filled.username !== void 0;
96284
96838
  if (hasLiteralCredentials) {
96285
96839
  if (isGitTracked(configPath, root)) {
96286
96840
  console.error(
96287
- import_picocolors32.default.red(".horus/config.json is already tracked by Git.") + "\nStoring credentials here would expose them in the repository.\n" + import_picocolors32.default.dim(
96841
+ import_picocolors33.default.red(".horus/config.json is already tracked by Git.") + "\nStoring credentials here would expose them in the repository.\n" + import_picocolors33.default.dim(
96288
96842
  " 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."
96289
96843
  )
96290
96844
  );
@@ -96294,18 +96848,18 @@ ${import_picocolors32.default.green("\u2713")} ${connectorType} reachable ${impo
96294
96848
  }
96295
96849
  patchLocalConnector(configPath, connectorType, patch, filled.env);
96296
96850
  console.log(
96297
- `${import_picocolors32.default.green("\u2713")} ${import_picocolors32.default.bold(connectorType)} connector saved \u2192 ${import_picocolors32.default.dim(configPath)}`
96851
+ `${import_picocolors33.default.green("\u2713")} ${import_picocolors33.default.bold(connectorType)} connector saved \u2192 ${import_picocolors33.default.dim(configPath)}`
96298
96852
  );
96299
96853
  printSummary(connectorType, filled);
96300
- console.log(import_picocolors32.default.dim(`
96854
+ console.log(import_picocolors33.default.dim(`
96301
96855
  run: horus investigate "<hint>"`));
96302
96856
  return 0;
96303
96857
  } catch (err) {
96304
96858
  if (err instanceof ExitPromptError) {
96305
- console.error(import_picocolors32.default.red("Cancelled."));
96859
+ console.error(import_picocolors33.default.red("Cancelled."));
96306
96860
  return 1;
96307
96861
  }
96308
- console.error(import_picocolors32.default.red(err.message));
96862
+ console.error(import_picocolors33.default.red(err.message));
96309
96863
  return 1;
96310
96864
  }
96311
96865
  }
@@ -96314,7 +96868,7 @@ async function fillInteractive(type, opts) {
96314
96868
  if (!needsInteraction) return opts;
96315
96869
  console.log(
96316
96870
  `
96317
- ${import_picocolors32.default.bold(`Connect ${type}`)} ${import_picocolors32.default.dim("(press Enter to skip optional fields)")}
96871
+ ${import_picocolors33.default.bold(`Connect ${type}`)} ${import_picocolors33.default.dim("(press Enter to skip optional fields)")}
96318
96872
  `
96319
96873
  );
96320
96874
  const filled = { ...opts };
@@ -96369,7 +96923,7 @@ ${import_picocolors32.default.bold(`Connect ${type}`)} ${import_picocolors32.def
96369
96923
  break;
96370
96924
  case "redis": {
96371
96925
  console.log(
96372
- import_picocolors32.default.dim(
96926
+ import_picocolors33.default.dim(
96373
96927
  " Tip: embed credentials directly in the URL \u2014 redis://:password@host:6379\n or enter the URL and password separately below."
96374
96928
  )
96375
96929
  );
@@ -96407,8 +96961,8 @@ function missingRequired(type, opts) {
96407
96961
  }
96408
96962
  function ask(label, placeholder = "", required2 = true) {
96409
96963
  return new Promise((resolve8) => {
96410
- const hint = placeholder ? import_picocolors32.default.dim(` (${placeholder})`) : "";
96411
- const suffix = required2 ? "" : import_picocolors32.default.dim(" [optional]");
96964
+ const hint = placeholder ? import_picocolors33.default.dim(` (${placeholder})`) : "";
96965
+ const suffix = required2 ? "" : import_picocolors33.default.dim(" [optional]");
96412
96966
  process.stdout.write(` ${label}${suffix}${hint}: `);
96413
96967
  const rl = (0, import_node_readline.createInterface)({
96414
96968
  input: process.stdin,
@@ -96425,7 +96979,7 @@ function askPassword(label) {
96425
96979
  return new Promise((resolve8) => {
96426
96980
  const stdin = process.stdin;
96427
96981
  if (typeof stdin.setRawMode === "function") {
96428
- process.stdout.write(` ${label}${import_picocolors32.default.dim(" [optional]")}: `);
96982
+ process.stdout.write(` ${label}${import_picocolors33.default.dim(" [optional]")}: `);
96429
96983
  stdin.setRawMode(true);
96430
96984
  stdin.resume();
96431
96985
  let value = "";
@@ -96525,15 +97079,15 @@ function describeProbe(p) {
96525
97079
  return `${p.keyCount} keys \xB7 ${p.suggestedRoles.join("/")}${examples ? ` \xB7 ${examples}` : ""}`;
96526
97080
  }
96527
97081
  async function discoverAndSelectDbs(url, bullmqPrefix) {
96528
- console.log(import_picocolors32.default.dim("\n Scanning DBs 0-15 (read-only, sampled)\u2026"));
97082
+ console.log(import_picocolors33.default.dim("\n Scanning DBs 0-15 (read-only, sampled)\u2026"));
96529
97083
  const probes = await probeRedisDatabases(url, { bullmqPrefix });
96530
97084
  const nonEmpty = probes.filter((p) => p.reachable && p.keyCount > 0);
96531
97085
  if (nonEmpty.length === 0) {
96532
- console.log(import_picocolors32.default.dim(" No populated DBs found."));
97086
+ console.log(import_picocolors33.default.dim(" No populated DBs found."));
96533
97087
  return [];
96534
97088
  }
96535
97089
  for (const p of nonEmpty) {
96536
- console.log(` ${import_picocolors32.default.bold(`DB ${p.db}`)} ${import_picocolors32.default.dim("\xB7 " + describeProbe(p))}`);
97090
+ console.log(` ${import_picocolors33.default.bold(`DB ${p.db}`)} ${import_picocolors33.default.dim("\xB7 " + describeProbe(p))}`);
96537
97091
  }
96538
97092
  const byLabel = /* @__PURE__ */ new Map();
96539
97093
  const choices = nonEmpty.map((p) => {
@@ -96644,7 +97198,7 @@ function printSummary(type, opts) {
96644
97198
  } else if (opts.dashboard) {
96645
97199
  lines.push(` dashboard: ${opts.dashboard}`);
96646
97200
  }
96647
- if (lines.length > 0) console.log(import_picocolors32.default.dim(lines.join("\n")));
97201
+ if (lines.length > 0) console.log(import_picocolors33.default.dim(lines.join("\n")));
96648
97202
  }
96649
97203
  function isGitTracked(filePath, cwd) {
96650
97204
  try {
@@ -96683,11 +97237,11 @@ async function askIndexSelection(indices) {
96683
97237
  const shown = indices.slice(0, MAX_DISPLAY);
96684
97238
  console.log("\n Available Elasticsearch indexes/data streams:");
96685
97239
  shown.forEach((name, i) => {
96686
- console.log(` ${import_picocolors32.default.dim(`[${i + 1}]`)} ${name}`);
97240
+ console.log(` ${import_picocolors33.default.dim(`[${i + 1}]`)} ${name}`);
96687
97241
  });
96688
97242
  if (indices.length > MAX_DISPLAY) {
96689
97243
  console.log(
96690
- import_picocolors32.default.dim(
97244
+ import_picocolors33.default.dim(
96691
97245
  ` \u2026 and ${indices.length - MAX_DISPLAY} more (type a pattern manually to match all)`
96692
97246
  )
96693
97247
  );
@@ -96731,11 +97285,11 @@ async function askDashboardSelection(dashboards) {
96731
97285
  const shown = dashboards.slice(0, MAX_DISPLAY);
96732
97286
  console.log("\n Available Grafana dashboards:");
96733
97287
  shown.forEach((d, i) => {
96734
- const folder = d.folderTitle ? import_picocolors32.default.dim(` (${d.folderTitle})`) : "";
96735
- console.log(` ${import_picocolors32.default.dim(`[${i + 1}]`)} ${d.title}${folder}`);
97288
+ const folder = d.folderTitle ? import_picocolors33.default.dim(` (${d.folderTitle})`) : "";
97289
+ console.log(` ${import_picocolors33.default.dim(`[${i + 1}]`)} ${d.title}${folder}`);
96736
97290
  });
96737
97291
  if (dashboards.length > MAX_DISPLAY) {
96738
- console.log(import_picocolors32.default.dim(` \u2026 and ${dashboards.length - MAX_DISPLAY} more`));
97292
+ console.log(import_picocolors33.default.dim(` \u2026 and ${dashboards.length - MAX_DISPLAY} more`));
96739
97293
  }
96740
97294
  const input = (await ask(
96741
97295
  ` Select dashboards to use (e.g. 1,2 or Enter to type uid manually)`,
@@ -96786,12 +97340,12 @@ function redactUrl(raw) {
96786
97340
  // ../../packages/cli/src/commands/stop.ts
96787
97341
  init_cjs_shims();
96788
97342
  var import_node_child_process7 = require("child_process");
96789
- var import_node_fs14 = require("fs");
97343
+ var import_node_fs16 = require("fs");
96790
97344
  var import_node_util4 = require("util");
96791
- var import_node_path16 = require("path");
96792
- var import_picocolors33 = __toESM(require_picocolors(), 1);
97345
+ var import_node_path18 = require("path");
97346
+ var import_picocolors34 = __toESM(require_picocolors(), 1);
96793
97347
  var execFileAsync = (0, import_node_util4.promisify)(import_node_child_process7.execFile);
96794
- var unlinkAsync = (0, import_node_util4.promisify)(import_node_fs14.unlink);
97348
+ var unlinkAsync = (0, import_node_util4.promisify)(import_node_fs16.unlink);
96795
97349
  var SPAWNED_HOST_FILE2 = "spawned-host.json";
96796
97350
  var START_TIME_TOLERANCE_S = 60;
96797
97351
  var STOP_WAIT_MS = 5e3;
@@ -96806,30 +97360,30 @@ async function runStop(opts) {
96806
97360
  const root = findRepoRoot(cwd) ?? cwd;
96807
97361
  const hostUrl = readSourceHostUrl(root);
96808
97362
  if (!hostUrl) {
96809
- console.log(import_picocolors33.default.dim("No source-intelligence host found for this repo (.horus/source/host.json absent)."));
97363
+ console.log(import_picocolors34.default.dim("No source-intelligence host found for this repo (.horus/source/host.json absent)."));
96810
97364
  return 0;
96811
97365
  }
96812
97366
  return await stopHost(root, hostUrl);
96813
97367
  } catch (err) {
96814
- console.error(import_picocolors33.default.red(err.message));
97368
+ console.error(import_picocolors34.default.red(err.message));
96815
97369
  return 1;
96816
97370
  }
96817
97371
  }
96818
97372
  async function stopHost(root, hostUrl) {
96819
97373
  const alive = await isHostHealthy(hostUrl);
96820
97374
  if (!alive) {
96821
- console.log(import_picocolors33.default.dim(`Host ${hostUrl} is already stopped.`));
97375
+ console.log(import_picocolors34.default.dim(`Host ${hostUrl} is already stopped.`));
96822
97376
  return 0;
96823
97377
  }
96824
97378
  const port = extractPort(hostUrl);
96825
97379
  if (port === null) {
96826
- console.error(import_picocolors33.default.red(`Cannot determine port from host URL: ${hostUrl}`));
97380
+ console.error(import_picocolors34.default.red(`Cannot determine port from host URL: ${hostUrl}`));
96827
97381
  return 1;
96828
97382
  }
96829
97383
  const spawned = readSpawnedHost(root);
96830
97384
  if (spawned === null) {
96831
97385
  console.error(
96832
- import_picocolors33.default.red(
97386
+ import_picocolors34.default.red(
96833
97387
  `No ownership record found (.horus/${SPAWNED_HOST_FILE2} absent). Horus will not stop a host it did not spawn.`
96834
97388
  )
96835
97389
  );
@@ -96837,12 +97391,12 @@ async function stopHost(root, hostUrl) {
96837
97391
  }
96838
97392
  const recordError = validateSpawnedRecord(spawned);
96839
97393
  if (recordError !== null) {
96840
- console.error(import_picocolors33.default.red(`Ownership record is malformed: ${recordError}. Aborting for safety.`));
97394
+ console.error(import_picocolors34.default.red(`Ownership record is malformed: ${recordError}. Aborting for safety.`));
96841
97395
  return 1;
96842
97396
  }
96843
97397
  if (spawned.port !== port) {
96844
97398
  console.error(
96845
- import_picocolors33.default.red(
97399
+ import_picocolors34.default.red(
96846
97400
  `Ownership record port (${spawned.port}) does not match host URL port (${port}). Record may be stale.`
96847
97401
  )
96848
97402
  );
@@ -96850,7 +97404,7 @@ async function stopHost(root, hostUrl) {
96850
97404
  }
96851
97405
  if (spawned.root !== root) {
96852
97406
  console.error(
96853
- import_picocolors33.default.red(
97407
+ import_picocolors34.default.red(
96854
97408
  `Ownership record root (${spawned.root}) does not match resolved root (${root}). Record may be stale.`
96855
97409
  )
96856
97410
  );
@@ -96858,9 +97412,9 @@ async function stopHost(root, hostUrl) {
96858
97412
  }
96859
97413
  const info = await getProcessInfo(spawned.pid);
96860
97414
  if (info === null) {
96861
- console.log(import_picocolors33.default.dim(`Process pid ${spawned.pid} is no longer running \u2014 already stopped.`));
97415
+ console.log(import_picocolors34.default.dim(`Process pid ${spawned.pid} is no longer running \u2014 already stopped.`));
96862
97416
  try {
96863
- await unlinkAsync((0, import_node_path16.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
97417
+ await unlinkAsync((0, import_node_path18.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
96864
97418
  } catch {
96865
97419
  }
96866
97420
  return 0;
@@ -96871,7 +97425,7 @@ async function stopHost(root, hostUrl) {
96871
97425
  );
96872
97426
  if (!hostPortRe.test(info.args)) {
96873
97427
  console.error(
96874
- import_picocolors33.default.red(
97428
+ import_picocolors34.default.red(
96875
97429
  `Pid ${spawned.pid} args do not match "horus-source host --port ${portStr}". Got: "${info.args.slice(0, 120)}". Aborting for safety.`
96876
97430
  )
96877
97431
  );
@@ -96880,12 +97434,12 @@ async function stopHost(root, hostUrl) {
96880
97434
  const startTs = new Date(spawned.startedAt).getTime();
96881
97435
  const recordedAgeS = Math.round((Date.now() - startTs) / 1e3);
96882
97436
  if (!Number.isFinite(info.etimeSeconds)) {
96883
- console.error(import_picocolors33.default.red(`Could not read elapsed time for pid ${spawned.pid}. Aborting for safety.`));
97437
+ console.error(import_picocolors34.default.red(`Could not read elapsed time for pid ${spawned.pid}. Aborting for safety.`));
96884
97438
  return 1;
96885
97439
  }
96886
97440
  if (Math.abs(info.etimeSeconds - recordedAgeS) > START_TIME_TOLERANCE_S) {
96887
97441
  console.error(
96888
- import_picocolors33.default.red(
97442
+ import_picocolors34.default.red(
96889
97443
  `Pid ${spawned.pid} age mismatch: record says ~${recordedAgeS}s, process reports ${info.etimeSeconds}s elapsed. Possible PID reuse \u2014 aborting for safety.`
96890
97444
  )
96891
97445
  );
@@ -96897,9 +97451,9 @@ async function stopHost(root, hostUrl) {
96897
97451
  signaled = true;
96898
97452
  } catch (err) {
96899
97453
  if (err.code === "ESRCH") {
96900
- console.log(import_picocolors33.default.dim(`Process pid ${spawned.pid} exited before signal \u2014 already stopped.`));
97454
+ console.log(import_picocolors34.default.dim(`Process pid ${spawned.pid} exited before signal \u2014 already stopped.`));
96901
97455
  } else {
96902
- console.error(import_picocolors33.default.red(`Failed to signal pid ${spawned.pid}: ${err.message}`));
97456
+ console.error(import_picocolors34.default.red(`Failed to signal pid ${spawned.pid}: ${err.message}`));
96903
97457
  return 1;
96904
97458
  }
96905
97459
  }
@@ -96915,18 +97469,18 @@ async function stopHost(root, hostUrl) {
96915
97469
  }
96916
97470
  if (!exited) {
96917
97471
  console.error(
96918
- import_picocolors33.default.red(
97472
+ import_picocolors34.default.red(
96919
97473
  `Host pid ${spawned.pid} did not exit within ${STOP_WAIT_MS / 1e3}s after SIGTERM.`
96920
97474
  )
96921
97475
  );
96922
97476
  return 1;
96923
97477
  }
96924
97478
  console.log(
96925
- `${import_picocolors33.default.green("\u2713")} Stopped source-intelligence host ` + import_picocolors33.default.dim(`(pid ${spawned.pid}, port ${port})`) + ` for ${root}`
97479
+ `${import_picocolors34.default.green("\u2713")} Stopped source-intelligence host ` + import_picocolors34.default.dim(`(pid ${spawned.pid}, port ${port})`) + ` for ${root}`
96926
97480
  );
96927
97481
  }
96928
97482
  try {
96929
- await unlinkAsync((0, import_node_path16.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
97483
+ await unlinkAsync((0, import_node_path18.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
96930
97484
  } catch {
96931
97485
  }
96932
97486
  return 0;
@@ -96935,7 +97489,7 @@ async function stopAll() {
96935
97489
  const registry2 = readRegistry();
96936
97490
  const projects2 = Object.entries(registry2.projects);
96937
97491
  if (projects2.length === 0) {
96938
- console.log(import_picocolors33.default.dim("No registered projects."));
97492
+ console.log(import_picocolors34.default.dim("No registered projects."));
96939
97493
  return 0;
96940
97494
  }
96941
97495
  let stopped = 0;
@@ -96945,17 +97499,17 @@ async function stopAll() {
96945
97499
  if (!hostUrl) continue;
96946
97500
  const alive = await isHostHealthy(hostUrl);
96947
97501
  if (!alive) continue;
96948
- console.log(` Stopping ${import_picocolors33.default.bold(name)} ${import_picocolors33.default.dim(`(${hostUrl})`)}`);
97502
+ console.log(` Stopping ${import_picocolors34.default.bold(name)} ${import_picocolors34.default.dim(`(${hostUrl})`)}`);
96949
97503
  const code = await stopHost(entry2.root, hostUrl);
96950
97504
  if (code === 0) stopped++;
96951
97505
  else failed++;
96952
97506
  }
96953
97507
  if (stopped === 0 && failed === 0) {
96954
- console.log(import_picocolors33.default.dim("No running source-intelligence hosts found."));
97508
+ console.log(import_picocolors34.default.dim("No running source-intelligence hosts found."));
96955
97509
  } else {
96956
97510
  console.log(
96957
97511
  `
96958
- Stopped ${stopped} host(s)${failed > 0 ? import_picocolors33.default.red(`, ${failed} failed`) : ""}.`
97512
+ Stopped ${stopped} host(s)${failed > 0 ? import_picocolors34.default.red(`, ${failed} failed`) : ""}.`
96959
97513
  );
96960
97514
  }
96961
97515
  return failed > 0 ? 1 : 0;
@@ -97007,12 +97561,12 @@ function extractPort(hostUrl) {
97007
97561
 
97008
97562
  // ../../packages/cli/src/commands/hosts.ts
97009
97563
  init_cjs_shims();
97010
- var import_picocolors34 = __toESM(require_picocolors(), 1);
97564
+ var import_picocolors35 = __toESM(require_picocolors(), 1);
97011
97565
  async function runHosts() {
97012
97566
  const registry2 = readRegistry();
97013
97567
  const projects2 = Object.entries(registry2.projects);
97014
97568
  if (projects2.length === 0) {
97015
- console.log(import_picocolors34.default.dim("No registered projects. Run `horus index` in a repo first."));
97569
+ console.log(import_picocolors35.default.dim("No registered projects. Run `horus index` in a repo first."));
97016
97570
  return 0;
97017
97571
  }
97018
97572
  const rows = [];
@@ -97029,29 +97583,29 @@ async function runHosts() {
97029
97583
  });
97030
97584
  const anyHost = rows.some((r) => r.hostUrl !== null);
97031
97585
  if (!anyHost) {
97032
- console.log(import_picocolors34.default.dim("No source-intelligence hosts found. Run `horus index` to start one."));
97586
+ console.log(import_picocolors35.default.dim("No source-intelligence hosts found. Run `horus index` to start one."));
97033
97587
  return 0;
97034
97588
  }
97035
97589
  console.log("");
97036
97590
  for (const row of rows) {
97037
97591
  if (row.hostUrl === null) continue;
97038
- const status = row.healthy ? import_picocolors34.default.green("\u25CF running") : import_picocolors34.default.red("\u25CF stopped");
97592
+ const status = row.healthy ? import_picocolors35.default.green("\u25CF running") : import_picocolors35.default.red("\u25CF stopped");
97039
97593
  const port = extractPort2(row.hostUrl) ?? "?";
97040
97594
  console.log(
97041
- ` ${status} ${import_picocolors34.default.bold(row.name.padEnd(24))} port ${String(port).padEnd(6)} ${import_picocolors34.default.dim(row.root)}`
97595
+ ` ${status} ${import_picocolors35.default.bold(row.name.padEnd(24))} port ${String(port).padEnd(6)} ${import_picocolors35.default.dim(row.root)}`
97042
97596
  );
97043
97597
  }
97044
97598
  console.log("");
97045
97599
  const noHost = rows.filter((r) => r.hostUrl === null);
97046
97600
  if (noHost.length > 0) {
97047
97601
  for (const row of noHost) {
97048
- console.log(` ${import_picocolors34.default.dim("\u25CB no host")} ${import_picocolors34.default.dim(row.name)}`);
97602
+ console.log(` ${import_picocolors35.default.dim("\u25CB no host")} ${import_picocolors35.default.dim(row.name)}`);
97049
97603
  }
97050
97604
  console.log("");
97051
97605
  }
97052
97606
  const running = rows.filter((r) => r.healthy).length;
97053
97607
  console.log(
97054
- import_picocolors34.default.dim(
97608
+ import_picocolors35.default.dim(
97055
97609
  `${running} running \xB7 horus stop to reap \xB7 horus stop --all to stop everything`
97056
97610
  )
97057
97611
  );
@@ -97068,12 +97622,12 @@ function extractPort2(hostUrl) {
97068
97622
 
97069
97623
  // ../../packages/cli/src/commands/doctor.ts
97070
97624
  init_cjs_shims();
97071
- var import_picocolors35 = __toESM(require_picocolors(), 1);
97625
+ var import_picocolors36 = __toESM(require_picocolors(), 1);
97072
97626
  var DEFAULT_DB_URL4 = "postgresql://horus:horus@localhost:5433/horus";
97073
97627
  function mark2(status) {
97074
- if (status === "pass") return import_picocolors35.default.green("\u2713");
97075
- if (status === "warn") return import_picocolors35.default.yellow("~");
97076
- return import_picocolors35.default.red("\u2717");
97628
+ if (status === "pass") return import_picocolors36.default.green("\u2713");
97629
+ if (status === "warn") return import_picocolors36.default.yellow("~");
97630
+ return import_picocolors36.default.red("\u2717");
97077
97631
  }
97078
97632
  async function runDoctor(opts) {
97079
97633
  const cwd = opts?.cwd ?? process.cwd();
@@ -97300,11 +97854,11 @@ async function runDoctor(opts) {
97300
97854
  write(JSON.stringify(output, null, 2));
97301
97855
  return hasFailure ? 1 : 0;
97302
97856
  }
97303
- write(import_picocolors35.default.bold("\nHorus readiness check\n"));
97857
+ write(import_picocolors36.default.bold("\nHorus readiness check\n"));
97304
97858
  for (const check2 of checks) {
97305
- write(` ${mark2(check2.status)} ${import_picocolors35.default.bold(check2.label.padEnd(26))} ${import_picocolors35.default.dim(check2.detail)}`);
97859
+ write(` ${mark2(check2.status)} ${import_picocolors36.default.bold(check2.label.padEnd(26))} ${import_picocolors36.default.dim(check2.detail)}`);
97306
97860
  if (check2.next) {
97307
- write(` ${import_picocolors35.default.dim("\u2192 " + check2.next)}`);
97861
+ write(` ${import_picocolors36.default.dim("\u2192 " + check2.next)}`);
97308
97862
  }
97309
97863
  }
97310
97864
  write("");
@@ -97314,16 +97868,16 @@ async function runDoctor(opts) {
97314
97868
  // ../../packages/cli/src/commands/providers-doctor.ts
97315
97869
  init_cjs_shims();
97316
97870
  var import_node_child_process8 = require("child_process");
97317
- var import_picocolors36 = __toESM(require_picocolors(), 1);
97871
+ var import_picocolors37 = __toESM(require_picocolors(), 1);
97318
97872
  function statusMark(status) {
97319
- if (status === "ready") return import_picocolors36.default.green("\u2713");
97320
- if (status === "installed") return import_picocolors36.default.yellow("~");
97321
- return import_picocolors36.default.red("\u2717");
97873
+ if (status === "ready") return import_picocolors37.default.green("\u2713");
97874
+ if (status === "installed") return import_picocolors37.default.yellow("~");
97875
+ return import_picocolors37.default.red("\u2717");
97322
97876
  }
97323
97877
  function statusLabel(status) {
97324
- if (status === "ready") return import_picocolors36.default.green("ready");
97325
- if (status === "installed") return import_picocolors36.default.yellow("installed (not configured)");
97326
- return import_picocolors36.default.dim("not found on PATH");
97878
+ if (status === "ready") return import_picocolors37.default.green("ready");
97879
+ if (status === "installed") return import_picocolors37.default.yellow("installed (not configured)");
97880
+ return import_picocolors37.default.dim("not found on PATH");
97327
97881
  }
97328
97882
  function detectBinary(id) {
97329
97883
  const result = (0, import_node_child_process8.spawnSync)(id, ["--version"], { stdio: "pipe", timeout: 2e3 });
@@ -97340,7 +97894,7 @@ async function runProvidersDoctorCommand(opts) {
97340
97894
  const registry2 = opts?.registry ?? DEFAULT_LOCAL_PROVIDER_REGISTRY;
97341
97895
  const write = opts?.write ?? ((line2) => console.log(line2));
97342
97896
  const results = buildProviderResults(registry2, opts?._detect);
97343
- write(import_picocolors36.default.bold("\nLocal AI providers\n"));
97897
+ write(import_picocolors37.default.bold("\nLocal AI providers\n"));
97344
97898
  for (const result of results) {
97345
97899
  const descriptor = registry2.get(result.id);
97346
97900
  const name = descriptor?.displayName ?? result.id;
@@ -97348,11 +97902,11 @@ async function runProvidersDoctorCommand(opts) {
97348
97902
  ` ${statusMark(result.status)} ${result.id.padEnd(8)} ${name.padEnd(22)} ${statusLabel(result.status)}`
97349
97903
  );
97350
97904
  if (result.status !== "ready" && result.detail) {
97351
- write(` ${import_picocolors36.default.dim("\u2192 " + result.detail)}`);
97905
+ write(` ${import_picocolors37.default.dim("\u2192 " + result.detail)}`);
97352
97906
  }
97353
97907
  }
97354
97908
  write("");
97355
- write(import_picocolors36.default.bold("Cloud AI providers\n"));
97909
+ write(import_picocolors37.default.bold("Cloud AI providers\n"));
97356
97910
  let source = null;
97357
97911
  if (opts?._anthropicKey !== void 0) {
97358
97912
  source = opts._anthropicKey ? "env" : null;
@@ -97367,18 +97921,18 @@ async function runProvidersDoctorCommand(opts) {
97367
97921
  }
97368
97922
  if (source === "config") {
97369
97923
  write(
97370
- ` ${import_picocolors36.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors36.default.green("configured (local .horus config)")}`
97924
+ ` ${import_picocolors37.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors37.default.green("configured (local .horus config)")}`
97371
97925
  );
97372
97926
  } else if (source === "env") {
97373
97927
  write(
97374
- ` ${import_picocolors36.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors36.default.green("configured (ANTHROPIC_API_KEY env)")}`
97928
+ ` ${import_picocolors37.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors37.default.green("configured (ANTHROPIC_API_KEY env)")}`
97375
97929
  );
97376
97930
  } else {
97377
97931
  write(
97378
- ` ${import_picocolors36.default.red("\u2717")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors36.default.dim("not configured")}`
97932
+ ` ${import_picocolors37.default.red("\u2717")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors37.default.dim("not configured")}`
97379
97933
  );
97380
97934
  write(
97381
- ` ${import_picocolors36.default.dim("\u2192 run `horus connect ai` (or set ANTHROPIC_API_KEY) to enable `horus investigate --ai`")}`
97935
+ ` ${import_picocolors37.default.dim("\u2192 run `horus connect ai` (or set ANTHROPIC_API_KEY) to enable `horus investigate --ai`")}`
97382
97936
  );
97383
97937
  }
97384
97938
  write("");
@@ -97387,9 +97941,9 @@ async function runProvidersDoctorCommand(opts) {
97387
97941
 
97388
97942
  // ../../packages/cli/src/commands/generate-config.ts
97389
97943
  init_cjs_shims();
97390
- var import_node_fs15 = require("fs");
97391
- var import_node_path17 = require("path");
97392
- var import_picocolors37 = __toESM(require_picocolors(), 1);
97944
+ var import_node_fs17 = require("fs");
97945
+ var import_node_path19 = require("path");
97946
+ var import_picocolors38 = __toESM(require_picocolors(), 1);
97393
97947
  function configTemplate(name, repoPath) {
97394
97948
  return `export default {
97395
97949
  database: {
@@ -97437,39 +97991,39 @@ async function runGenerateConfig(opts) {
97437
97991
  const defaults = localConfigPath2 != null ? projectDefaults(localConfigPath2) : null;
97438
97992
  const hasLocalConfig = defaults != null;
97439
97993
  const defaultOut = hasLocalConfig ? "horus.config.example.js" : "horus.config.js";
97440
- const outPath = (0, import_node_path17.resolve)(cwd, opts.out ?? defaultOut);
97994
+ const outPath = (0, import_node_path19.resolve)(cwd, opts.out ?? defaultOut);
97441
97995
  const name = opts.name ?? defaults?.name ?? "my-project";
97442
97996
  const repoPath = opts.repo ?? defaults?.repoPath ?? `/path/to/${name}`;
97443
- if ((0, import_node_fs15.existsSync)(outPath) && !opts.force) {
97444
- log(`${import_picocolors37.default.red("\u2717")} ${outPath} already exists`);
97445
- log(import_picocolors37.default.dim(" pass --force to overwrite"));
97997
+ if ((0, import_node_fs17.existsSync)(outPath) && !opts.force) {
97998
+ log(`${import_picocolors38.default.red("\u2717")} ${outPath} already exists`);
97999
+ log(import_picocolors38.default.dim(" pass --force to overwrite"));
97446
98000
  return 1;
97447
98001
  }
97448
98002
  try {
97449
- (0, import_node_fs15.mkdirSync)((0, import_node_path17.dirname)(outPath), { recursive: true });
97450
- (0, import_node_fs15.writeFileSync)(outPath, configTemplate(name, repoPath), "utf8");
98003
+ (0, import_node_fs17.mkdirSync)((0, import_node_path19.dirname)(outPath), { recursive: true });
98004
+ (0, import_node_fs17.writeFileSync)(outPath, configTemplate(name, repoPath), "utf8");
97451
98005
  } catch (err) {
97452
- log(`${import_picocolors37.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
98006
+ log(`${import_picocolors38.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
97453
98007
  return 1;
97454
98008
  }
97455
- log(`${import_picocolors37.default.green("\u2713")} Created ${outPath}`);
97456
- log(import_picocolors37.default.dim(` project: ${name}`));
97457
- log(import_picocolors37.default.dim(` repo: ${repoPath}`));
98009
+ log(`${import_picocolors38.default.green("\u2713")} Created ${outPath}`);
98010
+ log(import_picocolors38.default.dim(` project: ${name}`));
98011
+ log(import_picocolors38.default.dim(` repo: ${repoPath}`));
97458
98012
  if (hasLocalConfig && localConfigPath2 != null) {
97459
98013
  log("");
97460
- log(import_picocolors37.default.yellow("Note:") + ` an initialized Horus project config exists at ${localConfigPath2}`);
98014
+ log(import_picocolors38.default.yellow("Note:") + ` an initialized Horus project config exists at ${localConfigPath2}`);
97461
98015
  log(" \u2022 .horus/config.json \u2014 project config used by `horus investigate` from this repo");
97462
98016
  log(" \u2022 horus.config.js \u2014 standalone/global config used with `horus doctor --config <path>`");
97463
- log(import_picocolors37.default.dim(` next: review ${outPath} and copy/adapt it as needed`));
98017
+ log(import_picocolors38.default.dim(` next: review ${outPath} and copy/adapt it as needed`));
97464
98018
  } else {
97465
- log(import_picocolors37.default.dim(` next: horus doctor --config ${outPath}`));
98019
+ log(import_picocolors38.default.dim(` next: horus doctor --config ${outPath}`));
97466
98020
  }
97467
98021
  return 0;
97468
98022
  }
97469
98023
 
97470
98024
  // ../../packages/cli/src/commands/readiness.ts
97471
98025
  init_cjs_shims();
97472
- var import_picocolors38 = __toESM(require_picocolors(), 1);
98026
+ var import_picocolors39 = __toESM(require_picocolors(), 1);
97473
98027
  var READINESS_AI_CONTRACT = `Provide a clearly separated AI readiness summary with:
97474
98028
 
97475
98029
  Overall assessment
@@ -97492,9 +98046,9 @@ Confidence / gaps
97492
98046
  - Which checks Horus could verify vs. which require manual confirmation`;
97493
98047
  var DEFAULT_DB_URL5 = "postgresql://horus:horus@localhost:5433/horus";
97494
98048
  function mark3(status) {
97495
- if (status === "pass") return import_picocolors38.default.green("\u2713");
97496
- if (status === "warn") return import_picocolors38.default.yellow("~");
97497
- return import_picocolors38.default.red("\u2717");
98049
+ if (status === "pass") return import_picocolors39.default.green("\u2713");
98050
+ if (status === "warn") return import_picocolors39.default.yellow("~");
98051
+ return import_picocolors39.default.red("\u2717");
97498
98052
  }
97499
98053
  async function runReadiness(opts) {
97500
98054
  const cwd = opts?.cwd ?? process.cwd();
@@ -97649,20 +98203,20 @@ async function runReadiness(opts) {
97649
98203
  }
97650
98204
  const blockingChecks = checks.filter((c) => c.blocking);
97651
98205
  const optionalChecks = checks.filter((c) => !c.blocking);
97652
- write(import_picocolors38.default.bold("\nHorus release readiness\n"));
97653
- write(import_picocolors38.default.bold(" Blocking"));
98206
+ write(import_picocolors39.default.bold("\nHorus release readiness\n"));
98207
+ write(import_picocolors39.default.bold(" Blocking"));
97654
98208
  for (const check2 of blockingChecks) {
97655
- write(` ${mark3(check2.status)} ${import_picocolors38.default.bold(check2.label.padEnd(22))} ${import_picocolors38.default.dim(check2.detail)}`);
98209
+ write(` ${mark3(check2.status)} ${import_picocolors39.default.bold(check2.label.padEnd(22))} ${import_picocolors39.default.dim(check2.detail)}`);
97656
98210
  if (check2.next) {
97657
- write(` ${import_picocolors38.default.dim("\u2192 " + check2.next)}`);
98211
+ write(` ${import_picocolors39.default.dim("\u2192 " + check2.next)}`);
97658
98212
  }
97659
98213
  }
97660
98214
  write("");
97661
- write(import_picocolors38.default.bold(" Optional"));
98215
+ write(import_picocolors39.default.bold(" Optional"));
97662
98216
  for (const check2 of optionalChecks) {
97663
- write(` ${mark3(check2.status)} ${import_picocolors38.default.bold(check2.label.padEnd(22))} ${import_picocolors38.default.dim(check2.detail)}`);
98217
+ write(` ${mark3(check2.status)} ${import_picocolors39.default.bold(check2.label.padEnd(22))} ${import_picocolors39.default.dim(check2.detail)}`);
97664
98218
  if (check2.next) {
97665
- write(` ${import_picocolors38.default.dim("\u2192 " + check2.next)}`);
98219
+ write(` ${import_picocolors39.default.dim("\u2192 " + check2.next)}`);
97666
98220
  }
97667
98221
  }
97668
98222
  write("");
@@ -97670,21 +98224,21 @@ async function runReadiness(opts) {
97670
98224
  const optionalWarns = optionalChecks.filter((c) => c.status === "warn").length;
97671
98225
  if (blockingFails.length === 0) {
97672
98226
  if (optionalWarns === 0) {
97673
- write(import_picocolors38.default.green(" Ready for demo/release."));
98227
+ write(import_picocolors39.default.green(" Ready for demo/release."));
97674
98228
  } else {
97675
98229
  write(
97676
- import_picocolors38.default.yellow(
98230
+ import_picocolors39.default.yellow(
97677
98231
  ` Ready for a basic demo. ${optionalWarns} optional item(s) not configured \u2014 investigation evidence will be limited.`
97678
98232
  )
97679
98233
  );
97680
98234
  }
97681
98235
  } else {
97682
98236
  write(
97683
- import_picocolors38.default.red(
98237
+ import_picocolors39.default.red(
97684
98238
  ` Not ready. ${blockingFails.length} blocking item(s) must be resolved before demo/release.`
97685
98239
  )
97686
98240
  );
97687
- write(import_picocolors38.default.dim(" Re-run `horus readiness` after resolving the items above."));
98241
+ write(import_picocolors39.default.dim(" Re-run `horus readiness` after resolving the items above."));
97688
98242
  }
97689
98243
  write("");
97690
98244
  if (opts?.ai) {
@@ -97699,7 +98253,7 @@ async function runReadiness(opts) {
97699
98253
  });
97700
98254
  console.log("\n" + renderInterpretation(result));
97701
98255
  if (!result.ok) {
97702
- console.error(import_picocolors38.default.yellow(`[ai] ${result.warning}`));
98256
+ console.error(import_picocolors39.default.yellow(`[ai] ${result.warning}`));
97703
98257
  }
97704
98258
  }
97705
98259
  return blockingFails.length > 0 ? 1 : 0;
@@ -97707,33 +98261,33 @@ async function runReadiness(opts) {
97707
98261
 
97708
98262
  // ../../packages/cli/src/commands/skill.ts
97709
98263
  init_cjs_shims();
97710
- var import_node_fs16 = require("fs");
97711
- var import_node_path18 = require("path");
97712
- var import_node_os3 = require("os");
97713
- var import_promises = require("readline/promises");
97714
- var import_picocolors39 = __toESM(require_picocolors(), 1);
98264
+ var import_node_fs18 = require("fs");
98265
+ var import_node_path20 = require("path");
98266
+ var import_node_os4 = require("os");
98267
+ var import_promises2 = require("readline/promises");
98268
+ var import_picocolors40 = __toESM(require_picocolors(), 1);
97715
98269
  var SUPPORTED_TARGETS = ["claude", "codex", "gemini", "cursor", "generic"];
97716
98270
  function isValidTarget(target) {
97717
98271
  return SUPPORTED_TARGETS.includes(target);
97718
98272
  }
97719
98273
  function getLegacyClaudeSkillPath(opts) {
97720
- const base2 = opts.global ? (0, import_node_os3.homedir)() : opts.cwd ?? process.cwd();
97721
- return (0, import_node_path18.join)(base2, ".claude", "skills", "horus.md");
98274
+ const base2 = opts.global ? (0, import_node_os4.homedir)() : opts.cwd ?? process.cwd();
98275
+ return (0, import_node_path20.join)(base2, ".claude", "skills", "horus.md");
97722
98276
  }
97723
98277
  function getSkillInstallPath(target, opts) {
97724
- const base2 = opts.global ? (0, import_node_os3.homedir)() : opts.cwd ?? process.cwd();
98278
+ const base2 = opts.global ? (0, import_node_os4.homedir)() : opts.cwd ?? process.cwd();
97725
98279
  switch (target) {
97726
98280
  case "claude":
97727
- return (0, import_node_path18.join)(base2, ".claude", "skills", "horus", "SKILL.md");
98281
+ return (0, import_node_path20.join)(base2, ".claude", "skills", "horus", "SKILL.md");
97728
98282
  case "gemini":
97729
- return opts.global ? (0, import_node_path18.join)(base2, ".gemini", "GEMINI.md") : (0, import_node_path18.join)(base2, "GEMINI.md");
98283
+ return opts.global ? (0, import_node_path20.join)(base2, ".gemini", "GEMINI.md") : (0, import_node_path20.join)(base2, "GEMINI.md");
97730
98284
  case "cursor":
97731
- return opts.global ? (0, import_node_path18.join)(base2, ".cursorrules") : (0, import_node_path18.join)(base2, ".cursor", "rules", "horus.mdc");
98285
+ return opts.global ? (0, import_node_path20.join)(base2, ".cursorrules") : (0, import_node_path20.join)(base2, ".cursor", "rules", "horus.mdc");
97732
98286
  case "codex":
97733
- return opts.global ? (0, import_node_path18.join)(base2, ".codex", "AGENTS.md") : (0, import_node_path18.join)(base2, "AGENTS.md");
98287
+ return opts.global ? (0, import_node_path20.join)(base2, ".codex", "AGENTS.md") : (0, import_node_path20.join)(base2, "AGENTS.md");
97734
98288
  case "generic":
97735
98289
  default:
97736
- return (0, import_node_path18.join)(base2, ".horus", "skills", `horus-${target}.md`);
98290
+ return (0, import_node_path20.join)(base2, ".horus", "skills", `horus-${target}.md`);
97737
98291
  }
97738
98292
  }
97739
98293
  var BASE_SKILL = `# Horus Grounded Investigation Skill
@@ -98018,7 +98572,7 @@ async function confirm(message) {
98018
98572
  if (!process.stdin.isTTY) {
98019
98573
  return false;
98020
98574
  }
98021
- const rl = (0, import_promises.createInterface)({ input: process.stdin, output: process.stdout });
98575
+ const rl = (0, import_promises2.createInterface)({ input: process.stdin, output: process.stdout });
98022
98576
  try {
98023
98577
  const answer = await rl.question(`${message} [y/N] `);
98024
98578
  return /^y(es)?$/i.test(answer.trim());
@@ -98029,66 +98583,66 @@ async function confirm(message) {
98029
98583
  async function runSkillInstall(target, opts) {
98030
98584
  const log = opts.write ?? ((line2) => console.log(line2));
98031
98585
  if (!isValidTarget(target)) {
98032
- log(`${import_picocolors39.default.red("\u2717")} Unknown target: ${import_picocolors39.default.bold(target)}`);
98033
- log(import_picocolors39.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
98586
+ log(`${import_picocolors40.default.red("\u2717")} Unknown target: ${import_picocolors40.default.bold(target)}`);
98587
+ log(import_picocolors40.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
98034
98588
  return 1;
98035
98589
  }
98036
98590
  const outPath = getSkillInstallPath(target, { global: opts.global, cwd: opts.cwd });
98037
98591
  const content = generateSkillContent(target);
98038
98592
  if (target === "claude") {
98039
98593
  const legacyPath = getLegacyClaudeSkillPath({ global: opts.global, cwd: opts.cwd });
98040
- if ((0, import_node_fs16.existsSync)(legacyPath)) {
98594
+ if ((0, import_node_fs18.existsSync)(legacyPath)) {
98041
98595
  if (!opts.force) {
98042
98596
  const confirmed = await (opts.confirm ?? confirm)(
98043
98597
  `Found old flat skill file at ${legacyPath}. Replace it with the new directory layout?`
98044
98598
  );
98045
98599
  if (!confirmed) {
98046
- log(`${import_picocolors39.default.yellow("!")} Migration cancelled; existing skill left untouched.`);
98047
- log(import_picocolors39.default.dim(" Pass --force to replace without prompting."));
98600
+ log(`${import_picocolors40.default.yellow("!")} Migration cancelled; existing skill left untouched.`);
98601
+ log(import_picocolors40.default.dim(" Pass --force to replace without prompting."));
98048
98602
  return 1;
98049
98603
  }
98050
98604
  }
98051
98605
  try {
98052
- (0, import_node_fs16.rmSync)(legacyPath);
98053
- log(`${import_picocolors39.default.yellow("!")} Replaced old flat skill file: ${legacyPath}`);
98606
+ (0, import_node_fs18.rmSync)(legacyPath);
98607
+ log(`${import_picocolors40.default.yellow("!")} Replaced old flat skill file: ${legacyPath}`);
98054
98608
  } catch (err) {
98055
- log(`${import_picocolors39.default.red("\u2717")} Could not remove legacy skill file ${legacyPath}: ${err.message}`);
98609
+ log(`${import_picocolors40.default.red("\u2717")} Could not remove legacy skill file ${legacyPath}: ${err.message}`);
98056
98610
  return 1;
98057
98611
  }
98058
98612
  }
98059
98613
  }
98060
- if ((0, import_node_fs16.existsSync)(outPath) && !opts.force) {
98061
- log(`${import_picocolors39.default.red("\u2717")} ${outPath} already exists`);
98062
- log(import_picocolors39.default.dim(" pass --force to overwrite"));
98614
+ if ((0, import_node_fs18.existsSync)(outPath) && !opts.force) {
98615
+ log(`${import_picocolors40.default.red("\u2717")} ${outPath} already exists`);
98616
+ log(import_picocolors40.default.dim(" pass --force to overwrite"));
98063
98617
  return 1;
98064
98618
  }
98065
98619
  try {
98066
- (0, import_node_fs16.mkdirSync)((0, import_node_path18.dirname)(outPath), { recursive: true });
98067
- (0, import_node_fs16.writeFileSync)(outPath, content, "utf8");
98620
+ (0, import_node_fs18.mkdirSync)((0, import_node_path20.dirname)(outPath), { recursive: true });
98621
+ (0, import_node_fs18.writeFileSync)(outPath, content, "utf8");
98068
98622
  } catch (err) {
98069
- log(`${import_picocolors39.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
98623
+ log(`${import_picocolors40.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
98070
98624
  return 1;
98071
98625
  }
98072
- log(`${import_picocolors39.default.green("\u2713")} Horus skill installed \u2192 ${outPath}`);
98626
+ log(`${import_picocolors40.default.green("\u2713")} Horus skill installed \u2192 ${outPath}`);
98073
98627
  if (target === "claude") {
98074
- log(import_picocolors39.default.dim(" Claude Code will load this skill automatically."));
98075
- log(import_picocolors39.default.dim(" Invoke it with: /horus"));
98628
+ log(import_picocolors40.default.dim(" Claude Code will load this skill automatically."));
98629
+ log(import_picocolors40.default.dim(" Invoke it with: /horus"));
98076
98630
  } else if (target === "gemini") {
98077
- log(import_picocolors39.default.dim(" Gemini CLI will load this context file automatically."));
98631
+ log(import_picocolors40.default.dim(" Gemini CLI will load this context file automatically."));
98078
98632
  } else if (target === "cursor") {
98079
- log(import_picocolors39.default.dim(" Cursor will load this rule automatically."));
98633
+ log(import_picocolors40.default.dim(" Cursor will load this rule automatically."));
98080
98634
  } else if (target === "codex") {
98081
- log(import_picocolors39.default.dim(" Codex CLI will load this file automatically."));
98635
+ log(import_picocolors40.default.dim(" Codex CLI will load this file automatically."));
98082
98636
  } else {
98083
- log(import_picocolors39.default.dim(` Copy or reference this file in your ${target} agent instructions.`));
98637
+ log(import_picocolors40.default.dim(` Copy or reference this file in your ${target} agent instructions.`));
98084
98638
  }
98085
98639
  return 0;
98086
98640
  }
98087
98641
  async function runSkillPrint(target, opts) {
98088
98642
  const log = opts.write ?? ((line2) => console.log(line2));
98089
98643
  if (!isValidTarget(target)) {
98090
- log(`${import_picocolors39.default.red("\u2717")} Unknown target: ${import_picocolors39.default.bold(target)}`);
98091
- log(import_picocolors39.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
98644
+ log(`${import_picocolors40.default.red("\u2717")} Unknown target: ${import_picocolors40.default.bold(target)}`);
98645
+ log(import_picocolors40.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
98092
98646
  return 1;
98093
98647
  }
98094
98648
  process.stdout.write(generateSkillContent(target));
@@ -98097,8 +98651,8 @@ async function runSkillPrint(target, opts) {
98097
98651
  async function runSkillPath(target, opts) {
98098
98652
  const log = opts.write ?? ((line2) => console.log(line2));
98099
98653
  if (!isValidTarget(target)) {
98100
- log(`${import_picocolors39.default.red("\u2717")} Unknown target: ${import_picocolors39.default.bold(target)}`);
98101
- log(import_picocolors39.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
98654
+ log(`${import_picocolors40.default.red("\u2717")} Unknown target: ${import_picocolors40.default.bold(target)}`);
98655
+ log(import_picocolors40.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
98102
98656
  return 1;
98103
98657
  }
98104
98658
  const outPath = getSkillInstallPath(target, { global: opts.global, cwd: opts.cwd });
@@ -98108,11 +98662,11 @@ async function runSkillPath(target, opts) {
98108
98662
 
98109
98663
  // ../../packages/cli/src/commands/update.ts
98110
98664
  init_cjs_shims();
98111
- var import_node_fs17 = require("fs");
98112
- var import_node_path19 = require("path");
98113
- var import_node_os4 = require("os");
98114
- var import_node_crypto5 = require("crypto");
98115
- var import_picocolors40 = __toESM(require_picocolors(), 1);
98665
+ var import_node_fs19 = require("fs");
98666
+ var import_node_path21 = require("path");
98667
+ var import_node_os5 = require("os");
98668
+ var import_node_crypto7 = require("crypto");
98669
+ var import_picocolors41 = __toESM(require_picocolors(), 1);
98116
98670
  var RELEASES_API = "https://api.github.com/repos/meritt-dev/horus/releases/latest";
98117
98671
  function parseVersion(v) {
98118
98672
  const m = v.replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
@@ -98137,13 +98691,13 @@ async function fetchLatestRelease(_fetch = fetch) {
98137
98691
  return res.json();
98138
98692
  }
98139
98693
  function sha256hex(buf) {
98140
- return (0, import_node_crypto5.createHash)("sha256").update(buf).digest("hex");
98694
+ return (0, import_node_crypto7.createHash)("sha256").update(buf).digest("hex");
98141
98695
  }
98142
98696
  function resolveBinaryPath() {
98143
98697
  const raw = process.argv[1];
98144
98698
  if (!raw) throw new Error("Cannot determine binary path from process.argv[1]");
98145
98699
  try {
98146
- return (0, import_node_fs17.realpathSync)(raw);
98700
+ return (0, import_node_fs19.realpathSync)(raw);
98147
98701
  } catch {
98148
98702
  return raw;
98149
98703
  }
@@ -98153,33 +98707,33 @@ async function runUpdate(opts) {
98153
98707
  const _fetch = opts._fetch ?? fetch;
98154
98708
  const currentVersion = opts._currentVersion ?? HORUS_VERSION;
98155
98709
  write(`
98156
- ${import_picocolors40.default.bold("Horus update")}
98710
+ ${import_picocolors41.default.bold("Horus update")}
98157
98711
  `);
98158
- write(` Current version: ${import_picocolors40.default.cyan(currentVersion)}`);
98712
+ write(` Current version: ${import_picocolors41.default.cyan(currentVersion)}`);
98159
98713
  let release;
98160
98714
  try {
98161
98715
  release = await fetchLatestRelease(_fetch);
98162
98716
  } catch (e) {
98163
- write(` ${import_picocolors40.default.red("\u2717")} Could not reach GitHub: ${e.message}`);
98717
+ write(` ${import_picocolors41.default.red("\u2717")} Could not reach GitHub: ${e.message}`);
98164
98718
  return 1;
98165
98719
  }
98166
98720
  const latest = release.tag_name.replace(/^v/, "");
98167
- write(` Latest version: ${import_picocolors40.default.cyan(latest)}`);
98721
+ write(` Latest version: ${import_picocolors41.default.cyan(latest)}`);
98168
98722
  const newer = isNewer(latest, currentVersion);
98169
98723
  if (opts.check) {
98170
98724
  if (newer) {
98171
98725
  write(`
98172
- ${import_picocolors40.default.yellow("\u2192")} ${import_picocolors40.default.bold(latest)} is available.`);
98173
- write(` Run ${import_picocolors40.default.bold("horus update")} to upgrade.`);
98726
+ ${import_picocolors41.default.yellow("\u2192")} ${import_picocolors41.default.bold(latest)} is available.`);
98727
+ write(` Run ${import_picocolors41.default.bold("horus update")} to upgrade.`);
98174
98728
  } else {
98175
98729
  write(`
98176
- ${import_picocolors40.default.green("\u2713")} Already on the latest version.`);
98730
+ ${import_picocolors41.default.green("\u2713")} Already on the latest version.`);
98177
98731
  }
98178
98732
  return 0;
98179
98733
  }
98180
98734
  if (!newer && !opts.force) {
98181
98735
  write(`
98182
- ${import_picocolors40.default.green("\u2713")} Already on the latest version.`);
98736
+ ${import_picocolors41.default.green("\u2713")} Already on the latest version.`);
98183
98737
  return 0;
98184
98738
  }
98185
98739
  const tag = release.tag_name;
@@ -98188,8 +98742,8 @@ ${import_picocolors40.default.bold("Horus update")}
98188
98742
  const binaryAsset = release.assets.find((a) => a.name === assetName);
98189
98743
  const checksumAsset = release.assets.find((a) => a.name === checksumName);
98190
98744
  if (!binaryAsset) {
98191
- write(` ${import_picocolors40.default.red("\u2717")} Release ${tag} has no binary asset named '${assetName}'.`);
98192
- write(` Fallback: ${import_picocolors40.default.dim("curl -fsSL https://horus.sh/install.sh | bash")}`);
98745
+ write(` ${import_picocolors41.default.red("\u2717")} Release ${tag} has no binary asset named '${assetName}'.`);
98746
+ write(` Fallback: ${import_picocolors41.default.dim("curl -fsSL https://horus.sh/install.sh | bash")}`);
98193
98747
  return 1;
98194
98748
  }
98195
98749
  write(`
@@ -98199,7 +98753,7 @@ ${import_picocolors40.default.bold("Horus update")}
98199
98753
  redirect: "follow"
98200
98754
  });
98201
98755
  if (!binRes.ok || !binRes.body) {
98202
- write(` ${import_picocolors40.default.red("\u2717")} Download failed: ${binRes.status}`);
98756
+ write(` ${import_picocolors41.default.red("\u2717")} Download failed: ${binRes.status}`);
98203
98757
  return 1;
98204
98758
  }
98205
98759
  const binBuf = Buffer.from(await binRes.arrayBuffer());
@@ -98213,75 +98767,75 @@ ${import_picocolors40.default.bold("Horus update")}
98213
98767
  const expectedHash = csText.trim().split(/\s+/)[0];
98214
98768
  const actualHash = sha256hex(binBuf);
98215
98769
  if (expectedHash !== actualHash) {
98216
- write(` ${import_picocolors40.default.red("\u2717")} Checksum mismatch \u2014 download may be corrupt.`);
98770
+ write(` ${import_picocolors41.default.red("\u2717")} Checksum mismatch \u2014 download may be corrupt.`);
98217
98771
  write(` Expected: ${expectedHash}`);
98218
98772
  write(` Got: ${actualHash}`);
98219
98773
  return 1;
98220
98774
  }
98221
- write(` ${import_picocolors40.default.green("\u2713")} Checksum verified.`);
98775
+ write(` ${import_picocolors41.default.green("\u2713")} Checksum verified.`);
98222
98776
  }
98223
98777
  }
98224
98778
  let binaryPath;
98225
98779
  try {
98226
98780
  binaryPath = resolveBinaryPath();
98227
98781
  } catch (e) {
98228
- write(` ${import_picocolors40.default.red("\u2717")} ${e.message}`);
98782
+ write(` ${import_picocolors41.default.red("\u2717")} ${e.message}`);
98229
98783
  return 1;
98230
98784
  }
98231
- if (!(0, import_node_fs17.existsSync)(binaryPath)) {
98232
- write(` ${import_picocolors40.default.red("\u2717")} Binary not found at ${binaryPath}`);
98785
+ if (!(0, import_node_fs19.existsSync)(binaryPath)) {
98786
+ write(` ${import_picocolors41.default.red("\u2717")} Binary not found at ${binaryPath}`);
98233
98787
  return 1;
98234
98788
  }
98235
- const tmpPath = (0, import_node_path19.join)((0, import_node_os4.tmpdir)(), `horus-update-${tag}-${process.pid}`);
98789
+ const tmpPath = (0, import_node_path21.join)((0, import_node_os5.tmpdir)(), `horus-update-${tag}-${process.pid}`);
98236
98790
  const backupPath = `${binaryPath}.bak`;
98237
98791
  try {
98238
- (0, import_node_fs17.writeFileSync)(tmpPath, binBuf, { mode: 493 });
98792
+ (0, import_node_fs19.writeFileSync)(tmpPath, binBuf, { mode: 493 });
98239
98793
  } catch (e) {
98240
- write(` ${import_picocolors40.default.red("\u2717")} Could not write to temp dir: ${e.message}`);
98794
+ write(` ${import_picocolors41.default.red("\u2717")} Could not write to temp dir: ${e.message}`);
98241
98795
  return 1;
98242
98796
  }
98243
98797
  try {
98244
- (0, import_node_fs17.renameSync)(binaryPath, backupPath);
98798
+ (0, import_node_fs19.renameSync)(binaryPath, backupPath);
98245
98799
  try {
98246
- (0, import_node_fs17.renameSync)(tmpPath, binaryPath);
98247
- (0, import_node_fs17.chmodSync)(binaryPath, 493);
98800
+ (0, import_node_fs19.renameSync)(tmpPath, binaryPath);
98801
+ (0, import_node_fs19.chmodSync)(binaryPath, 493);
98248
98802
  } catch (e) {
98249
98803
  try {
98250
- (0, import_node_fs17.renameSync)(backupPath, binaryPath);
98804
+ (0, import_node_fs19.renameSync)(backupPath, binaryPath);
98251
98805
  } catch {
98252
98806
  }
98253
98807
  throw e;
98254
98808
  }
98255
98809
  try {
98256
- (0, import_node_fs17.unlinkSync)(backupPath);
98810
+ (0, import_node_fs19.unlinkSync)(backupPath);
98257
98811
  } catch {
98258
98812
  }
98259
98813
  } catch (e) {
98260
98814
  try {
98261
- (0, import_node_fs17.unlinkSync)(tmpPath);
98815
+ (0, import_node_fs19.unlinkSync)(tmpPath);
98262
98816
  } catch {
98263
98817
  }
98264
98818
  const msg = e.message ?? String(e);
98265
98819
  const needsSudo = e.code === "EACCES" || e.code === "EPERM";
98266
- write(` ${import_picocolors40.default.red("\u2717")} Could not replace binary at ${binaryPath}: ${msg}`);
98820
+ write(` ${import_picocolors41.default.red("\u2717")} Could not replace binary at ${binaryPath}: ${msg}`);
98267
98821
  if (needsSudo) {
98268
98822
  write(`
98269
98823
  Try with sudo:`);
98270
98824
  write(
98271
- ` ${import_picocolors40.default.dim(`sudo curl -fsSL https://github.com/meritt-dev/horus/releases/download/${tag}/${assetName} -o ${binaryPath} && sudo chmod +x ${binaryPath}`)}`
98825
+ ` ${import_picocolors41.default.dim(`sudo curl -fsSL https://github.com/meritt-dev/horus/releases/download/${tag}/${assetName} -o ${binaryPath} && sudo chmod +x ${binaryPath}`)}`
98272
98826
  );
98273
98827
  }
98274
98828
  return 1;
98275
98829
  }
98276
- write(` ${import_picocolors40.default.green("\u2713")} Updated: ${import_picocolors40.default.bold(currentVersion)} \u2192 ${import_picocolors40.default.bold(latest)}`);
98277
- write(` ${import_picocolors40.default.dim(binaryPath)}`);
98830
+ write(` ${import_picocolors41.default.green("\u2713")} Updated: ${import_picocolors41.default.bold(currentVersion)} \u2192 ${import_picocolors41.default.bold(latest)}`);
98831
+ write(` ${import_picocolors41.default.dim(binaryPath)}`);
98278
98832
  return 0;
98279
98833
  }
98280
98834
 
98281
98835
  // ../../packages/cli/src/commands/login.ts
98282
98836
  init_cjs_shims();
98283
98837
  var import_node_child_process9 = require("child_process");
98284
- var import_picocolors41 = __toESM(require_picocolors(), 1);
98838
+ var import_picocolors42 = __toESM(require_picocolors(), 1);
98285
98839
  var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
98286
98840
  function openBrowser(url) {
98287
98841
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
@@ -98306,19 +98860,19 @@ async function loginWithToken(apiBaseUrl, token) {
98306
98860
  }
98307
98861
  });
98308
98862
  console.log(
98309
- `${import_picocolors41.default.green("\u2713")} Logged in as ${import_picocolors41.default.bold(me.user.primaryEmail)}. Run ${import_picocolors41.default.bold("horus cloud link")} to connect this repo.`
98863
+ `${import_picocolors42.default.green("\u2713")} Logged in as ${import_picocolors42.default.bold(me.user.primaryEmail)}. Run ${import_picocolors42.default.bold("horus cloud link")} to connect this repo.`
98310
98864
  );
98311
98865
  return 0;
98312
98866
  } catch (err) {
98313
98867
  if (err instanceof CloudOfflineError) {
98314
- console.error(import_picocolors41.default.red(err.message));
98868
+ console.error(import_picocolors42.default.red(err.message));
98315
98869
  return 1;
98316
98870
  }
98317
98871
  if (err instanceof CloudError && err.status === 401) {
98318
- console.error(import_picocolors41.default.red("That token is not valid. Create a fresh one in the dashboard and try again."));
98872
+ console.error(import_picocolors42.default.red("That token is not valid. Create a fresh one in the dashboard and try again."));
98319
98873
  return 1;
98320
98874
  }
98321
- console.error(import_picocolors41.default.red(`Login failed: ${err.message}`));
98875
+ console.error(import_picocolors42.default.red(`Login failed: ${err.message}`));
98322
98876
  return 1;
98323
98877
  }
98324
98878
  }
@@ -98329,19 +98883,19 @@ async function loginWithBrowser(apiBaseUrl) {
98329
98883
  session = await client.startCliSession();
98330
98884
  } catch (err) {
98331
98885
  if (err instanceof CloudOfflineError) {
98332
- console.error(import_picocolors41.default.red(err.message));
98886
+ console.error(import_picocolors42.default.red(err.message));
98333
98887
  return 1;
98334
98888
  }
98335
- console.error(import_picocolors41.default.red(`Could not start login: ${err.message}`));
98889
+ console.error(import_picocolors42.default.red(`Could not start login: ${err.message}`));
98336
98890
  return 1;
98337
98891
  }
98338
98892
  console.log("");
98339
98893
  console.log(` To sign in, open this URL in your browser:`);
98340
- console.log(` ${import_picocolors41.default.cyan(import_picocolors41.default.bold(session.verificationUri))}`);
98894
+ console.log(` ${import_picocolors42.default.cyan(import_picocolors42.default.bold(session.verificationUri))}`);
98341
98895
  console.log("");
98342
- console.log(` and enter the code: ${import_picocolors41.default.bold(session.userCode)}`);
98896
+ console.log(` and enter the code: ${import_picocolors42.default.bold(session.userCode)}`);
98343
98897
  console.log("");
98344
- console.log(import_picocolors41.default.dim(` Waiting for approval\u2026 (press Ctrl+C to cancel)`));
98898
+ console.log(import_picocolors42.default.dim(` Waiting for approval\u2026 (press Ctrl+C to cancel)`));
98345
98899
  openBrowser(session.verificationUriComplete);
98346
98900
  const deadline = Date.now() + session.expiresIn * 1e3;
98347
98901
  let intervalMs = Math.max(session.interval, 1) * 1e3;
@@ -98352,7 +98906,7 @@ async function loginWithBrowser(apiBaseUrl) {
98352
98906
  poll = await client.pollCliSession(session.deviceCode);
98353
98907
  } catch (err) {
98354
98908
  if (err instanceof CloudOfflineError) {
98355
- console.error(import_picocolors41.default.red(`
98909
+ console.error(import_picocolors42.default.red(`
98356
98910
  ${err.message}`));
98357
98911
  return 1;
98358
98912
  }
@@ -98365,14 +98919,14 @@ ${err.message}`));
98365
98919
  intervalMs += 2e3;
98366
98920
  break;
98367
98921
  case "denied":
98368
- console.error(import_picocolors41.default.red("\nLogin was denied in the browser."));
98922
+ console.error(import_picocolors42.default.red("\nLogin was denied in the browser."));
98369
98923
  return 1;
98370
98924
  case "expired":
98371
- console.error(import_picocolors41.default.red("\nThis login request expired. Run `horus login` again."));
98925
+ console.error(import_picocolors42.default.red("\nThis login request expired. Run `horus login` again."));
98372
98926
  return 1;
98373
98927
  case "approved":
98374
98928
  if (!poll.token || !poll.account) {
98375
- console.error(import_picocolors41.default.red("\nLogin approved but no token was returned. Try again."));
98929
+ console.error(import_picocolors42.default.red("\nLogin approved but no token was returned. Try again."));
98376
98930
  return 1;
98377
98931
  }
98378
98932
  writeAuth({
@@ -98382,12 +98936,12 @@ ${err.message}`));
98382
98936
  });
98383
98937
  console.log(
98384
98938
  `
98385
- ${import_picocolors41.default.green("\u2713")} Logged in as ${import_picocolors41.default.bold(poll.account.email)}. Run ${import_picocolors41.default.bold("horus cloud link")} to connect this repo.`
98939
+ ${import_picocolors42.default.green("\u2713")} Logged in as ${import_picocolors42.default.bold(poll.account.email)}. Run ${import_picocolors42.default.bold("horus cloud link")} to connect this repo.`
98386
98940
  );
98387
98941
  return 0;
98388
98942
  }
98389
98943
  }
98390
- console.error(import_picocolors41.default.red("\nTimed out waiting for approval. Run `horus login` again."));
98944
+ console.error(import_picocolors42.default.red("\nTimed out waiting for approval. Run `horus login` again."));
98391
98945
  return 1;
98392
98946
  }
98393
98947
  async function runLogin(opts = {}) {
@@ -98401,7 +98955,7 @@ async function runLogin(opts = {}) {
98401
98955
  async function runLogout() {
98402
98956
  const auth = readAuth();
98403
98957
  if (!auth) {
98404
- console.log(import_picocolors41.default.dim("Not logged in."));
98958
+ console.log(import_picocolors42.default.dim("Not logged in."));
98405
98959
  return 0;
98406
98960
  }
98407
98961
  const session = authedClient();
@@ -98415,14 +98969,14 @@ async function runLogout() {
98415
98969
  }
98416
98970
  }
98417
98971
  clearAuth();
98418
- console.log(`${import_picocolors41.default.green("\u2713")} Logged out. Local mode still works.`);
98972
+ console.log(`${import_picocolors42.default.green("\u2713")} Logged out. Local mode still works.`);
98419
98973
  return 0;
98420
98974
  }
98421
98975
 
98422
98976
  // ../../packages/cli/src/commands/cloud.ts
98423
98977
  init_cjs_shims();
98424
- var import_picocolors42 = __toESM(require_picocolors(), 1);
98425
- var import_promises2 = require("readline/promises");
98978
+ var import_picocolors43 = __toESM(require_picocolors(), 1);
98979
+ var import_promises3 = require("readline/promises");
98426
98980
 
98427
98981
  // ../../packages/cli/src/lib/cloud/git.ts
98428
98982
  init_cjs_shims();
@@ -98466,7 +99020,7 @@ async function runCloudLink(opts = {}) {
98466
99020
  const root = repoRootOrCwd(opts.cwd);
98467
99021
  const session = authedClient();
98468
99022
  if (!session) {
98469
- console.error(import_picocolors42.default.red(`Not logged in. Run ${import_picocolors42.default.bold("horus login")} first.`));
99023
+ console.error(import_picocolors43.default.red(`Not logged in. Run ${import_picocolors43.default.bold("horus login")} first.`));
98470
99024
  return 1;
98471
99025
  }
98472
99026
  let ctx;
@@ -98476,7 +99030,7 @@ async function runCloudLink(opts = {}) {
98476
99030
  return reportCloudError(err);
98477
99031
  }
98478
99032
  const remote = detectGitRemote(root);
98479
- if (remote) console.log(import_picocolors42.default.dim(`Detected git remote: ${remote.remoteUrl}`));
99033
+ if (remote) console.log(import_picocolors43.default.dim(`Detected git remote: ${remote.remoteUrl}`));
98480
99034
  let resolved = null;
98481
99035
  let target = opts.project;
98482
99036
  if (!target) {
@@ -98492,7 +99046,7 @@ async function runCloudLink(opts = {}) {
98492
99046
  const org = ctx.organizations.find((o) => o.id === match.organizationId);
98493
99047
  if (ws && org) {
98494
99048
  target = `${org.slug}/${ws.slug}/${match.slug}`;
98495
- console.log(import_picocolors42.default.dim(`Matched existing project by remote: ${import_picocolors42.default.bold(target)}`));
99049
+ console.log(import_picocolors43.default.dim(`Matched existing project by remote: ${import_picocolors43.default.bold(target)}`));
98496
99050
  }
98497
99051
  }
98498
99052
  }
@@ -98501,8 +99055,8 @@ async function runCloudLink(opts = {}) {
98501
99055
  if (created === "aborted") return 1;
98502
99056
  if (!created) {
98503
99057
  console.error(
98504
- import_picocolors42.default.red("No project to link.") + `
98505
- Run ${import_picocolors42.default.bold("horus cloud link")} in a TTY to create one, pass ${import_picocolors42.default.bold("--project <org/workspace/project>")}, or create a project at ${import_picocolors42.default.bold("https://cloud.horus.sh")}.`
99058
+ import_picocolors43.default.red("No project to link.") + `
99059
+ Run ${import_picocolors43.default.bold("horus cloud link")} in a TTY to create one, pass ${import_picocolors43.default.bold("--project <org/workspace/project>")}, or create a project at ${import_picocolors43.default.bold("https://cloud.horus.sh")}.`
98506
99060
  );
98507
99061
  return 1;
98508
99062
  }
@@ -98512,8 +99066,8 @@ async function runCloudLink(opts = {}) {
98512
99066
  resolved = resolveTriple(ctx, target);
98513
99067
  if (!resolved) {
98514
99068
  console.error(
98515
- import_picocolors42.default.red(`You don't have access to ${import_picocolors42.default.bold(target)}.`) + `
98516
- Run ${import_picocolors42.default.bold("horus context list")} to see available projects.`
99069
+ import_picocolors43.default.red(`You don't have access to ${import_picocolors43.default.bold(target)}.`) + `
99070
+ Run ${import_picocolors43.default.bold("horus context list")} to see available projects.`
98517
99071
  );
98518
99072
  return 1;
98519
99073
  }
@@ -98526,10 +99080,10 @@ async function runCloudLink(opts = {}) {
98526
99080
  project: resolved.project
98527
99081
  });
98528
99082
  console.log(
98529
- `${import_picocolors42.default.green("\u2713")} Linked. Context for this repo is now ${import_picocolors42.default.bold(label)} ${import_picocolors42.default.dim("(cloud)")}.`
99083
+ `${import_picocolors43.default.green("\u2713")} Linked. Context for this repo is now ${import_picocolors43.default.bold(label)} ${import_picocolors43.default.dim("(cloud)")}.`
98530
99084
  );
98531
99085
  console.log(
98532
- import_picocolors42.default.dim(` Investigations here save to Horus Cloud. ${import_picocolors42.default.bold("horus context use local")} to switch back.`)
99086
+ import_picocolors43.default.dim(` Investigations here save to Horus Cloud. ${import_picocolors43.default.bold("horus context use local")} to switch back.`)
98533
99087
  );
98534
99088
  return 0;
98535
99089
  }
@@ -98537,11 +99091,11 @@ async function runCloudUnlink(opts = {}) {
98537
99091
  const root = repoRootOrCwd(opts.cwd);
98538
99092
  const cfg = readCloudConfig(root);
98539
99093
  if (!cfg || cfg.context !== "cloud") {
98540
- console.log(import_picocolors42.default.dim("This repo isn't linked to a cloud project."));
99094
+ console.log(import_picocolors43.default.dim("This repo isn't linked to a cloud project."));
98541
99095
  return 0;
98542
99096
  }
98543
99097
  clearCloudConfig(root);
98544
- console.log(`${import_picocolors42.default.green("\u2713")} Unlinked. This repo is back to ${import_picocolors42.default.bold("local")} mode.`);
99098
+ console.log(`${import_picocolors43.default.green("\u2713")} Unlinked. This repo is back to ${import_picocolors43.default.bold("local")} mode.`);
98545
99099
  return 0;
98546
99100
  }
98547
99101
  async function runCloudStatus(opts = {}) {
@@ -98549,18 +99103,18 @@ async function runCloudStatus(opts = {}) {
98549
99103
  const cfg = readCloudConfig(root);
98550
99104
  const session = authedClient();
98551
99105
  if (!isCloudActive(cfg) || !cfg?.project) {
98552
- console.log(`${import_picocolors42.default.bold("Context:")} local`);
98553
- console.log(`${import_picocolors42.default.bold("Storage:")} local Postgres (~/.horus)`);
99106
+ console.log(`${import_picocolors43.default.bold("Context:")} local`);
99107
+ console.log(`${import_picocolors43.default.bold("Storage:")} local Postgres (~/.horus)`);
98554
99108
  console.log(
98555
- import_picocolors42.default.dim(`Tip: ${import_picocolors42.default.bold("horus login")} then ${import_picocolors42.default.bold("horus cloud link")} to share investigations with your team.`)
99109
+ import_picocolors43.default.dim(`Tip: ${import_picocolors43.default.bold("horus login")} then ${import_picocolors43.default.bold("horus cloud link")} to share investigations with your team.`)
98556
99110
  );
98557
99111
  return 0;
98558
99112
  }
98559
99113
  const triple = `${cfg.organization?.slug}/${cfg.workspace?.slug}/${cfg.project.slug}`;
98560
- console.log(`${import_picocolors42.default.bold("Context:")} ${triple} ${import_picocolors42.default.dim("(cloud)")}`);
99114
+ console.log(`${import_picocolors43.default.bold("Context:")} ${triple} ${import_picocolors43.default.dim("(cloud)")}`);
98561
99115
  let projectLine = `${cfg.project.slug}`;
98562
99116
  let metaLines = [];
98563
- let account = import_picocolors42.default.dim("not logged in");
99117
+ let account = import_picocolors43.default.dim("not logged in");
98564
99118
  if (session) {
98565
99119
  account = session.auth.account.email;
98566
99120
  try {
@@ -98575,9 +99129,9 @@ async function runCloudStatus(opts = {}) {
98575
99129
  } catch {
98576
99130
  }
98577
99131
  }
98578
- console.log(`${import_picocolors42.default.bold("Project:")} ${projectLine}`);
99132
+ console.log(`${import_picocolors43.default.bold("Project:")} ${projectLine}`);
98579
99133
  for (const line2 of metaLines) console.log(line2);
98580
- console.log(`${import_picocolors42.default.bold("Account:")} ${account}`);
99134
+ console.log(`${import_picocolors43.default.bold("Account:")} ${account}`);
98581
99135
  return 0;
98582
99136
  }
98583
99137
  async function runCloudSync(opts = {}) {
@@ -98585,13 +99139,13 @@ async function runCloudSync(opts = {}) {
98585
99139
  const cfg = readCloudConfig(root);
98586
99140
  if (!isCloudActive(cfg) || !cfg.project) {
98587
99141
  console.error(
98588
- import_picocolors42.default.red("This repo isn't linked to a cloud project.") + ` Run ${import_picocolors42.default.bold("horus cloud link")} first.`
99142
+ import_picocolors43.default.red("This repo isn't linked to a cloud project.") + ` Run ${import_picocolors43.default.bold("horus cloud link")} first.`
98589
99143
  );
98590
99144
  return 1;
98591
99145
  }
98592
99146
  const session = authedClient();
98593
99147
  if (!session) {
98594
- console.error(import_picocolors42.default.red(`Not logged in. Run ${import_picocolors42.default.bold("horus login")} first.`));
99148
+ console.error(import_picocolors43.default.red(`Not logged in. Run ${import_picocolors43.default.bold("horus login")} first.`));
98595
99149
  return 1;
98596
99150
  }
98597
99151
  const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
@@ -98612,34 +99166,34 @@ async function runCloudSync(opts = {}) {
98612
99166
  const target = `${cfg.organization?.slug}/${cfg.workspace?.slug}/${cfg.project.slug}`;
98613
99167
  if (uploadable.length === 0) {
98614
99168
  console.log(
98615
- import_picocolors42.default.dim(
99169
+ import_picocolors43.default.dim(
98616
99170
  `No local investigations with reports to upload.` + (skippedNoReport ? ` (${skippedNoReport} without a report skipped)` : "")
98617
99171
  )
98618
99172
  );
98619
99173
  return 0;
98620
99174
  }
98621
- console.log(import_picocolors42.default.bold(`Will upload ${uploadable.length} investigation(s) to ${target} ${import_picocolors42.default.dim("(cloud)")}:`));
99175
+ console.log(import_picocolors43.default.bold(`Will upload ${uploadable.length} investigation(s) to ${target} ${import_picocolors43.default.dim("(cloud)")}:`));
98622
99176
  for (const c of uploadable.slice(0, 20)) {
98623
- console.log(` ${import_picocolors42.default.dim(c.id.slice(0, 8))} ${(c.title ?? "untitled").slice(0, 70)}`);
99177
+ console.log(` ${import_picocolors43.default.dim(c.id.slice(0, 8))} ${(c.title ?? "untitled").slice(0, 70)}`);
98624
99178
  }
98625
- if (uploadable.length > 20) console.log(import_picocolors42.default.dim(` \u2026and ${uploadable.length - 20} more`));
98626
- if (skippedNoReport) console.log(import_picocolors42.default.dim(` (${skippedNoReport} local investigation(s) without a report will be skipped)`));
99179
+ if (uploadable.length > 20) console.log(import_picocolors43.default.dim(` \u2026and ${uploadable.length - 20} more`));
99180
+ if (skippedNoReport) console.log(import_picocolors43.default.dim(` (${skippedNoReport} local investigation(s) without a report will be skipped)`));
98627
99181
  if (opts.dryRun) {
98628
- console.log(import_picocolors42.default.dim("Dry run \u2014 nothing uploaded. Re-run without --dry-run to upload."));
99182
+ console.log(import_picocolors43.default.dim("Dry run \u2014 nothing uploaded. Re-run without --dry-run to upload."));
98629
99183
  return 0;
98630
99184
  }
98631
99185
  if (!opts.yes) {
98632
99186
  if (!process.stdin.isTTY) {
98633
99187
  console.error(
98634
- import_picocolors42.default.yellow(`Re-run with ${import_picocolors42.default.bold("--yes")} to upload, or ${import_picocolors42.default.bold("--dry-run")} to preview only.`)
99188
+ import_picocolors43.default.yellow(`Re-run with ${import_picocolors43.default.bold("--yes")} to upload, or ${import_picocolors43.default.bold("--dry-run")} to preview only.`)
98635
99189
  );
98636
99190
  return 1;
98637
99191
  }
98638
- const rl = (0, import_promises2.createInterface)({ input: process.stdin, output: process.stdout });
99192
+ const rl = (0, import_promises3.createInterface)({ input: process.stdin, output: process.stdout });
98639
99193
  try {
98640
99194
  const answer = (await rl.question(`Upload ${uploadable.length} investigation(s) to ${target}? (y/N) `)).trim().toLowerCase();
98641
99195
  if (answer !== "y" && answer !== "yes") {
98642
- console.log(import_picocolors42.default.dim("Aborted. Nothing uploaded."));
99196
+ console.log(import_picocolors43.default.dim("Aborted. Nothing uploaded."));
98643
99197
  return 0;
98644
99198
  }
98645
99199
  } finally {
@@ -98652,18 +99206,18 @@ async function runCloudSync(opts = {}) {
98652
99206
  try {
98653
99207
  const refs = await uploadInvestigationToCloud(session.client, cfg, c.report);
98654
99208
  created++;
98655
- console.log(`${import_picocolors42.default.green("\u2713")} ${import_picocolors42.default.dim(c.id.slice(0, 8))} \u2192 ${refs.investigationId}`);
99209
+ console.log(`${import_picocolors43.default.green("\u2713")} ${import_picocolors43.default.dim(c.id.slice(0, 8))} \u2192 ${refs.investigationId}`);
98656
99210
  } catch (err) {
98657
99211
  failed++;
98658
- console.error(`${import_picocolors42.default.red("\u2717")} ${import_picocolors42.default.dim(c.id.slice(0, 8))} ${err.message}`);
99212
+ console.error(`${import_picocolors43.default.red("\u2717")} ${import_picocolors43.default.dim(c.id.slice(0, 8))} ${err.message}`);
98659
99213
  }
98660
99214
  }
98661
99215
  console.log("");
98662
99216
  console.log(
98663
- `${import_picocolors42.default.bold("Sync complete:")} ${import_picocolors42.default.green(`${created} uploaded`)}, ${skippedNoReport} skipped, ` + (failed ? import_picocolors42.default.red(`${failed} failed`) : "0 failed") + "."
99217
+ `${import_picocolors43.default.bold("Sync complete:")} ${import_picocolors43.default.green(`${created} uploaded`)}, ${skippedNoReport} skipped, ` + (failed ? import_picocolors43.default.red(`${failed} failed`) : "0 failed") + "."
98664
99218
  );
98665
99219
  console.log(
98666
- import_picocolors42.default.dim("Local data was not modified. Re-running is safe \u2014 duplicates are deduped by idempotency key.")
99220
+ import_picocolors43.default.dim("Local data was not modified. Re-running is safe \u2014 duplicates are deduped by idempotency key.")
98667
99221
  );
98668
99222
  return failed > 0 ? 1 : 0;
98669
99223
  }
@@ -98671,12 +99225,12 @@ async function createProjectFromRepo(client, ctx, remote, yes) {
98671
99225
  if (!process.stdin.isTTY && !yes) return null;
98672
99226
  if (ctx.organizations.length === 0) {
98673
99227
  console.error(
98674
- import_picocolors42.default.red("You're not a member of any organization yet.") + `
98675
- Create one at ${import_picocolors42.default.bold("https://cloud.horus.sh")} first.`
99228
+ import_picocolors43.default.red("You're not a member of any organization yet.") + `
99229
+ Create one at ${import_picocolors43.default.bold("https://cloud.horus.sh")} first.`
98676
99230
  );
98677
99231
  return null;
98678
99232
  }
98679
- const rl = (0, import_promises2.createInterface)({ input: process.stdin, output: process.stdout });
99233
+ const rl = (0, import_promises3.createInterface)({ input: process.stdin, output: process.stdout });
98680
99234
  const ask2 = async (q, def) => {
98681
99235
  if (yes && def !== void 0) return def;
98682
99236
  const a = (await rl.question(def ? `${q} (${def}): ` : `${q}: `)).trim();
@@ -98697,9 +99251,9 @@ async function createProjectFromRepo(client, ctx, remote, yes) {
98697
99251
  const wsName = await ask2("New workspace name", repoName || "default");
98698
99252
  const ws = await client.createWorkspace(org.id, { name: wsName });
98699
99253
  workspace = { id: ws.id, slug: ws.slug };
98700
- console.log(import_picocolors42.default.dim(`Created workspace ${import_picocolors42.default.bold(ws.slug)}.`));
99254
+ console.log(import_picocolors43.default.dim(`Created workspace ${import_picocolors43.default.bold(ws.slug)}.`));
98701
99255
  } else {
98702
- console.log(`Workspaces in ${import_picocolors42.default.bold(org.slug)}:`);
99256
+ console.log(`Workspaces in ${import_picocolors43.default.bold(org.slug)}:`);
98703
99257
  orgWorkspaces.forEach((w, i) => console.log(` ${i + 1}) ${w.slug}`));
98704
99258
  console.log(` ${orgWorkspaces.length + 1}) + create a new workspace`);
98705
99259
  const pick2 = Number(await ask2("Workspace number", "1"));
@@ -98707,7 +99261,7 @@ async function createProjectFromRepo(client, ctx, remote, yes) {
98707
99261
  const wsName = await ask2("New workspace name", repoName || "default");
98708
99262
  const ws = await client.createWorkspace(org.id, { name: wsName });
98709
99263
  workspace = { id: ws.id, slug: ws.slug };
98710
- console.log(import_picocolors42.default.dim(`Created workspace ${import_picocolors42.default.bold(ws.slug)}.`));
99264
+ console.log(import_picocolors43.default.dim(`Created workspace ${import_picocolors43.default.bold(ws.slug)}.`));
98711
99265
  } else {
98712
99266
  const chosen = orgWorkspaces[pick2 - 1] ?? orgWorkspaces[0];
98713
99267
  workspace = { id: chosen.id, slug: chosen.slug };
@@ -98719,7 +99273,7 @@ async function createProjectFromRepo(client, ctx, remote, yes) {
98719
99273
  remoteUrl: remote?.remoteUrl,
98720
99274
  provider: remote ? inferProvider(remote.remoteUrl) : void 0
98721
99275
  });
98722
- console.log(import_picocolors42.default.dim(`Created project ${import_picocolors42.default.bold(project.slug)}.`));
99276
+ console.log(import_picocolors43.default.dim(`Created project ${import_picocolors43.default.bold(project.slug)}.`));
98723
99277
  return {
98724
99278
  organization: { id: org.id, slug: org.slug },
98725
99279
  workspace,
@@ -98733,6 +99287,274 @@ async function createProjectFromRepo(client, ctx, remote, yes) {
98733
99287
  }
98734
99288
  }
98735
99289
 
99290
+ // ../../packages/cli/src/commands/telemetry.ts
99291
+ init_cjs_shims();
99292
+ var import_picocolors45 = __toESM(require_picocolors(), 1);
99293
+ var import_node_crypto8 = require("crypto");
99294
+
99295
+ // ../../packages/cli/src/lib/telemetry/transport.ts
99296
+ init_cjs_shims();
99297
+ var CHUNK = 100;
99298
+ var DEFAULT_TIMEOUT_MS = 1500;
99299
+ function telemetryBaseUrl() {
99300
+ return process.env.HORUS_TELEMETRY_URL ?? process.env.HORUS_API_BASE_URL ?? readAuth()?.apiBaseUrl ?? DEFAULT_API_BASE_URL;
99301
+ }
99302
+ async function postBatch(url, events, signal) {
99303
+ const res = await fetch(url, {
99304
+ method: "POST",
99305
+ headers: { "content-type": "application/json" },
99306
+ body: JSON.stringify({ events }),
99307
+ signal
99308
+ });
99309
+ return res.ok;
99310
+ }
99311
+ async function flushTelemetry(opts = {}) {
99312
+ try {
99313
+ if (!resolveConsent().tierA) return;
99314
+ const events = readSpooledEvents();
99315
+ if (events.length === 0) return;
99316
+ const url = `${telemetryBaseUrl().replace(/\/$/, "")}/v1/telemetry`;
99317
+ const controller = new AbortController();
99318
+ const timer2 = setTimeout(() => controller.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
99319
+ try {
99320
+ for (let i = 0; i < events.length; i += CHUNK) {
99321
+ const ok = await postBatch(url, events.slice(i, i + CHUNK), controller.signal);
99322
+ if (!ok) return;
99323
+ }
99324
+ clearSpool();
99325
+ } finally {
99326
+ clearTimeout(timer2);
99327
+ }
99328
+ } catch {
99329
+ }
99330
+ }
99331
+ async function deleteRemoteTelemetry(installId, opts = {}) {
99332
+ try {
99333
+ if (!installId) return;
99334
+ const url = `${telemetryBaseUrl().replace(/\/$/, "")}/v1/telemetry/delete`;
99335
+ const controller = new AbortController();
99336
+ const timer2 = setTimeout(() => controller.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
99337
+ try {
99338
+ await fetch(url, {
99339
+ method: "POST",
99340
+ headers: { "content-type": "application/json" },
99341
+ body: JSON.stringify({ installId }),
99342
+ signal: controller.signal
99343
+ });
99344
+ } finally {
99345
+ clearTimeout(timer2);
99346
+ }
99347
+ } catch {
99348
+ }
99349
+ }
99350
+
99351
+ // ../../packages/cli/src/lib/telemetry/notice.ts
99352
+ init_cjs_shims();
99353
+ var import_picocolors44 = __toESM(require_picocolors(), 1);
99354
+ var PRIVACY_URL = "https://horus.sh/privacy";
99355
+ var SUPPRESS_FIRST_TOKEN = /* @__PURE__ */ new Set(["telemetry", "help"]);
99356
+ var SUPPRESS_FLAGS = /* @__PURE__ */ new Set(["-V", "--version", "-h", "--help"]);
99357
+ function shouldSuppressFor(argv) {
99358
+ const args = argv.slice(2);
99359
+ if (args.length === 0) return true;
99360
+ if (args.some((a) => SUPPRESS_FLAGS.has(a))) return true;
99361
+ const firstToken = args.find((a) => !a.startsWith("-"));
99362
+ return firstToken != null && SUPPRESS_FIRST_TOKEN.has(firstToken);
99363
+ }
99364
+ function banner() {
99365
+ const b2 = import_picocolors44.default.bold;
99366
+ const dim = import_picocolors44.default.dim;
99367
+ return [
99368
+ "",
99369
+ import_picocolors44.default.cyan(b2("Horus collects anonymous usage data to improve the tool.")),
99370
+ dim(" \u2022 On by default: command names, timings, success/failure, and where the"),
99371
+ dim(" engine ran out of evidence. No file contents, queries, or secrets."),
99372
+ dim(" \u2022 Off by default: your investigation inputs/outputs (opt in to help train"),
99373
+ ` ${dim("Horus)")} ${dim("\u2014 run")} ${b2("horus telemetry enable-content")}`,
99374
+ dim(" \u2022 If you use Horus Cloud, linked investigations sync to your workspace."),
99375
+ "",
99376
+ ` ${dim("Opt out anytime:")} ${b2("horus telemetry disable")} ${dim("\xB7")} ${b2("HORUS_TELEMETRY=0")} ${dim("\xB7")} ${b2("DO_NOT_TRACK=1")}`,
99377
+ ` ${dim("Details:")} ${import_picocolors44.default.underline(PRIVACY_URL)}`,
99378
+ ""
99379
+ ].join("\n");
99380
+ }
99381
+ function maybeShowFirstRunNotice(argv = process.argv) {
99382
+ try {
99383
+ const env = process.env;
99384
+ if (resolveConsent({ env }).forcedOff) return;
99385
+ if (isCI(env)) return;
99386
+ const state = loadOrInitTelemetryState();
99387
+ if (state.tierA.noticeShownAt) return;
99388
+ if (shouldSuppressFor(argv)) return;
99389
+ if (!process.stderr.isTTY) return;
99390
+ process.stderr.write(banner() + "\n");
99391
+ updateTelemetryState((s) => {
99392
+ s.tierA.noticeShownAt = (/* @__PURE__ */ new Date()).toISOString();
99393
+ });
99394
+ } catch {
99395
+ }
99396
+ }
99397
+
99398
+ // ../../packages/cli/src/commands/telemetry.ts
99399
+ function onOff(enabled) {
99400
+ return enabled ? import_picocolors45.default.green("on") : import_picocolors45.default.dim("off");
99401
+ }
99402
+ function warnIfEnvOverrides() {
99403
+ const decision = resolveConsent();
99404
+ if (decision.forcedOff) {
99405
+ console.log(
99406
+ import_picocolors45.default.yellow(
99407
+ `
99408
+ Note: telemetry is currently forced off by your environment (${decision.reason}).`
99409
+ )
99410
+ );
99411
+ console.log(import_picocolors45.default.dim("Your saved preference will apply once that override is removed."));
99412
+ } else if (decision.source === "ci") {
99413
+ console.log(
99414
+ import_picocolors45.default.yellow("\nNote: CI environment detected \u2014 telemetry stays off here regardless.")
99415
+ );
99416
+ }
99417
+ }
99418
+ async function runTelemetryStatus() {
99419
+ const state = readTelemetryState();
99420
+ const decision = resolveConsent({ state });
99421
+ console.log(import_picocolors45.default.bold("Horus telemetry"));
99422
+ console.log(
99423
+ ` Usage metadata (Tier A): ${onOff(decision.tierA)} ${import_picocolors45.default.dim(`[${decision.source}]`)}`
99424
+ );
99425
+ console.log(` Content sharing (Tier B): ${onOff(decision.tierB)}`);
99426
+ console.log(import_picocolors45.default.dim(` Reason: ${decision.reason}`));
99427
+ if (state) {
99428
+ console.log(import_picocolors45.default.dim(` Install ID: ${state.installId}`));
99429
+ console.log(
99430
+ import_picocolors45.default.dim(
99431
+ ` Saved: Tier A ${state.tierA.enabled ? "on" : "off"}, Tier B ${state.tierB.enabled ? "on" : "off"}`
99432
+ )
99433
+ );
99434
+ } else {
99435
+ console.log(import_picocolors45.default.dim(" No saved preference yet (defaults apply)."));
99436
+ }
99437
+ console.log(import_picocolors45.default.dim(` Config: ${telemetryPath()}`));
99438
+ console.log(import_picocolors45.default.dim(` Privacy: ${PRIVACY_URL}`));
99439
+ if (!state || decision.source === "env" || decision.source === "ci") {
99440
+ warnIfEnvOverrides();
99441
+ }
99442
+ return 0;
99443
+ }
99444
+ async function runTelemetryEnable() {
99445
+ updateTelemetryState((s) => {
99446
+ s.tierA.enabled = true;
99447
+ if (!s.tierA.noticeShownAt) s.tierA.noticeShownAt = (/* @__PURE__ */ new Date()).toISOString();
99448
+ });
99449
+ console.log(import_picocolors45.default.green("Anonymous usage metadata (Tier A) is now ON. Thank you!"));
99450
+ warnIfEnvOverrides();
99451
+ return 0;
99452
+ }
99453
+ async function runTelemetryDisable() {
99454
+ updateTelemetryState((s) => {
99455
+ s.tierA.enabled = false;
99456
+ s.tierB.enabled = false;
99457
+ if (!s.tierA.noticeShownAt) s.tierA.noticeShownAt = (/* @__PURE__ */ new Date()).toISOString();
99458
+ });
99459
+ console.log(import_picocolors45.default.yellow("All telemetry is now OFF (Tier A and Tier B)."));
99460
+ return 0;
99461
+ }
99462
+ async function runTelemetryEnableContent() {
99463
+ updateTelemetryState((s) => {
99464
+ s.tierA.enabled = true;
99465
+ s.tierB.enabled = true;
99466
+ s.tierB.enabledAt = (/* @__PURE__ */ new Date()).toISOString();
99467
+ if (!s.tierA.noticeShownAt) s.tierA.noticeShownAt = (/* @__PURE__ */ new Date()).toISOString();
99468
+ });
99469
+ console.log(
99470
+ import_picocolors45.default.green("Content sharing (Tier B) is now ON \u2014 redacted inputs/outputs help improve Horus.")
99471
+ );
99472
+ console.log(import_picocolors45.default.dim("Content is scrubbed of secrets/PII before it ever leaves your machine."));
99473
+ console.log(import_picocolors45.default.dim(`What this means: ${PRIVACY_URL}`));
99474
+ warnIfEnvOverrides();
99475
+ return 0;
99476
+ }
99477
+ async function runTelemetryDisableContent() {
99478
+ updateTelemetryState((s) => {
99479
+ s.tierB.enabled = false;
99480
+ });
99481
+ console.log(import_picocolors45.default.yellow("Content sharing (Tier B) is now OFF. Usage metadata is unchanged."));
99482
+ return 0;
99483
+ }
99484
+ async function runTelemetryResetId() {
99485
+ const next = (0, import_node_crypto8.randomUUID)();
99486
+ updateTelemetryState((s) => {
99487
+ s.installId = next;
99488
+ });
99489
+ console.log(import_picocolors45.default.green("Install ID reset."));
99490
+ console.log(import_picocolors45.default.dim(` New install ID: ${next}`));
99491
+ return 0;
99492
+ }
99493
+ async function runTelemetryDelete() {
99494
+ const installId = readTelemetryState()?.installId;
99495
+ if (installId) await deleteRemoteTelemetry(installId);
99496
+ deleteTelemetryState();
99497
+ clearSpool();
99498
+ console.log(import_picocolors45.default.green("Telemetry deleted \u2014 local state cleared and a server purge requested."));
99499
+ console.log(
99500
+ import_picocolors45.default.dim(
99501
+ "Next run will treat this as a fresh install and re-show the notice. If the purge could not reach the server (offline), re-run this command when back online."
99502
+ )
99503
+ );
99504
+ return 0;
99505
+ }
99506
+
99507
+ // ../../packages/cli/src/lib/telemetry/command-hooks.ts
99508
+ init_cjs_shims();
99509
+ function commandPath(cmd) {
99510
+ const parts = [];
99511
+ let current = cmd;
99512
+ while (current && current.parent) {
99513
+ parts.unshift(current.name());
99514
+ current = current.parent;
99515
+ }
99516
+ return parts.join(" ") || cmd.name();
99517
+ }
99518
+ function installCommandTelemetry(program2) {
99519
+ let startedAt = 0;
99520
+ program2.hook("preAction", (_thisCommand, actionCommand) => {
99521
+ startedAt = Date.now();
99522
+ track({
99523
+ type: "command.invoked",
99524
+ command: commandPath(actionCommand),
99525
+ flags: extractFlagNames(process.argv)
99526
+ });
99527
+ });
99528
+ program2.hook("postAction", (_thisCommand, actionCommand) => {
99529
+ const exitCode = typeof process.exitCode === "number" ? process.exitCode : 0;
99530
+ track({
99531
+ type: "command.completed",
99532
+ command: commandPath(actionCommand),
99533
+ flags: extractFlagNames(process.argv),
99534
+ durationMs: startedAt ? Date.now() - startedAt : 0,
99535
+ exitCode,
99536
+ ok: exitCode === 0
99537
+ });
99538
+ });
99539
+ }
99540
+
99541
+ // ../../packages/cli/src/commands/feedback.ts
99542
+ init_cjs_shims();
99543
+ var import_picocolors46 = __toESM(require_picocolors(), 1);
99544
+ async function runFeedback(investigationId) {
99545
+ if (!process.stdin.isTTY) {
99546
+ console.error(import_picocolors46.default.yellow("horus feedback needs an interactive terminal."));
99547
+ return 1;
99548
+ }
99549
+ const id = investigationId?.trim();
99550
+ if (!id) {
99551
+ console.error(import_picocolors46.default.red("Usage: horus feedback <investigationId>"));
99552
+ return 1;
99553
+ }
99554
+ await runFeedbackPrompt(id, null);
99555
+ return 0;
99556
+ }
99557
+
98736
99558
  // ../../packages/cli/src/index.ts
98737
99559
  function buildProgram() {
98738
99560
  const program2 = new Command();
@@ -99154,11 +99976,50 @@ Examples:
99154
99976
  horus cloud sync --dry-run # preview what would upload
99155
99977
  horus cloud sync # confirm, then upload
99156
99978
  horus cloud sync --yes # upload without prompting`);
99979
+ program2.command("feedback [investigationId]").description("Leave quick impact feedback on an investigation (helps improve Horus)").action(async (investigationId) => {
99980
+ process.exitCode = await runFeedback(investigationId);
99981
+ });
99982
+ const telemetry = program2.command("telemetry").description("Manage anonymous usage telemetry that helps improve Horus");
99983
+ telemetry.command("status").description("Show the effective telemetry decision, saved preferences, and install ID").action(async () => {
99984
+ process.exitCode = await runTelemetryStatus();
99985
+ });
99986
+ telemetry.command("enable").description("Turn on anonymous usage metadata (Tier A)").action(async () => {
99987
+ process.exitCode = await runTelemetryEnable();
99988
+ });
99989
+ telemetry.command("disable").description("Turn off all telemetry (Tier A and Tier B)").action(async () => {
99990
+ process.exitCode = await runTelemetryDisable();
99991
+ });
99992
+ telemetry.command("enable-content").description("Opt in to sharing redacted investigation inputs/outputs (Tier B)").action(async () => {
99993
+ process.exitCode = await runTelemetryEnableContent();
99994
+ });
99995
+ telemetry.command("disable-content").description("Stop sharing inputs/outputs; keep usage metadata (Tier B off)").action(async () => {
99996
+ process.exitCode = await runTelemetryDisableContent();
99997
+ });
99998
+ telemetry.command("reset-id").description("Generate a new random install ID").action(async () => {
99999
+ process.exitCode = await runTelemetryResetId();
100000
+ });
100001
+ telemetry.command("delete").description("Delete local telemetry state (install ID + saved preferences)").action(async () => {
100002
+ process.exitCode = await runTelemetryDelete();
100003
+ });
100004
+ telemetry.addHelpText(
100005
+ "after",
100006
+ `
100007
+ Defaults: usage metadata ON (with this notice), content sharing OFF.
100008
+ Hard opt-out via env: HORUS_TELEMETRY=0 or DO_NOT_TRACK=1.
100009
+
100010
+ Examples:
100011
+ horus telemetry status
100012
+ horus telemetry disable
100013
+ horus telemetry enable-content`
100014
+ );
99157
100015
  return program2;
99158
100016
  }
99159
100017
  async function run(argv = process.argv) {
100018
+ maybeShowFirstRunNotice(argv);
99160
100019
  const program2 = buildProgram();
100020
+ installCommandTelemetry(program2);
99161
100021
  await program2.parseAsync(argv);
100022
+ await flushTelemetry();
99162
100023
  }
99163
100024
 
99164
100025
  // src/index.ts