@merittdev/horus 0.8.11 → 0.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.cjs +76 -12
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -65880,7 +65880,7 @@ init_cjs_shims();
65880
65880
 
65881
65881
  // ../../packages/core/src/version.ts
65882
65882
  init_cjs_shims();
65883
- var HORUS_VERSION = true ? "0.8.11" : "dev";
65883
+ var HORUS_VERSION = true ? "0.8.12" : "dev";
65884
65884
  var PINNED_SOURCE_VERSION = "1.5.1";
65885
65885
 
65886
65886
  // ../../packages/core/src/evidence.ts
@@ -71571,6 +71571,18 @@ function buildLevelFilter(m5, minLevel) {
71571
71571
  );
71572
71572
  return { terms: { [m5.levelField]: stringLevels } };
71573
71573
  }
71574
+ function buildWhereFilters(where) {
71575
+ if (where === void 0 || where.length === 0) return [];
71576
+ return where.map((w4) => ({
71577
+ bool: {
71578
+ should: [
71579
+ { term: { [w4.field]: w4.value } },
71580
+ { term: { [`${w4.field}.keyword`]: w4.value } }
71581
+ ],
71582
+ minimum_should_match: 1
71583
+ }
71584
+ }));
71585
+ }
71574
71586
  function buildTextMust(q2, mapping = MERITT_FIELD_MAPPING) {
71575
71587
  if (q2.text === void 0) return [{ match_all: {} }];
71576
71588
  if (q2.broadText === true) {
@@ -71595,6 +71607,7 @@ function buildSearchBody(q2, mapping = MERITT_FIELD_MAPPING) {
71595
71607
  if (q2.service !== void 0) {
71596
71608
  filters.push({ term: { [serviceTermField(mapping)]: q2.service } });
71597
71609
  }
71610
+ filters.push(...buildWhereFilters(q2.where));
71598
71611
  const mustClause = buildTextMust(q2, mapping);
71599
71612
  return {
71600
71613
  query: {
@@ -71623,6 +71636,7 @@ function buildErrorAggBody(q2, field, mapping = MERITT_FIELD_MAPPING) {
71623
71636
  if (q2.eventCode !== void 0) {
71624
71637
  filters.push({ term: { [signatureTermField(mapping)]: q2.eventCode } });
71625
71638
  }
71639
+ filters.push(...buildWhereFilters(q2.where));
71626
71640
  const aggField = field === mapping.eventCodeField ? signatureTermField(mapping) : `${field}.keyword`;
71627
71641
  return {
71628
71642
  size: 0,
@@ -71857,6 +71871,7 @@ function buildErrorAnalysisBody(q2, field = "event_code", mapping = MERITT_FIELD
71857
71871
  if (q2.eventCode !== void 0) {
71858
71872
  filters.push({ term: { [sigTerm]: q2.eventCode } });
71859
71873
  }
71874
+ filters.push(...buildWhereFilters(q2.where));
71860
71875
  const mustClause = buildTextMust(q2, mapping);
71861
71876
  return {
71862
71877
  size: 0,
@@ -112108,6 +112123,19 @@ function resolveRawLevel(opts) {
112108
112123
  if (opts.allLevels) return void 0;
112109
112124
  return opts.level ?? "error";
112110
112125
  }
112126
+ function parseWhere(raw) {
112127
+ if (raw === void 0) return [];
112128
+ return raw.map((entry2) => {
112129
+ const eq2 = entry2.indexOf("=");
112130
+ if (eq2 <= 0) {
112131
+ throw new Error(
112132
+ `Invalid --where "${entry2}" \u2014 expected field=value, e.g. --where context.brand_id=42`
112133
+ );
112134
+ }
112135
+ return { field: entry2.slice(0, eq2).trim(), value: entry2.slice(eq2 + 1).trim() };
112136
+ });
112137
+ }
112138
+ var whereLabel = (where) => where.map((w4) => `${w4.field}=${w4.value}`).join(", ");
112111
112139
  function levelColor(level, text2) {
112112
112140
  if (level === "error" || level === "fatal") return import_picocolors27.default.red(text2);
112113
112141
  if (level === "warn") return import_picocolors27.default.yellow(text2);
@@ -112123,6 +112151,15 @@ function matchServiceLabel(requested, candidates) {
112123
112151
  }
112124
112152
  async function runLogs(service, opts) {
112125
112153
  try {
112154
+ let where;
112155
+ try {
112156
+ where = parseWhere(opts.where);
112157
+ } catch (err2) {
112158
+ console.error(import_picocolors27.default.red(err2.message));
112159
+ return 1;
112160
+ }
112161
+ const hasWhere = where.length > 0;
112162
+ const whereArg = hasWhere ? where : void 0;
112126
112163
  const config2 = await loadConfig(opts.config, { name: opts.name });
112127
112164
  let renv;
112128
112165
  try {
@@ -112174,8 +112211,9 @@ async function runLogs(service, opts) {
112174
112211
  const from = sinceToIso(opts.since) ?? new Date(Date.now() - 7 * 864e5).toISOString();
112175
112212
  const fromDisplay = from.slice(0, 16).replace("T", " ");
112176
112213
  if (opts.raw === true) {
112177
- const rawLevel = resolveRawLevel(opts);
112178
- const limit = opts.limit !== void 0 ? Math.min(Number(opts.limit), 1e3) : 20;
112214
+ const rawLevel = hasWhere && opts.level === void 0 ? void 0 : resolveRawLevel(opts);
112215
+ const defaultLimit = hasWhere ? 200 : 20;
112216
+ const limit = opts.limit !== void 0 ? Math.min(Number(opts.limit), 1e3) : defaultLimit;
112179
112217
  const records = await logs.searchLogs({
112180
112218
  service: resolvedService,
112181
112219
  from,
@@ -112184,8 +112222,10 @@ async function runLogs(service, opts) {
112184
112222
  // grep searches message + detail + context.* so error strings buried
112185
112223
  // outside the message field are still found (HOR-216).
112186
112224
  broadText: opts.grep !== void 0,
112225
+ where: whereArg,
112187
112226
  limit
112188
112227
  });
112228
+ const truncated = records.length === limit;
112189
112229
  if (opts.json) {
112190
112230
  console.log(
112191
112231
  JSON.stringify(
@@ -112197,7 +112237,9 @@ async function runLogs(service, opts) {
112197
112237
  index: indexPattern,
112198
112238
  from,
112199
112239
  level: rawLevel ?? "all",
112200
- grep: opts.grep ?? null
112240
+ grep: opts.grep ?? null,
112241
+ where: whereArg ?? null,
112242
+ truncated
112201
112243
  },
112202
112244
  records: records.map((r) => ({
112203
112245
  timestamp: r.timestamp,
@@ -112219,6 +112261,11 @@ async function runLogs(service, opts) {
112219
112261
  );
112220
112262
  return 0;
112221
112263
  }
112264
+ console.error(
112265
+ import_picocolors27.default.dim(
112266
+ `logs: ${renv.project}/${renv.env}` + (resolvedService ? ` \xB7 service ${resolvedService}` : "") + ` \xB7 from ${fromDisplay} UTC \xB7 level ${rawLevel ?? "all"}` + (hasWhere ? ` \xB7 where ${whereLabel(where)}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "") + (rawLevel === "error" && !hasWhere ? " (error+ only \u2014 use --all-levels for info/debug)" : "")
112267
+ )
112268
+ );
112222
112269
  if (records.length === 0) {
112223
112270
  console.log(import_picocolors27.default.dim("No logs matched."));
112224
112271
  return 0;
@@ -112233,6 +112280,13 @@ async function runLogs(service, opts) {
112233
112280
  console.log(import_picocolors27.default.dim(` ${key}: ${value.slice(0, 200)}`));
112234
112281
  }
112235
112282
  }
112283
+ if (truncated) {
112284
+ console.error(
112285
+ import_picocolors27.default.yellow(
112286
+ `[warn] showing the first ${limit} record(s) \u2014 results may be truncated. Narrow with --where/--since/--level or raise --limit (max 1000).`
112287
+ )
112288
+ );
112289
+ }
112236
112290
  return 0;
112237
112291
  }
112238
112292
  if (opts.groupBy !== void 0) {
@@ -112242,7 +112296,8 @@ async function runLogs(service, opts) {
112242
112296
  service: resolvedService,
112243
112297
  from,
112244
112298
  text: opts.grep,
112245
- broadText: opts.grep !== void 0
112299
+ broadText: opts.grep !== void 0,
112300
+ where: whereArg
112246
112301
  },
112247
112302
  field
112248
112303
  );
@@ -112258,6 +112313,7 @@ async function runLogs(service, opts) {
112258
112313
  from,
112259
112314
  severity: "error+",
112260
112315
  grep: opts.grep ?? null,
112316
+ where: whereArg ?? null,
112261
112317
  groupBy: field
112262
112318
  },
112263
112319
  buckets
@@ -112270,7 +112326,7 @@ async function runLogs(service, opts) {
112270
112326
  }
112271
112327
  console.log(
112272
112328
  import_picocolors27.default.bold(`Top ${field}`) + import_picocolors27.default.dim(
112273
- ` \u2014 ${renv.project}/${renv.env}` + (resolvedService ? ` \xB7 service ${resolvedService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
112329
+ ` \u2014 ${renv.project}/${renv.env}` + (resolvedService ? ` \xB7 service ${resolvedService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "") + (hasWhere ? ` \xB7 where ${whereLabel(where)}` : "")
112274
112330
  )
112275
112331
  );
112276
112332
  console.log(import_picocolors27.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
@@ -112291,12 +112347,13 @@ async function runLogs(service, opts) {
112291
112347
  service: resolvedService,
112292
112348
  from,
112293
112349
  text: opts.grep,
112294
- broadText: opts.grep !== void 0
112350
+ broadText: opts.grep !== void 0,
112351
+ where: whereArg
112295
112352
  });
112296
112353
  let scopeService = resolvedService;
112297
112354
  let broadeningNote;
112298
112355
  if (analysis.signatures.length === 0 && resolvedService !== void 0) {
112299
- const broader = await logs.analyzeErrors({ from, text: opts.grep, broadText: opts.grep !== void 0 });
112356
+ const broader = await logs.analyzeErrors({ from, text: opts.grep, broadText: opts.grep !== void 0, where: whereArg });
112300
112357
  if (broader.signatures.length > 0) {
112301
112358
  const match = matchServiceLabel(resolvedService, broader.affectedServices);
112302
112359
  if (match.kind === "unique" && match.matched !== void 0 && match.matched !== resolvedService) {
@@ -112304,7 +112361,8 @@ async function runLogs(service, opts) {
112304
112361
  service: match.matched,
112305
112362
  from,
112306
112363
  text: opts.grep,
112307
- broadText: opts.grep !== void 0
112364
+ broadText: opts.grep !== void 0,
112365
+ where: whereArg
112308
112366
  });
112309
112367
  if (scoped.signatures.length > 0) {
112310
112368
  analysis = scoped;
@@ -112337,7 +112395,8 @@ async function runLogs(service, opts) {
112337
112395
  index: indexPattern,
112338
112396
  from,
112339
112397
  severity: "error+",
112340
- grep: opts.grep ?? null
112398
+ grep: opts.grep ?? null,
112399
+ where: whereArg ?? null
112341
112400
  },
112342
112401
  totalErrors: analysis.totalErrors,
112343
112402
  signatures: analysis.signatures,
@@ -112352,7 +112411,7 @@ async function runLogs(service, opts) {
112352
112411
  }
112353
112412
  console.log(
112354
112413
  import_picocolors27.default.bold(`Error analysis`) + import_picocolors27.default.dim(
112355
- ` \u2014 ${renv.project}/${renv.env}` + (scopeService ? ` \xB7 service ${scopeService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
112414
+ ` \u2014 ${renv.project}/${renv.env}` + (scopeService ? ` \xB7 service ${scopeService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "") + (hasWhere ? ` \xB7 where ${whereLabel(where)}` : "")
112356
112415
  )
112357
112416
  );
112358
112417
  console.log(import_picocolors27.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
@@ -116652,7 +116711,12 @@ Examples:
116652
116711
  });
116653
116712
  program2.command("logs [service]").description(
116654
116713
  "Synthesize error evidence from logs (signatures, first/last, affected services); --raw for lines"
116655
- ).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--since <when>", "time window, e.g. 24h, 7d, or an ISO date").option("--level <level>", "minimum level (with --raw): trace|debug|info|warn|error|fatal").option("--grep <text>", "match text in the message").option("--raw", "dump individual log lines instead of synthesized evidence (error+ by default)").option("--all-levels", "with --raw: show all severity levels, not just error+").option("--group-by <field>", "aggregate error counts by a context field, e.g. context.brand_id").option("--limit <n>", "max records (with --raw)").option("--json", "output JSON").option("--ai", "append AI narration of error signatures (default mode only, not --raw or --json)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
116714
+ ).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--since <when>", "time window, e.g. 24h, 7d, or an ISO date").option("--level <level>", "minimum level (with --raw): trace|debug|info|warn|error|fatal").option("--grep <text>", "match text in the message").option("--raw", "dump individual log lines instead of synthesized evidence (error+ by default)").option("--all-levels", "with --raw: show all severity levels, not just error+").option("--group-by <field>", "aggregate error counts by a context field, e.g. context.brand_id").option(
116715
+ "--where <field=value>",
116716
+ "filter by a structured context field (repeatable, AND-combined), e.g. --where context.brand_id=42",
116717
+ (val, acc) => [...acc, val],
116718
+ []
116719
+ ).option("--limit <n>", "max records (with --raw)").option("--json", "output JSON").option("--ai", "append AI narration of error signatures (default mode only, not --raw or --json)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
116656
116720
  async (service, opts) => {
116657
116721
  process.exitCode = await runLogs(service, opts);
116658
116722
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.8.11",
3
+ "version": "0.8.12",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {