@akasecurity/cli 0.8.2 → 0.9.0-rc1

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 (91) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +412 -97
  3. package/package.json +10 -4
  4. package/web-ui/rules/core-code-context/db-table-name.json +0 -1
  5. package/web-ui/web-ui/.next/BUILD_ID +1 -1
  6. package/web-ui/web-ui/.next/app-build-manifest.json +54 -53
  7. package/web-ui/web-ui/.next/app-path-routes-manifest.json +5 -5
  8. package/web-ui/web-ui/.next/build-manifest.json +2 -2
  9. package/web-ui/web-ui/.next/prerender-manifest.json +15 -15
  10. package/web-ui/web-ui/.next/required-server-files.json +2 -2
  11. package/web-ui/web-ui/.next/server/app/(app)/activity/page.js +2 -2
  12. package/web-ui/web-ui/.next/server/app/(app)/activity/page_client-reference-manifest.js +1 -1
  13. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js +2 -2
  14. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page_client-reference-manifest.js +1 -1
  15. package/web-ui/web-ui/.next/server/app/(app)/detections/page.js +2 -2
  16. package/web-ui/web-ui/.next/server/app/(app)/detections/page_client-reference-manifest.js +1 -1
  17. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js +2 -2
  18. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js.nft.json +1 -1
  19. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page_client-reference-manifest.js +1 -1
  20. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js +2 -2
  21. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js.nft.json +1 -1
  22. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page_client-reference-manifest.js +1 -1
  23. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js +1 -1
  24. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js.nft.json +1 -1
  25. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page_client-reference-manifest.js +1 -1
  26. package/web-ui/web-ui/.next/server/app/(app)/findings/page.js +1 -1
  27. package/web-ui/web-ui/.next/server/app/(app)/findings/page_client-reference-manifest.js +1 -1
  28. package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js +1 -1
  29. package/web-ui/web-ui/.next/server/app/(app)/inventory/page_client-reference-manifest.js +1 -1
  30. package/web-ui/web-ui/.next/server/app/(app)/policies/page_client-reference-manifest.js +1 -1
  31. package/web-ui/web-ui/.next/server/app/(app)/scan/page.js +1 -1
  32. package/web-ui/web-ui/.next/server/app/(app)/scan/page.js.nft.json +1 -1
  33. package/web-ui/web-ui/.next/server/app/(app)/scan/page_client-reference-manifest.js +1 -1
  34. package/web-ui/web-ui/.next/server/app/(app)/security/page.js +1 -1
  35. package/web-ui/web-ui/.next/server/app/(app)/security/page_client-reference-manifest.js +1 -1
  36. package/web-ui/web-ui/.next/server/app/(app)/settings/page.js +2 -2
  37. package/web-ui/web-ui/.next/server/app/(app)/settings/page_client-reference-manifest.js +1 -1
  38. package/web-ui/web-ui/.next/server/app/(app)/updates/page.js +2 -2
  39. package/web-ui/web-ui/.next/server/app/(app)/updates/page.js.nft.json +1 -1
  40. package/web-ui/web-ui/.next/server/app/(app)/updates/page_client-reference-manifest.js +1 -1
  41. package/web-ui/web-ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  42. package/web-ui/web-ui/.next/server/app/_not-found.html +1 -1
  43. package/web-ui/web-ui/.next/server/app/_not-found.rsc +2 -2
  44. package/web-ui/web-ui/.next/server/app/index.html +1 -1
  45. package/web-ui/web-ui/.next/server/app/index.rsc +2 -2
  46. package/web-ui/web-ui/.next/server/app/page_client-reference-manifest.js +1 -1
  47. package/web-ui/web-ui/.next/server/app-paths-manifest.json +5 -5
  48. package/web-ui/web-ui/.next/server/chunks/366.js +73 -57
  49. package/web-ui/web-ui/.next/server/chunks/45.js +1 -1
  50. package/web-ui/web-ui/.next/server/chunks/461.js +5 -5
  51. package/web-ui/web-ui/.next/server/chunks/484.js +72 -56
  52. package/web-ui/web-ui/.next/server/chunks/773.js +4 -4
  53. package/web-ui/web-ui/.next/server/chunks/900.js +1 -0
  54. package/web-ui/web-ui/.next/server/chunks/918.js +2 -0
  55. package/web-ui/web-ui/.next/server/functions-config-manifest.json +5 -5
  56. package/web-ui/web-ui/.next/server/middleware-manifest.json +5 -5
  57. package/web-ui/web-ui/.next/server/pages/404.html +1 -1
  58. package/web-ui/web-ui/.next/server/pages/500.html +1 -1
  59. package/web-ui/web-ui/.next/server/server-reference-manifest.js +1 -1
  60. package/web-ui/web-ui/.next/server/server-reference-manifest.json +1 -1
  61. package/web-ui/web-ui/.next/static/chunks/{24-b20c857178647805.js → 24-55b96836aea5ce23.js} +3 -3
  62. package/web-ui/web-ui/.next/static/chunks/561-c3388f066f813e17.js +1 -0
  63. package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-0c208c8b275a69ac.js +1 -0
  64. package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/{page-d469bd704615ea76.js → page-041187c76bd42461.js} +1 -1
  65. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-481fedcbfb474cef.js +1 -0
  66. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-a20e7d7a2f0ad507.js +1 -0
  67. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/{page-d6cfd847bb528eec.js → page-2cfe42c3dd4833c3.js} +1 -1
  68. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-849d66cc2cd77cb6.js +1 -0
  69. package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-d33f3999e89c7e77.js +1 -0
  70. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-76edd10592ba3cb1.js +1 -0
  71. package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-d15081016f40ac92.js +1 -0
  72. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/{page-b00573441ac43bdf.js → page-50b604f229747240.js} +1 -1
  73. package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/{page-3b5fe92cd995b763.js → page-92d1086c8e8ed3bf.js} +1 -1
  74. package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-c3c1671d32547619.js +1 -0
  75. package/web-ui/web-ui/.next/static/chunks/app/{layout-8b19b03146218dc7.js → layout-40fea3e270a8e120.js} +1 -1
  76. package/web-ui/web-ui/.next/static/css/43f95c94ecc1bd17.css +2 -0
  77. package/web-ui/web-ui/server.js +1 -1
  78. package/web-ui/web-ui/.next/server/chunks/640.js +0 -1
  79. package/web-ui/web-ui/.next/server/chunks/913.js +0 -2
  80. package/web-ui/web-ui/.next/static/chunks/561-46ea4b9ad0419130.js +0 -1
  81. package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-49c63d0cec0da80d.js +0 -1
  82. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-07ca4b0ce174ef7a.js +0 -1
  83. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-16d5fd7d373c8f0b.js +0 -1
  84. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-291617f3a4d46a01.js +0 -1
  85. package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-c59135e0cfa8d5c7.js +0 -1
  86. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-0eb54f5acb30a73e.js +0 -1
  87. package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-a86819fe3aeb2491.js +0 -1
  88. package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-50f828314c9a5d91.js +0 -1
  89. package/web-ui/web-ui/.next/static/css/dfdcbdce6b7c9eb0.css +0 -2
  90. /package/web-ui/web-ui/.next/static/{zNaHfOWePiRki0ugHKYzl → unUYwnPVSRwxtrUFgSAxr}/_buildManifest.js +0 -0
  91. /package/web-ui/web-ui/.next/static/{zNaHfOWePiRki0ugHKYzl → unUYwnPVSRwxtrUFgSAxr}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -755,21 +755,28 @@ import { createHash, randomUUID } from "crypto";
755
755
  import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
756
756
  import { join as join2, relative, sep } from "path";
757
757
 
758
+ // ../packages/detections/src/escape-regexp.ts
759
+ function escapeRegExp(value) {
760
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
761
+ }
762
+
763
+ // ../packages/detections/src/matchers/limits.ts
764
+ var MAX_MATCHES_PER_RULE = 1e4;
765
+
758
766
  // ../packages/detections/src/matchers/keyword.ts
759
767
  var KeywordMatcher = class {
760
768
  match(text, rule) {
761
769
  if (rule.matcher.type !== "keyword") return [];
762
770
  const { keywords, caseSensitive } = rule.matcher;
763
- const haystack = caseSensitive ? text : text.toLowerCase();
764
771
  const spans = [];
765
772
  for (const kw of keywords) {
766
- const needle = caseSensitive ? kw : kw.toLowerCase();
767
- let idx = 0;
768
- while (idx < haystack.length) {
769
- const pos = haystack.indexOf(needle, idx);
770
- if (pos === -1) break;
771
- spans.push({ start: pos, end: pos + kw.length });
772
- idx = pos + 1;
773
+ if (kw.length === 0) continue;
774
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
775
+ const re = new RegExp(escapeRegExp(kw), caseSensitive ? "gu" : "giu");
776
+ let m;
777
+ while ((m = re.exec(text)) !== null) {
778
+ spans.push({ start: m.index, end: m.index + m[0].length });
779
+ if (spans.length >= MAX_MATCHES_PER_RULE) break;
773
780
  }
774
781
  }
775
782
  return spans;
@@ -777,7 +784,6 @@ var KeywordMatcher = class {
777
784
  };
778
785
 
779
786
  // ../packages/detections/src/matchers/regex.ts
780
- var MAX_MATCHES_PER_RULE = 1e4;
781
787
  var RegexMatcher = class {
782
788
  match(text, rule) {
783
789
  if (rule.matcher.type !== "regex") return [];
@@ -863,9 +869,6 @@ function registerPack(pack) {
863
869
  function getLoadedRules() {
864
870
  return [...packs.values()].flatMap((p) => p.rules);
865
871
  }
866
- function escapeRegExp(value) {
867
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
868
- }
869
872
  function isCorroborated(candidate, candidates, text) {
870
873
  const req = candidate.rule.requiresNearby;
871
874
  if (!req) return true;
@@ -1098,6 +1101,10 @@ var SQLITE_MIGRATIONS = [
1098
1101
  {
1099
1102
  tag: "0009_findings_path_expression_index",
1100
1103
  sql: "-- Custom migration: partial expression index for the resolver's per-path reads.\n--\n-- openAtRestKeysForPath / resolvedAtRestKeysForPath (and the scanner's tier-3\n-- open-key probe) filter at-rest events by\n-- e.kind = 'code_change' AND json_extract(e.metadata, '$.filePath') = :path\n-- once per changed/deleted file on every scan \u2014 previously a full events scan\n-- per file. json_extract is deterministic, so SQLite allows it in an index;\n-- the WHERE kind = 'code_change' keeps the index to exactly the rows those\n-- queries can match (in-flight events are never path-addressed).\nCREATE INDEX `idx_events_code_change_path` ON `events` (json_extract(`metadata`, '$.filePath')) WHERE `kind` = 'code_change';\n"
1104
+ },
1105
+ {
1106
+ tag: "0010_events_session_expression_index",
1107
+ sql: "-- Custom migration: partial expression index for session-scoped finding reads.\n--\n-- sessionFindingsCount, the session-scoped listGroupedFindings paths, and the\n-- insert-time session dedup all filter live-capture events by\n-- json_extract(e.metadata, '$.sessionId') = :sessionId\n-- \u2014 previously a full findings-join scan with a JSON parse per row. The\n-- IS NOT NULL predicate keeps the index to session-stamped events only (an\n-- equality probe implies non-null, so SQLite still uses it).\nCREATE INDEX `idx_events_session_id` ON `events` (json_extract(`metadata`, '$.sessionId')) WHERE json_extract(`metadata`, '$.sessionId') IS NOT NULL;\n"
1101
1108
  }
1102
1109
  ];
1103
1110
 
@@ -1160,6 +1167,10 @@ function scopeFromAnswer(answer, now = Date.now()) {
1160
1167
  };
1161
1168
  }
1162
1169
 
1170
+ // ../packages/schema/src/identity.ts
1171
+ var PRODUCT_NAME = "AKA Security";
1172
+ var PRODUCT_TAGLINE = "We secure agent harnesses at the source.";
1173
+
1163
1174
  // ../packages/schema/src/time.ts
1164
1175
  function isoToEpochMillis(iso) {
1165
1176
  return new Date(iso).getTime();
@@ -1254,12 +1265,15 @@ var defaultCostModel = {
1254
1265
  };
1255
1266
 
1256
1267
  // ../packages/schema/src/token/format.ts
1268
+ var COMPACT = new Intl.NumberFormat("en-US", {
1269
+ notation: "compact",
1270
+ maximumFractionDigits: 1
1271
+ });
1272
+ function compactNumber(n) {
1273
+ return COMPACT.format(n);
1274
+ }
1257
1275
  function formatTokenCount(n) {
1258
- const abs = Math.abs(n);
1259
- if (abs < 1e3) return String(n);
1260
- if (abs < 1e6) return `${(n / 1e3).toFixed(1)}k`;
1261
- if (abs < 1e9) return `${(n / 1e6).toFixed(1)}M`;
1262
- return `${(n / 1e9).toFixed(1)}B`;
1276
+ return compactNumber(n);
1263
1277
  }
1264
1278
  function formatUsd(value) {
1265
1279
  return value > 0 && value < 0.1 ? `$${value.toFixed(4)}` : `$${value.toFixed(2)}`;
@@ -15993,6 +16007,12 @@ var FindingInstance = external_exports.object({
15993
16007
  provider: FindingProvider,
15994
16008
  repo: external_exports.string(),
15995
16009
  file: external_exports.string(),
16010
+ // Host tool that produced the scanned text (event metadata's toolName).
16011
+ // Present whenever the capturing hook recorded one — including
16012
+ // file-attributed captures (views prefer `file`); its display value is
16013
+ // the location fallback ("via Bash") when no filePath exists. Absent for
16014
+ // legacy rows and non-tool captures (prompts, worktree scans).
16015
+ toolName: external_exports.string().optional(),
15996
16016
  // Effective action: override.action ?? actionTaken, translated to FindingAction.
15997
16017
  action: FindingAction,
15998
16018
  detectedAt: external_exports.iso.datetime(),
@@ -16049,6 +16069,9 @@ var ListGroupedFindingsQuery = external_exports.object({
16049
16069
  provider: external_exports.array(FindingProvider).optional(),
16050
16070
  action: external_exports.array(FindingAction).optional(),
16051
16071
  q: external_exports.string().optional(),
16072
+ // Scope to findings whose event carries this session id (the Activity page's
16073
+ // session → findings drilldown). Findings without a session never match.
16074
+ sessionId: external_exports.string().optional(),
16052
16075
  groupBy: external_exports.literal("type").optional(),
16053
16076
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
16054
16077
  cursor: external_exports.string().optional()
@@ -16060,7 +16083,13 @@ var ListGroupedFindingsResponse = external_exports.object({
16060
16083
  }),
16061
16084
  facets: FindingFacets,
16062
16085
  items: external_exports.array(FindingGroup),
16063
- nextCursor: external_exports.string().nullable()
16086
+ nextCursor: external_exports.string().nullable(),
16087
+ // Present only on session-scoped queries (`sessionId` set): per ruleId, how
16088
+ // many times that rule fired in the session's persisted transcript. Findings
16089
+ // here are deduplicated to unique values while the transcript tally counts
16090
+ // every firing, so the two numbers legitimately differ — this map lets a
16091
+ // session-scoped view show both.
16092
+ sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
16064
16093
  }).meta({ id: "ListGroupedFindingsResponse" });
16065
16094
  var ApplyFindingActionRequest = external_exports.object({
16066
16095
  // 'quarantined' is system-assigned (see FindingAction) — clients may not set
@@ -16468,6 +16497,10 @@ var ListActivitySessionsQuery = external_exports.object({
16468
16497
  from: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
16469
16498
  /** Upper bound on startedAt; omitted defaults to now. */
16470
16499
  to: external_exports.union([external_exports.iso.date(), external_exports.iso.datetime()]).optional(),
16500
+ /** Exclude zero-activity sessions — roots whose only recorded children are
16501
+ * bookkeeping rows (hooks, config scans), typically background `claude`
16502
+ * launches. Omitted = list everything. `z.stringbool()` per the note above. */
16503
+ excludeEmpty: external_exports.stringbool().optional(),
16471
16504
  /** Page size, 1–100; out-of-range values are a 400. `z.coerce` — query params arrive as strings. */
16472
16505
  limit: external_exports.coerce.number().int().min(1).max(100).default(50),
16473
16506
  /** Opaque pagination cursor (most-recent first). */
@@ -16476,7 +16509,11 @@ var ListActivitySessionsQuery = external_exports.object({
16476
16509
  var ListActivitySessionsResponse = external_exports.object({
16477
16510
  items: external_exports.array(ActivitySessionSummary),
16478
16511
  /** `null` once the last page is reached. */
16479
- nextCursor: external_exports.string().nullable()
16512
+ nextCursor: external_exports.string().nullable(),
16513
+ /** Zero-activity sessions matching the query's filters/range (whether or
16514
+ * not `excludeEmpty` dropped them from `items`) — the count a UI toggle
16515
+ * shows when collapsing them. */
16516
+ emptyCount: external_exports.number().int().nonnegative()
16480
16517
  }).meta({ id: "ListActivitySessionsResponse" });
16481
16518
  var ListSessionEventsQuery = external_exports.object({
16482
16519
  /** Default 100, range 1–500. */
@@ -16511,6 +16548,12 @@ var EventMetadata = external_exports.object({
16511
16548
  sessionId: external_exports.string().optional(),
16512
16549
  repo: external_exports.string().optional(),
16513
16550
  filePath: external_exports.string().optional(),
16551
+ // The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
16552
+ // set by the tool-scanning hooks. The tool NAME only — never the tool's
16553
+ // arguments or output, which can carry the very value a finding masked
16554
+ // (metadata is stored unredacted). Gives findings on non-file captures a
16555
+ // display location ("via Bash") when no filePath exists.
16556
+ toolName: external_exports.string().optional(),
16514
16557
  // Set (true) by the worktree scanner when the file is excluded by the
16515
16558
  // repo's .gitignore. Gitignored files ARE still scanned — local scratch and
16516
16559
  // generated code can leak real secrets — but the provenance is recorded so
@@ -16842,7 +16885,10 @@ var ExceptionBundleEntry = DetectionException.pick({
16842
16885
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
16843
16886
  var KeywordMatcher2 = external_exports.object({
16844
16887
  type: external_exports.literal("keyword"),
16845
- keywords: external_exports.array(external_exports.string()).min(1),
16888
+ // An empty keyword matches at every position, yielding one zero-length span
16889
+ // per character. Rejected here because a keyword that matches everything is
16890
+ // never intentional.
16891
+ keywords: external_exports.array(external_exports.string().min(1)).min(1),
16846
16892
  caseSensitive: external_exports.boolean().default(false)
16847
16893
  });
16848
16894
  function isValidRegex(pattern, flags) {
@@ -17091,6 +17137,10 @@ var ListEventsResponse = external_exports.object({
17091
17137
  items: external_exports.array(Event),
17092
17138
  nextCursor: external_exports.string().nullable()
17093
17139
  }).meta({ id: "ListEventsResponse" });
17140
+ var IngestResponse = external_exports.object({
17141
+ accepted: external_exports.number().int().nonnegative(),
17142
+ duplicates: external_exports.number().int().nonnegative()
17143
+ }).meta({ id: "IngestResponse" });
17094
17144
  var ListFindingsQuery = external_exports.object({
17095
17145
  cursor: external_exports.string().optional(),
17096
17146
  limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
@@ -17480,6 +17530,7 @@ function buildFindingGroups(rows, opts = {}) {
17480
17530
  provider: toApiProvider(r.sourceTool),
17481
17531
  repo: r.repo,
17482
17532
  file: r.file,
17533
+ ...r.toolName === void 0 ? {} : { toolName: r.toolName },
17483
17534
  action: toApiAction(effectiveDbAction),
17484
17535
  detectedAt: r.occurredAt,
17485
17536
  confidence: r.confidence,
@@ -17552,6 +17603,7 @@ function buildHaystack(g, extra) {
17552
17603
  g.id,
17553
17604
  ...g.instances.map((i) => i.repo),
17554
17605
  ...g.instances.map((i) => i.file),
17606
+ ...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
17555
17607
  ...g.instances.map((i) => i.id),
17556
17608
  ...extra === void 0 ? [] : [extra]
17557
17609
  ].join(" ").toLowerCase();
@@ -17818,6 +17870,74 @@ var ProjectFilesScan = external_exports.object({
17818
17870
  scannedAt: external_exports.string()
17819
17871
  });
17820
17872
 
17873
+ // ../packages/schema/src/zod/ranges.ts
17874
+ var TIME_RANGES = ["7d", "30d", "3m", "6m"];
17875
+ var TimeRange = external_exports.enum(TIME_RANGES).meta({ id: "TimeRange" });
17876
+ var DEFAULT_TIME_RANGE = "7d";
17877
+ var RANGE_DAYS = {
17878
+ "7d": 7,
17879
+ "30d": 30,
17880
+ "3m": 90,
17881
+ "6m": 180
17882
+ };
17883
+ var TIME_RANGE_OR_DEFAULT = TimeRange.catch(DEFAULT_TIME_RANGE);
17884
+ function parseTimeRange(value) {
17885
+ return TIME_RANGE_OR_DEFAULT.parse(value);
17886
+ }
17887
+
17888
+ // ../packages/schema/src/zod/remediation.ts
17889
+ var SecretFindingState = external_exports.enum(["still-valid", "unknown", "invalid"]);
17890
+ var MaskedFindingLocation = external_exports.object({
17891
+ filePath: external_exports.string(),
17892
+ span: Span.optional()
17893
+ }).strict();
17894
+ var MaskedSecretFinding = external_exports.object({
17895
+ provider: external_exports.string(),
17896
+ maskedToken: external_exports.string(),
17897
+ where: MaskedFindingLocation,
17898
+ state: SecretFindingState,
17899
+ observedAt: external_exports.iso.datetime().optional()
17900
+ }).strict();
17901
+ var RotationChecklistEntry = external_exports.object({
17902
+ provider: external_exports.string(),
17903
+ maskedToken: external_exports.string(),
17904
+ consolePath: external_exports.string(),
17905
+ occurrenceSpread: external_exports.number().int().positive()
17906
+ }).strict();
17907
+ var RemediationOption = external_exports.enum([
17908
+ "redact-rotation-checklist",
17909
+ "redact-only",
17910
+ "set-secret-redact",
17911
+ "leave"
17912
+ ]);
17913
+ var RemediationEntrySource = external_exports.enum(["first-run", "pre-push", "secret-scan"]);
17914
+ var RemediationEntryContext = external_exports.object({
17915
+ entrySource: RemediationEntrySource
17916
+ }).strict();
17917
+ var RemediationOptionChoice = external_exports.object({
17918
+ id: RemediationOption,
17919
+ label: external_exports.string()
17920
+ });
17921
+ var BatchedRemediationDecision = external_exports.object({
17922
+ kind: external_exports.literal("decision"),
17923
+ entrySource: RemediationEntrySource,
17924
+ secretCount: external_exports.number().int().positive(),
17925
+ prompt: external_exports.string(),
17926
+ options: external_exports.tuple([
17927
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-rotation-checklist") }),
17928
+ RemediationOptionChoice.extend({ id: external_exports.literal("redact-only") }),
17929
+ RemediationOptionChoice.extend({ id: external_exports.literal("set-secret-redact") }),
17930
+ RemediationOptionChoice.extend({ id: external_exports.literal("leave") })
17931
+ ])
17932
+ });
17933
+ var NoRemediationDecision = external_exports.object({
17934
+ kind: external_exports.literal("no-decision")
17935
+ });
17936
+ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
17937
+ BatchedRemediationDecision,
17938
+ NoRemediationDecision
17939
+ ]);
17940
+
17821
17941
  // ../packages/schema/src/zod/rule-test.ts
17822
17942
  var TestRulesRequest = external_exports.object({
17823
17943
  rules: external_exports.array(Rule).min(1).max(100),
@@ -17876,10 +17996,8 @@ var SeveritySummaryResponse = external_exports.object({
17876
17996
  // All four severity levels are always present (count may be 0).
17877
17997
  bySeverity: external_exports.array(SeveritySummaryItem)
17878
17998
  }).meta({ id: "SeveritySummaryResponse" });
17879
- var SECURITY_RANGES = ["7d", "30d", "3m", "6m"];
17880
- var SecurityRange = external_exports.enum(SECURITY_RANGES).meta({ id: "SecurityRange" });
17881
17999
  var SecurityRangeQuery = external_exports.object({
17882
- range: external_exports.enum(SECURITY_RANGES).default("30d")
18000
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE)
17883
18001
  });
17884
18002
  var EnforcementActionKind = external_exports.enum(["blocked", "redacted", "warned"]).meta({ id: "EnforcementActionKind" });
17885
18003
  var EnforcementAction = external_exports.object({
@@ -17889,7 +18007,7 @@ var EnforcementAction = external_exports.object({
17889
18007
  delta: external_exports.number().int()
17890
18008
  }).meta({ id: "EnforcementAction" });
17891
18009
  var EnforcementActionsResponse = external_exports.object({
17892
- range: SecurityRange,
18010
+ range: TimeRange,
17893
18011
  // Sum of actions[].count in the window.
17894
18012
  total: external_exports.number().int().nonnegative(),
17895
18013
  // One entry per kind, always all three present (count may be 0).
@@ -17904,7 +18022,7 @@ var FindingsTimeseriesPoint = external_exports.object({
17904
18022
  medium: external_exports.number().int().nonnegative()
17905
18023
  }).meta({ id: "FindingsTimeseriesPoint" });
17906
18024
  var FindingsTimeseriesResponse = external_exports.object({
17907
- range: SecurityRange,
18025
+ range: TimeRange,
17908
18026
  granularity: TimeseriesGranularity,
17909
18027
  points: external_exports.array(FindingsTimeseriesPoint)
17910
18028
  }).meta({ id: "FindingsTimeseriesResponse" });
@@ -17919,7 +18037,7 @@ var MttrTrendPoint = external_exports.object({
17919
18037
  })
17920
18038
  }).meta({ id: "MttrTrendPoint" });
17921
18039
  var MttrTrendResponse = external_exports.object({
17922
- range: SecurityRange,
18040
+ range: TimeRange,
17923
18041
  granularity: TimeseriesGranularity,
17924
18042
  points: external_exports.array(MttrTrendPoint)
17925
18043
  }).meta({ id: "MttrTrendResponse" });
@@ -17946,11 +18064,11 @@ var TopSource = external_exports.object({
17946
18064
  findingsCount: external_exports.number().int().nonnegative()
17947
18065
  }).meta({ id: "TopSource" });
17948
18066
  var TopSourcesResponse = external_exports.object({
17949
- range: SecurityRange,
18067
+ range: TimeRange,
17950
18068
  items: external_exports.array(TopSource)
17951
18069
  }).meta({ id: "TopSourcesResponse" });
17952
18070
  var TopSourcesQuery = external_exports.object({
17953
- range: external_exports.enum(SECURITY_RANGES).default("30d"),
18071
+ range: external_exports.enum(TIME_RANGES).default(DEFAULT_TIME_RANGE),
17954
18072
  limit: external_exports.coerce.number().int().min(1).max(50).default(5),
17955
18073
  // Omit for both kinds.
17956
18074
  kind: external_exports.enum(SOURCE_KINDS).optional()
@@ -17963,7 +18081,7 @@ var ScanCoverageProvider = external_exports.object({
17963
18081
  supported: external_exports.boolean()
17964
18082
  }).meta({ id: "ScanCoverageProvider" });
17965
18083
  var ScanCoverageResponse = external_exports.object({
17966
- range: SecurityRange,
18084
+ range: TimeRange,
17967
18085
  providers: external_exports.array(ScanCoverageProvider)
17968
18086
  }).meta({ id: "ScanCoverageResponse" });
17969
18087
  var SubjectType = external_exports.enum(["repo", "user", "team", "policy", "share", "rule"]).meta({
@@ -18012,6 +18130,114 @@ var ApplyRecommendedActionResponse = external_exports.object({
18012
18130
  var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
18013
18131
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
18014
18132
 
18133
+ // ../packages/schema/src/zod/triage.ts
18134
+ var TriageHit = external_exports.object({
18135
+ ruleId: external_exports.string(),
18136
+ category: DetectionCategory,
18137
+ severity: Severity,
18138
+ maskedMatch: external_exports.string(),
18139
+ rawMatch: external_exports.string(),
18140
+ context: external_exports.string(),
18141
+ filePath: external_exports.string().optional(),
18142
+ confidence: external_exports.number().min(0).max(1),
18143
+ id: external_exports.string().optional(),
18144
+ valueFingerprint: external_exports.string().optional(),
18145
+ keyVersion: external_exports.number().int().nonnegative().optional()
18146
+ });
18147
+ var TriagePolicy = BuiltinPolicyId;
18148
+ var TriageCategoryRec = external_exports.object({
18149
+ category: DetectionCategory,
18150
+ action: TriagePolicy,
18151
+ reasoning: external_exports.string(),
18152
+ genuineCount: external_exports.number().int().nonnegative(),
18153
+ fpCount: external_exports.number().int().nonnegative(),
18154
+ // TriageHit ids judged false-positive in this category. fpCount must equal
18155
+ // this array's length — enforced by the consumer, not this schema.
18156
+ fpIds: external_exports.array(external_exports.string())
18157
+ });
18158
+ var TriageRecommendation = external_exports.object({
18159
+ perCategory: external_exports.array(TriageCategoryRec),
18160
+ notes: external_exports.string()
18161
+ });
18162
+
18163
+ // ../packages/schema/src/zod/setup-frame.ts
18164
+ var CalibrationCounts = external_exports.object({
18165
+ total: external_exports.number().int().nonnegative(),
18166
+ important: external_exports.number().int().nonnegative(),
18167
+ routine: external_exports.number().int().nonnegative()
18168
+ }).refine((c) => c.total === c.important + c.routine, {
18169
+ message: "total must equal important + routine",
18170
+ path: ["total"]
18171
+ });
18172
+ var FalsePositivePatternValue = external_exports.object({
18173
+ ruleId: external_exports.string(),
18174
+ category: DetectionCategory,
18175
+ valueFingerprint: external_exports.string(),
18176
+ keyVersion: external_exports.number().int().nonnegative()
18177
+ });
18178
+ var FalsePositivePatternGroup = external_exports.object({
18179
+ pattern: external_exports.string(),
18180
+ count: external_exports.number().int().nonnegative(),
18181
+ values: external_exports.array(FalsePositivePatternValue).min(1)
18182
+ });
18183
+ var CalibrationFindingKind = external_exports.object({
18184
+ category: DetectionCategory,
18185
+ count: external_exports.number().int().nonnegative(),
18186
+ egress: external_exports.boolean()
18187
+ });
18188
+ var CalibrationFrame = external_exports.object({
18189
+ counts: CalibrationCounts,
18190
+ routineCategories: external_exports.array(DetectionCategory),
18191
+ surfacedCategories: external_exports.array(DetectionCategory),
18192
+ findingKinds: external_exports.array(CalibrationFindingKind),
18193
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId),
18194
+ maskedFindings: external_exports.array(MaskedSecretFinding).optional(),
18195
+ falsePositivePatterns: external_exports.array(FalsePositivePatternGroup).optional()
18196
+ });
18197
+ var CalibrationPreviewCategory = TriageCategoryRec.pick({
18198
+ category: true,
18199
+ genuineCount: true,
18200
+ fpCount: true
18201
+ }).extend({
18202
+ egress: external_exports.boolean()
18203
+ });
18204
+ var CalibrationPreview = external_exports.object({
18205
+ categories: external_exports.array(CalibrationPreviewCategory),
18206
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId)
18207
+ });
18208
+ var CalibrationResult = external_exports.object({
18209
+ frame: CalibrationFrame,
18210
+ copy: external_exports.string()
18211
+ });
18212
+ var FirstRunCalibration = external_exports.enum(["scan", "floor"]);
18213
+ var SetupHandoffOption = external_exports.object({
18214
+ id: external_exports.enum(["enter-remediation", "open-dashboard", "not-now"]),
18215
+ label: external_exports.string()
18216
+ });
18217
+ var DashboardHandoffOptions = external_exports.tuple([
18218
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
18219
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
18220
+ ]);
18221
+ var ComposedRemediationOptions = external_exports.tuple([
18222
+ SetupHandoffOption.extend({ id: external_exports.literal("enter-remediation") }),
18223
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
18224
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
18225
+ ]);
18226
+ var SetupHandoffOffer = external_exports.object({
18227
+ worthALook: external_exports.number().int().nonnegative(),
18228
+ liveKeys: external_exports.number().int().nonnegative().optional(),
18229
+ options: external_exports.union([DashboardHandoffOptions, ComposedRemediationOptions])
18230
+ }).refine(
18231
+ (o) => o.options.some((opt) => opt.id === "enter-remediation") === (o.liveKeys ?? 0) > 0,
18232
+ {
18233
+ message: "the chain-entry option is present exactly when liveKeys > 0",
18234
+ path: ["options"]
18235
+ }
18236
+ ).refine((o) => (o.liveKeys ?? 0) <= o.worthALook, {
18237
+ message: "liveKeys is a subset of worthALook and cannot exceed it",
18238
+ path: ["liveKeys"]
18239
+ });
18240
+
18015
18241
  // ../packages/schema/src/zod/shares.ts
18016
18242
  var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
18017
18243
  var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
@@ -18197,36 +18423,6 @@ function reviewSeverityRank(reasons) {
18197
18423
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
18198
18424
  }
18199
18425
 
18200
- // ../packages/schema/src/zod/triage.ts
18201
- var TriageHit = external_exports.object({
18202
- ruleId: external_exports.string(),
18203
- category: DetectionCategory,
18204
- severity: Severity,
18205
- maskedMatch: external_exports.string(),
18206
- rawMatch: external_exports.string(),
18207
- context: external_exports.string(),
18208
- filePath: external_exports.string().optional(),
18209
- confidence: external_exports.number().min(0).max(1),
18210
- id: external_exports.string().optional(),
18211
- valueFingerprint: external_exports.string().optional(),
18212
- keyVersion: external_exports.number().int().nonnegative().optional()
18213
- });
18214
- var TriagePolicy = BuiltinPolicyId;
18215
- var TriageCategoryRec = external_exports.object({
18216
- category: DetectionCategory,
18217
- action: TriagePolicy,
18218
- reasoning: external_exports.string(),
18219
- genuineCount: external_exports.number().int().nonnegative(),
18220
- fpCount: external_exports.number().int().nonnegative(),
18221
- // TriageHit ids judged false-positive in this category. fpCount must equal
18222
- // this array's length — enforced by the consumer, not this schema.
18223
- fpIds: external_exports.array(external_exports.string())
18224
- });
18225
- var TriageRecommendation = external_exports.object({
18226
- perCategory: external_exports.array(TriageCategoryRec),
18227
- notes: external_exports.string()
18228
- });
18229
-
18230
18426
  // ../packages/local-ops/src/fs-scan.ts
18231
18427
  var import_ignore = __toESM(require_ignore(), 1);
18232
18428
  var AKAIGNORE_FILENAME = ".akaignore";
@@ -18922,6 +19118,10 @@ var TIMELINE_COLUMNS = `
18922
19118
  json_extract(attributes, '$.internal') AS internal,
18923
19119
  json_extract(attributes, '$.flagged') AS flagged`;
18924
19120
  var SESSION_ROOT = `event_type = 'session'`;
19121
+ var HAS_ACTIVITY = `EXISTS (
19122
+ SELECT 1 FROM audit_events c
19123
+ WHERE c.root_session_id = audit_events.id
19124
+ AND c.event_type NOT IN ('hook', 'config_scan'))`;
18925
19125
  var SqliteActivityRepository = class {
18926
19126
  constructor(db, now = () => Date.now()) {
18927
19127
  this.db = db;
@@ -18935,7 +19135,8 @@ var SqliteActivityRepository = class {
18935
19135
  const sessionsToday = countScalar(
18936
19136
  this.db,
18937
19137
  `SELECT count(*) AS n FROM audit_events
18938
- WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?`,
19138
+ WHERE ${SESSION_ROOT} AND started_at >= ? AND started_at < ?
19139
+ AND ${HAS_ACTIVITY}`,
18939
19140
  [startMs, endMs]
18940
19141
  );
18941
19142
  const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
@@ -19007,6 +19208,13 @@ var SqliteActivityRepository = class {
19007
19208
  );
19008
19209
  params.push(pattern, pattern, pattern, pattern, pattern, pattern);
19009
19210
  }
19211
+ const emptyCount = countScalar(
19212
+ this.db,
19213
+ `SELECT count(*) AS n FROM audit_events
19214
+ WHERE ${[...conditions, `NOT ${HAS_ACTIVITY}`].join(" AND ")}`,
19215
+ params
19216
+ );
19217
+ if (query.excludeEmpty) conditions.push(HAS_ACTIVITY);
19010
19218
  if (cursor) {
19011
19219
  conditions.push("(started_at < ? OR (started_at = ? AND id < ?))");
19012
19220
  params.push(cursor.startedAtMs, cursor.startedAtMs, cursor.id);
@@ -19043,7 +19251,7 @@ var SqliteActivityRepository = class {
19043
19251
  );
19044
19252
  const last = page[page.length - 1];
19045
19253
  const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19046
- return Promise.resolve({ items, nextCursor });
19254
+ return Promise.resolve({ items, nextCursor, emptyCount });
19047
19255
  }
19048
19256
  getSession(sessionId) {
19049
19257
  const rootRow = getRow(
@@ -20349,9 +20557,43 @@ var SqliteFindingsRepository = class {
20349
20557
  }))
20350
20558
  );
20351
20559
  }
20560
+ /** Live-enforced findings recorded for one session — a bare COUNT over the
20561
+ * session-stamped events (served by idx_events_session_id), so the Activity
20562
+ * page can label its findings link without the grouped pipeline. */
20563
+ sessionFindingsCount(sessionId) {
20564
+ if (!sessionId) return Promise.resolve(0);
20565
+ return Promise.resolve(
20566
+ countScalar(
20567
+ this.db,
20568
+ `SELECT count(*) AS n FROM findings f
20569
+ JOIN events e ON e.id = f.event_id
20570
+ WHERE json_extract(e.metadata, '$.sessionId') = :sessionId`,
20571
+ { sessionId }
20572
+ )
20573
+ );
20574
+ }
20575
+ /** Per-rule transcript firing tally for one session — reads the OTHER finding
20576
+ * store (inspection_findings, keyed to audit_events): every detection the
20577
+ * transcript pass recorded, counted per firing rather than per unique value.
20578
+ * Rides on session-scoped grouped responses so the findings view can
20579
+ * reconcile the Activity page's tally with the deduped groups it lists. */
20580
+ sessionFirings(sessionId) {
20581
+ return Object.fromEntries(
20582
+ countBy(
20583
+ this.db,
20584
+ `SELECT d.rule_id AS k, count(*) AS n
20585
+ FROM inspection_findings f
20586
+ JOIN audit_events e ON e.id = f.audit_event_id
20587
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
20588
+ WHERE e.root_session_id = :sessionId
20589
+ GROUP BY d.rule_id`,
20590
+ { sessionId }
20591
+ )
20592
+ );
20593
+ }
20352
20594
  /**
20353
- * Grouped findings for the dashboard — joins findings⋈events (repo/file
20354
- * from event metadata), groups by ruleId, computes per-filter-excluded facets,
20595
+ * Grouped findings for the dashboard — joins findings⋈events (repo/file/
20596
+ * toolName from event metadata), groups by ruleId, computes per-filter-excluded facets,
20355
20597
  * applies the requested filters, and sorts by severity then recency. Filtering
20356
20598
  * and faceting run in JS via the shared @akasecurity/schema helpers. `totals`
20357
20599
  * reflect the full filtered set; `items` is the requested
@@ -20368,11 +20610,16 @@ var SqliteFindingsRepository = class {
20368
20610
  * rule is ever restated in SQL.
20369
20611
  */
20370
20612
  listGroupedFindings(query) {
20371
- const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "");
20613
+ const sessionPredicate = query.sessionId ? `WHERE json_extract(e.metadata, '$.sessionId') = :sessionId` : "";
20614
+ const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
20615
+ const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
20616
+ predicate: sessionPredicate,
20617
+ params: sessionParams
20618
+ });
20372
20619
  const rows = allRows(
20373
20620
  this.db.prepare(
20374
20621
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
20375
- occurred_at, source_tool, repo, file, kind, finding_key, latest_status
20622
+ occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
20376
20623
  FROM (
20377
20624
  SELECT f.id AS id, f.rule_id AS rule_id, f.category AS category,
20378
20625
  f.severity AS severity, f.masked_match AS masked_match,
@@ -20380,6 +20627,7 @@ var SqliteFindingsRepository = class {
20380
20627
  e.occurred_at AS occurred_at, e.source_tool AS source_tool,
20381
20628
  json_extract(e.metadata, '$.repo') AS repo,
20382
20629
  json_extract(e.metadata, '$.filePath') AS file,
20630
+ json_extract(e.metadata, '$.toolName') AS tool_name,
20383
20631
  e.kind AS kind, f.finding_key AS finding_key,
20384
20632
  latest.status AS latest_status,
20385
20633
  ROW_NUMBER() OVER (
@@ -20390,11 +20638,12 @@ var SqliteFindingsRepository = class {
20390
20638
  JOIN events e ON e.id = f.event_id
20391
20639
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
20392
20640
  ON latest.finding_key = f.finding_key
20641
+ ${sessionPredicate}
20393
20642
  )
20394
20643
  WHERE rn <= :cap
20395
20644
  ORDER BY occurred_at DESC, id DESC`
20396
20645
  ),
20397
- { cap: PREVIEW_INSTANCES_PER_GROUP }
20646
+ { cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
20398
20647
  );
20399
20648
  const groupable = rows.map((r) => ({
20400
20649
  id: r.id,
@@ -20408,6 +20657,7 @@ var SqliteFindingsRepository = class {
20408
20657
  sourceTool: r.source_tool,
20409
20658
  repo: r.repo ?? "",
20410
20659
  file: r.file ?? "",
20660
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
20411
20661
  status: deriveInstanceStatus(r)
20412
20662
  }));
20413
20663
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -20426,7 +20676,13 @@ var SqliteFindingsRepository = class {
20426
20676
  };
20427
20677
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
20428
20678
  const items = sorted.slice(0, limit);
20429
- return Promise.resolve({ totals, facets, items, nextCursor: null });
20679
+ return Promise.resolve({
20680
+ totals,
20681
+ facets,
20682
+ items,
20683
+ nextCursor: null,
20684
+ ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
20685
+ });
20430
20686
  }
20431
20687
  /**
20432
20688
  * One row per rule_id, folding EVERY instance of the group into the values
@@ -20450,9 +20706,10 @@ var SqliteFindingsRepository = class {
20450
20706
  * would silently lose, so it is fetched only when the request actually
20451
20707
  * carries a `q`.
20452
20708
  */
20453
- groupAggregates(withSearchText) {
20709
+ groupAggregates(withSearchText, scope) {
20454
20710
  const searchTextColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.metadata, '$.repo')) AS repos,
20455
- group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files` : `, NULL AS repos, NULL AS files`;
20711
+ group_concat(DISTINCT json_extract(e.metadata, '$.filePath')) AS files,
20712
+ group_concat(DISTINCT 'via ' || json_extract(e.metadata, '$.toolName')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
20456
20713
  const rows = this.db.prepare(
20457
20714
  `SELECT f.rule_id AS rule_id,
20458
20715
  count(*) AS instance_count,
@@ -20469,8 +20726,9 @@ var SqliteFindingsRepository = class {
20469
20726
  JOIN events e ON e.id = f.event_id
20470
20727
  LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
20471
20728
  ON latest.finding_key = f.finding_key
20729
+ ${scope.predicate}
20472
20730
  GROUP BY f.rule_id`
20473
- ).all();
20731
+ ).all(scope.params);
20474
20732
  return new Map(
20475
20733
  rows.map((r) => [
20476
20734
  r.rule_id,
@@ -20494,7 +20752,9 @@ var SqliteFindingsRepository = class {
20494
20752
  // Left undefined (not '') when unfetched, so buildFindingGroups can
20495
20753
  // tell "no q this request" from "a group with no repo/file at all"
20496
20754
  // and skip priming a haystack nothing will read.
20497
- ...withSearchText ? { searchText: [r.repos ?? "", r.files ?? ""].filter((s) => s !== "").join(" ") } : {}
20755
+ ...withSearchText ? {
20756
+ searchText: [r.repos ?? "", r.files ?? "", r.tool_names ?? ""].filter((s) => s !== "").join(" ")
20757
+ } : {}
20498
20758
  }
20499
20759
  ])
20500
20760
  );
@@ -22230,7 +22490,6 @@ var SqliteScanLedgerRepository = class {
22230
22490
  // ../packages/persistence/src/repositories/security.ts
22231
22491
  var DAY_MS4 = 864e5;
22232
22492
  var SEVERITIES = ["critical", "high", "medium", "low"];
22233
- var RANGE_DAYS = { "7d": 7, "30d": 30, "3m": 90, "6m": 180 };
22234
22493
  var ACTION_TO_KIND = {
22235
22494
  block: "blocked",
22236
22495
  redact: "redacted",
@@ -22245,8 +22504,14 @@ var SCAN_COVERAGE = [
22245
22504
  { provider: "copilot", coverage: 0, supported: false },
22246
22505
  { provider: "api", coverage: 0, supported: false }
22247
22506
  ];
22507
+ var GRANULARITY = {
22508
+ "7d": "day",
22509
+ "30d": "day",
22510
+ "3m": "week",
22511
+ "6m": "week"
22512
+ };
22248
22513
  function granularityFor(range) {
22249
- return range === "7d" || range === "30d" ? "day" : "week";
22514
+ return GRANULARITY[range];
22250
22515
  }
22251
22516
  function startOfUtcDay2(ms) {
22252
22517
  return Math.floor(ms / DAY_MS4) * DAY_MS4;
@@ -23968,7 +24233,6 @@ var db_table_name_default = {
23968
24233
  "SELECT * FROM ",
23969
24234
  "SELECT COUNT(*) FROM ",
23970
24235
  "INSERT INTO ",
23971
- "UPDATE ",
23972
24236
  "DELETE FROM ",
23973
24237
  "CREATE TABLE ",
23974
24238
  "ALTER TABLE ",
@@ -25709,6 +25973,26 @@ function recordProjectInventory(db, target) {
25709
25973
  }
25710
25974
  }
25711
25975
 
25976
+ // ../packages/local-ops/src/self-exec.ts
25977
+ import { createRequire } from "module";
25978
+ var require2 = createRequire(import.meta.url);
25979
+ function isSea() {
25980
+ try {
25981
+ const sea = require2("node:sea");
25982
+ return typeof sea.isSea === "function" ? sea.isSea() : false;
25983
+ } catch {
25984
+ return false;
25985
+ }
25986
+ }
25987
+ function buildReinvocation(sea, execPath, entry, subcommand, extraArgs) {
25988
+ if (sea) return { command: execPath, args: [subcommand, ...extraArgs] };
25989
+ if (entry === void 0) return null;
25990
+ return { command: execPath, args: [entry, subcommand, ...extraArgs] };
25991
+ }
25992
+ function reinvokeArgv(subcommand, extraArgs = []) {
25993
+ return buildReinvocation(isSea(), process.execPath, process.argv[1], subcommand, extraArgs);
25994
+ }
25995
+
25712
25996
  // ../packages/local-ops/src/update-cache.ts
25713
25997
  import { spawn } from "child_process";
25714
25998
  import { existsSync as existsSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
@@ -25754,10 +26038,10 @@ function refreshCache(home) {
25754
26038
  });
25755
26039
  }
25756
26040
  function triggerBackgroundRefresh(home) {
25757
- const entry = process.argv[1];
25758
- if (!entry) return;
26041
+ const reinvoke = reinvokeArgv("__update-refresh", ["--home", home]);
26042
+ if (!reinvoke) return;
25759
26043
  try {
25760
- spawn(process.execPath, [entry, "__update-refresh", "--home", home], {
26044
+ spawn(reinvoke.command, reinvoke.args, {
25761
26045
  detached: true,
25762
26046
  stdio: "ignore"
25763
26047
  }).unref();
@@ -26080,10 +26364,10 @@ function runCompletion(argv) {
26080
26364
  // src/commands/dashboard.ts
26081
26365
  import { spawn as spawn3 } from "child_process";
26082
26366
  import { existsSync as existsSync8 } from "fs";
26083
- import { createRequire } from "module";
26367
+ import { createRequire as createRequire2 } from "module";
26084
26368
  import { createServer } from "net";
26085
26369
  import { dirname as dirname3, join as join14 } from "path";
26086
- import { fileURLToPath as fileURLToPath2 } from "url";
26370
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
26087
26371
  import { parseArgs as parseArgs2 } from "util";
26088
26372
 
26089
26373
  // src/lib/open-url.ts
@@ -26099,8 +26383,11 @@ function openUrl(url2) {
26099
26383
  }
26100
26384
 
26101
26385
  // src/commands/dashboard.ts
26102
- var require2 = createRequire(import.meta.url);
26386
+ var require3 = createRequire2(import.meta.url);
26103
26387
  var here = dirname3(fileURLToPath2(import.meta.url));
26388
+ function pkgBase() {
26389
+ return isSea() ? dirname3(process.execPath) : join14(here, "..");
26390
+ }
26104
26391
  function isPortFree(port) {
26105
26392
  return new Promise((resolve2) => {
26106
26393
  const probe = createServer();
@@ -26151,16 +26438,17 @@ async function runDashboard(argv) {
26151
26438
  return;
26152
26439
  }
26153
26440
  refreshUpdateMirror(dataDir());
26441
+ const base = pkgBase();
26154
26442
  const bundledServer = [
26155
- join14(here, "..", "web-ui", "server.js"),
26156
- join14(here, "..", "web-ui", "web-ui", "server.js")
26443
+ join14(base, "web-ui", "server.js"),
26444
+ join14(base, "web-ui", "web-ui", "server.js")
26157
26445
  ].find((p) => existsSync8(p));
26158
26446
  if (bundledServer) {
26159
26447
  launchStandalone(bundledServer, port, shouldOpen, url2);
26160
26448
  return;
26161
26449
  }
26162
- const webUiDir = dirname3(require2.resolve("@akasecurity/web-ui/package.json"));
26163
- const nextBin = require2.resolve("next/dist/bin/next", { paths: [webUiDir] });
26450
+ const webUiDir = dirname3(require3.resolve("@akasecurity/web-ui/package.json"));
26451
+ const nextBin = require3.resolve("next/dist/bin/next", { paths: [webUiDir] });
26164
26452
  const startNext = () => {
26165
26453
  const child = spawn3(
26166
26454
  process.execPath,
@@ -26189,7 +26477,13 @@ async function runDashboard(argv) {
26189
26477
  });
26190
26478
  }
26191
26479
  function launchStandalone(serverJs, port, shouldOpen, url2) {
26192
- const child = spawn3(process.execPath, [serverJs], {
26480
+ const reinvoke = reinvokeArgv("__dashboard-server", ["--server-js", serverJs]);
26481
+ if (!reinvoke) {
26482
+ process.stderr.write("aka dashboard: cannot resolve the CLI entry to launch the server.\n");
26483
+ process.exitCode = 1;
26484
+ return;
26485
+ }
26486
+ const child = spawn3(reinvoke.command, reinvoke.args, {
26193
26487
  cwd: dirname3(serverJs),
26194
26488
  stdio: ["inherit", "pipe", "inherit"],
26195
26489
  // Inherit the parent env so the child server sees PATH etc.; PORT/HOSTNAME tell
@@ -26200,6 +26494,16 @@ function launchStandalone(serverJs, port, shouldOpen, url2) {
26200
26494
  onReadyOpen(child, shouldOpen, url2, `Starting the AKA dashboard at ${url2}
26201
26495
  `);
26202
26496
  }
26497
+ async function runDashboardServer(argv) {
26498
+ const { values } = parseArgs2({ args: argv, options: { "server-js": { type: "string" } } });
26499
+ const serverJs = values["server-js"];
26500
+ if (serverJs === void 0 || serverJs === "") {
26501
+ process.stderr.write("aka __dashboard-server: --server-js <path> is required\n");
26502
+ process.exitCode = 1;
26503
+ return;
26504
+ }
26505
+ await import(pathToFileURL(serverJs).href);
26506
+ }
26203
26507
  function onReadyOpen(child, shouldOpen, url2, banner) {
26204
26508
  process.stdout.write(banner);
26205
26509
  let opened = false;
@@ -27214,6 +27518,7 @@ Then run \`aka init\` to set up the local store.
27214
27518
  }
27215
27519
 
27216
27520
  // src/commands/init.ts
27521
+ var PLUGIN_OFFER_IDENTITY = `${PRODUCT_NAME} \u2014 ${PRODUCT_TAGLINE}`;
27217
27522
  async function runInit(argv) {
27218
27523
  const { values } = parseArgs6({
27219
27524
  args: argv,
@@ -27265,6 +27570,7 @@ async function offerPluginInstall(autoYes) {
27265
27570
  if (!process.stdin.isTTY) {
27266
27571
  out.write(
27267
27572
  `
27573
+ ${PLUGIN_OFFER_IDENTITY}
27268
27574
  No Claude Code plugin detected. Install it via the marketplace:
27269
27575
  /plugin marketplace add ${agent.marketplaceSource ?? ""}
27270
27576
  /plugin install ${ref}
@@ -27273,8 +27579,11 @@ Or re-run \`aka init --yes\` to install it automatically.
27273
27579
  );
27274
27580
  return;
27275
27581
  }
27276
- if (!await confirm(`
27277
- Install the AKA Claude Code plugin now (via the marketplace)? [y/N] `)) {
27582
+ if (!await confirm(
27583
+ `
27584
+ ${PLUGIN_OFFER_IDENTITY}
27585
+ Install the ${PRODUCT_NAME} plugin for Claude Code now (via the marketplace)? [y/N] `
27586
+ )) {
27278
27587
  out.write("Skipped. Install anytime with `aka plugins install claude-code`.\n");
27279
27588
  return;
27280
27589
  }
@@ -27397,19 +27706,15 @@ function runScan(argv) {
27397
27706
  // src/commands/stats.ts
27398
27707
  import { parseArgs as parseArgs8 } from "util";
27399
27708
  var DAY_MS5 = 864e5;
27400
- var RANGE_DAYS2 = { "7d": 7, "30d": 30, "3m": 90, "6m": 180 };
27401
- function parseRange(value) {
27402
- return SECURITY_RANGES.includes(value ?? "") ? value : "30d";
27403
- }
27404
27709
  async function runStats(argv) {
27405
27710
  const { values } = parseArgs8({
27406
27711
  args: argv,
27407
27712
  options: { ...HOME_OPTION, range: { type: "string" } }
27408
27713
  });
27409
27714
  const home = homeBase(values.home);
27410
- const range = parseRange(values.range);
27715
+ const range = parseTimeRange(values.range);
27411
27716
  const db = openLocalDatabase(dataDir(home));
27412
- const tokenFromMs = Date.now() - RANGE_DAYS2[range] * DAY_MS5;
27717
+ const tokenFromMs = Date.now() - RANGE_DAYS[range] * DAY_MS5;
27413
27718
  const [health, severity, enforcement, packs2, recent, tokenReports] = await Promise.all([
27414
27719
  db.findings.healthSummary(),
27415
27720
  db.security.severitySummary(),
@@ -27432,7 +27737,7 @@ async function runStats(argv) {
27432
27737
  Enforcement (${range}): ${String(enforcement.total)} intercepted
27433
27738
  `);
27434
27739
  for (const a of enforcement.actions) {
27435
- const delta = a.delta === 0 ? "" : ` (${a.delta > 0 ? "+" : ""}${String(a.delta)} wk/wk)`;
27740
+ const delta = a.delta === 0 ? "" : ` (${a.delta > 0 ? "+" : ""}${String(a.delta)} vs prior ${range})`;
27436
27741
  out.write(` ${a.kind.padEnd(9)} ${String(a.count)}${delta}
27437
27742
  `);
27438
27743
  }
@@ -28249,9 +28554,19 @@ var COMMANDS = {
28249
28554
  // Hidden: the detached background refresh spawned by the passive update notice.
28250
28555
  "__update-refresh": (argv) => {
28251
28556
  refreshCache(homeFromArgv(argv));
28252
- }
28253
- };
28254
- var SKIP_NOTICE = /* @__PURE__ */ new Set(["update", "check-updates", "__update-refresh", "completion"]);
28557
+ },
28558
+ // Hidden: boots the bundled Next standalone server in-process (see dashboard.ts).
28559
+ // `aka dashboard` spawns this so a SEA binary can serve the dashboard without
28560
+ // exec-ing an external script. Returns the promise so main() awaits the boot.
28561
+ "__dashboard-server": (argv) => runDashboardServer(argv)
28562
+ };
28563
+ var SKIP_NOTICE = /* @__PURE__ */ new Set([
28564
+ "update",
28565
+ "check-updates",
28566
+ "__update-refresh",
28567
+ "__dashboard-server",
28568
+ "completion"
28569
+ ]);
28255
28570
  var USAGE = `aka \u2014 AI Traffic Control (local-first, everything stays on your machine)
28256
28571
 
28257
28572
  Usage: aka <command> [options]