chron-mcp 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -6162,7 +6162,7 @@ var require_websocket = __commonJS({
6162
6162
  var http = require("http");
6163
6163
  var net = require("net");
6164
6164
  var tls = require("tls");
6165
- var { randomBytes, createHash } = require("crypto");
6165
+ var { randomBytes, createHash: createHash2 } = require("crypto");
6166
6166
  var { Duplex, Readable } = require("stream");
6167
6167
  var { URL: URL2 } = require("url");
6168
6168
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -6842,7 +6842,7 @@ var require_websocket = __commonJS({
6842
6842
  abortHandshake(websocket, socket, "Invalid Upgrade header");
6843
6843
  return;
6844
6844
  }
6845
- const digest = createHash("sha1").update(key + GUID).digest("base64");
6845
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
6846
6846
  if (res.headers["sec-websocket-accept"] !== digest) {
6847
6847
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
6848
6848
  return;
@@ -7229,7 +7229,7 @@ var require_websocket_server = __commonJS({
7229
7229
  var EventEmitter = require("events");
7230
7230
  var http = require("http");
7231
7231
  var { Duplex } = require("stream");
7232
- var { createHash } = require("crypto");
7232
+ var { createHash: createHash2 } = require("crypto");
7233
7233
  var extension2 = require_extension();
7234
7234
  var PerMessageDeflate2 = require_permessage_deflate();
7235
7235
  var subprotocol2 = require_subprotocol();
@@ -7538,7 +7538,7 @@ var require_websocket_server = __commonJS({
7538
7538
  }
7539
7539
  if (this._state > RUNNING)
7540
7540
  return abortHandshake(socket, 503);
7541
- const digest = createHash("sha1").update(key + GUID).digest("base64");
7541
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
7542
7542
  const headers = [
7543
7543
  "HTTP/1.1 101 Switching Protocols",
7544
7544
  "Upgrade: websocket",
@@ -16539,7 +16539,9 @@ var init_schema = __esm({
16539
16539
  title: text("title").notNull().unique(),
16540
16540
  ai_tool: text("ai_tool"),
16541
16541
  created_at: text("created_at").notNull(),
16542
- updated_at: text("updated_at").notNull()
16542
+ updated_at: text("updated_at").notNull(),
16543
+ parent_session_id: text("parent_session_id"),
16544
+ external_ref: text("external_ref")
16543
16545
  });
16544
16546
  messages = sqliteTable("messages", {
16545
16547
  id: text("id").primaryKey(),
@@ -16548,7 +16550,9 @@ var init_schema = __esm({
16548
16550
  content: text("content").notNull(),
16549
16551
  created_at: text("created_at").notNull(),
16550
16552
  prev_hash: text("prev_hash"),
16551
- content_hash: text("content_hash")
16553
+ content_hash: text("content_hash"),
16554
+ // NULL = pre-migration row (treated as 'message'); set for all new rows
16555
+ event_type: text("event_type")
16552
16556
  });
16553
16557
  secrets_detected = sqliteTable("secrets_detected", {
16554
16558
  id: text("id").primaryKey(),
@@ -16579,14 +16583,25 @@ async function initDb(dbPath2) {
16579
16583
  for (const sql2 of CREATE_SQL) {
16580
16584
  await client.execute(sql2);
16581
16585
  }
16582
- const tableInfo = await client.execute("PRAGMA table_info(messages)");
16583
- const columns = tableInfo.rows.map((r) => r[1]);
16584
- if (!columns.includes("prev_hash")) {
16586
+ const msgInfo = await client.execute("PRAGMA table_info(messages)");
16587
+ const msgCols = msgInfo.rows.map((r) => r[1]);
16588
+ if (!msgCols.includes("prev_hash")) {
16585
16589
  await client.execute("ALTER TABLE messages ADD COLUMN prev_hash TEXT");
16586
16590
  }
16587
- if (!columns.includes("content_hash")) {
16591
+ if (!msgCols.includes("content_hash")) {
16588
16592
  await client.execute("ALTER TABLE messages ADD COLUMN content_hash TEXT");
16589
16593
  }
16594
+ if (!msgCols.includes("event_type")) {
16595
+ await client.execute("ALTER TABLE messages ADD COLUMN event_type TEXT");
16596
+ }
16597
+ const sessInfo = await client.execute("PRAGMA table_info(sessions)");
16598
+ const sessCols = sessInfo.rows.map((r) => r[1]);
16599
+ if (!sessCols.includes("parent_session_id")) {
16600
+ await client.execute("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
16601
+ }
16602
+ if (!sessCols.includes("external_ref")) {
16603
+ await client.execute("ALTER TABLE sessions ADD COLUMN external_ref TEXT");
16604
+ }
16590
16605
  return drizzle(client, { schema: schema_exports });
16591
16606
  }
16592
16607
  var import_os, import_fs, import_path, CREATE_SQL;
@@ -16605,7 +16620,9 @@ var init_db2 = __esm({
16605
16620
  title TEXT NOT NULL UNIQUE,
16606
16621
  ai_tool TEXT,
16607
16622
  created_at TEXT NOT NULL,
16608
- updated_at TEXT NOT NULL
16623
+ updated_at TEXT NOT NULL,
16624
+ parent_session_id TEXT,
16625
+ external_ref TEXT
16609
16626
  )`,
16610
16627
  `CREATE TABLE IF NOT EXISTS messages (
16611
16628
  id TEXT PRIMARY KEY,
@@ -16615,6 +16632,7 @@ var init_db2 = __esm({
16615
16632
  created_at TEXT NOT NULL,
16616
16633
  prev_hash TEXT,
16617
16634
  content_hash TEXT,
16635
+ event_type TEXT,
16618
16636
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
16619
16637
  )`,
16620
16638
  `CREATE TABLE IF NOT EXISTS secrets_detected (
@@ -16731,6 +16749,366 @@ var init_history = __esm({
16731
16749
  }
16732
16750
  });
16733
16751
 
16752
+ // src/cli/soc2.ts
16753
+ var soc2_exports = {};
16754
+ __export(soc2_exports, {
16755
+ buildSoc2Report: () => buildSoc2Report
16756
+ });
16757
+ async function q(db, sql2) {
16758
+ const res = await db.$client.execute(sql2);
16759
+ return res.rows;
16760
+ }
16761
+ async function queryOverview(db) {
16762
+ const rows = await q(db, `
16763
+ SELECT
16764
+ (SELECT COUNT(*) FROM sessions) AS total_sessions,
16765
+ (SELECT COUNT(*) FROM messages) AS total_messages,
16766
+ (SELECT COUNT(*) FROM messages WHERE role = 'user') AS user_messages,
16767
+ (SELECT COUNT(*) FROM messages WHERE role = 'assistant') AS ai_messages,
16768
+ (SELECT COUNT(*) FROM secrets_detected) AS total_detections,
16769
+ (SELECT MIN(created_at) FROM sessions) AS period_start,
16770
+ (SELECT MAX(updated_at) FROM sessions) AS period_end
16771
+ `);
16772
+ return rows[0];
16773
+ }
16774
+ async function queryToolUsage(db) {
16775
+ return await q(db, `
16776
+ SELECT
16777
+ COALESCE(s.ai_tool, 'unknown') AS tool,
16778
+ COUNT(DISTINCT s.id) AS sessions,
16779
+ COUNT(m.id) AS messages
16780
+ FROM sessions s
16781
+ LEFT JOIN messages m ON m.session_id = s.id
16782
+ GROUP BY s.ai_tool
16783
+ ORDER BY sessions DESC
16784
+ `);
16785
+ }
16786
+ async function queryDetectionsByType(db) {
16787
+ return await q(db, `
16788
+ SELECT type, COUNT(*) AS cnt, GROUP_CONCAT(masked_value, ', ') AS samples
16789
+ FROM secrets_detected
16790
+ GROUP BY type
16791
+ ORDER BY cnt DESC
16792
+ `);
16793
+ }
16794
+ async function querySessionInventory(db) {
16795
+ return await q(db, `
16796
+ SELECT
16797
+ s.id, s.title, s.ai_tool,
16798
+ s.created_at, s.updated_at,
16799
+ COUNT(m.id) AS message_count
16800
+ FROM sessions s
16801
+ LEFT JOIN messages m ON m.session_id = s.id
16802
+ GROUP BY s.id
16803
+ ORDER BY s.created_at DESC
16804
+ LIMIT 500
16805
+ `);
16806
+ }
16807
+ async function queryIntegrity(db) {
16808
+ const rows = await q(db, `
16809
+ SELECT session_id, content_hash, prev_hash
16810
+ FROM messages
16811
+ ORDER BY session_id, created_at, rowid
16812
+ `);
16813
+ const bySession = /* @__PURE__ */ new Map();
16814
+ for (const r of rows) {
16815
+ const list = bySession.get(r.session_id) ?? [];
16816
+ list.push({ content_hash: r.content_hash, prev_hash: r.prev_hash });
16817
+ bySession.set(r.session_id, list);
16818
+ }
16819
+ let intact = 0, broken = 0, unchained = 0;
16820
+ const brokenSessions = [];
16821
+ for (const [sid, chain] of bySession) {
16822
+ const chained = chain.filter((r) => r.content_hash !== null);
16823
+ if (chained.length === 0) {
16824
+ unchained++;
16825
+ continue;
16826
+ }
16827
+ let ok = true;
16828
+ for (let i = 1; i < chained.length; i++) {
16829
+ if (chained[i].prev_hash !== chained[i - 1].content_hash) {
16830
+ ok = false;
16831
+ break;
16832
+ }
16833
+ }
16834
+ if (ok)
16835
+ intact++;
16836
+ else {
16837
+ broken++;
16838
+ brokenSessions.push(sid.slice(0, 8));
16839
+ }
16840
+ }
16841
+ return { intact, broken, unchained, brokenSessions };
16842
+ }
16843
+ async function dbFingerprint(db) {
16844
+ const rows = await q(db, `SELECT COUNT(*) AS n, MAX(created_at) AS latest FROM messages`);
16845
+ const row = rows[0];
16846
+ const h = (0, import_crypto.createHash)("sha256").update(`${row?.n ?? 0}:${row?.latest ?? ""}`).digest("hex");
16847
+ return h.slice(0, 16);
16848
+ }
16849
+ function esc(s) {
16850
+ return (s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
16851
+ }
16852
+ function fmtDate2(iso) {
16853
+ if (!iso)
16854
+ return "\u2014";
16855
+ return iso.slice(0, 10);
16856
+ }
16857
+ function severityBadge(type) {
16858
+ const level = SEVERITY[type] ?? "Low";
16859
+ const color = level === "Critical" ? "#dc2626" : level === "High" ? "#d97706" : level === "Medium" ? "#2563eb" : "#6b7280";
16860
+ return `<span style="background:${color};color:#fff;padding:1px 6px;border-radius:3px;font-size:11px;font-weight:600">${level}</span>`;
16861
+ }
16862
+ function buildHtml(params) {
16863
+ const { host, fingerprint, overview, toolUsage, detections, inventory, integrity, generatedAt } = params;
16864
+ const periodStart = fmtDate2(overview.period_start) || "\u2014";
16865
+ const periodEnd = fmtDate2(overview.period_end) || "\u2014";
16866
+ const integrityStatus = integrity.broken === 0 ? `<span class="ok">&#10003; All ${integrity.intact} chained session(s) intact</span>` : `<span class="fail">&#9888; ${integrity.broken} session(s) with broken chain</span>`;
16867
+ const toolRows = toolUsage.map(
16868
+ (r) => `<tr><td>${esc(r.tool)}</td><td>${r.sessions}</td><td>${r.messages}</td></tr>`
16869
+ ).join("");
16870
+ const detectionRows = detections.map((r) => {
16871
+ const samples = (r.samples ?? "").split(", ").slice(0, 3).map(esc).join(", ");
16872
+ return `<tr><td>${esc(r.type)}</td><td>${severityBadge(r.type)}</td><td>${r.cnt}</td><td>${samples}</td></tr>`;
16873
+ }).join("") || '<tr><td colspan="4" style="color:#6b7280">No detections recorded</td></tr>';
16874
+ const inventoryRows = inventory.map(
16875
+ (r) => `<tr><td><code style="font-size:11px">${esc(r.id.slice(0, 8))}</code></td><td>${esc(r.title)}</td><td>${esc(r.ai_tool ?? "unknown")}</td><td>${fmtDate2(r.created_at)}</td><td>${fmtDate2(r.updated_at)}</td><td>${r.message_count}</td></tr>`
16876
+ ).join("") || '<tr><td colspan="6" style="color:#6b7280">No sessions</td></tr>';
16877
+ const brokenList = integrity.brokenSessions.length > 0 ? `<p class="fail">Sessions with broken chains: ${integrity.brokenSessions.map(esc).join(", ")}</p>` : "";
16878
+ return `<!DOCTYPE html>
16879
+ <html lang="en">
16880
+ <head>
16881
+ <meta charset="UTF-8">
16882
+ <meta name="viewport" content="width=device-width,initial-scale=1">
16883
+ <title>Chron AI Audit \u2014 SOC 2 Evidence Package</title>
16884
+ <style>${CSS}</style>
16885
+ </head>
16886
+ <body>
16887
+ <div class="page">
16888
+
16889
+ <!-- \u2460 COVER PAGE -->
16890
+ <div style="min-height:60vh;display:flex;flex-direction:column;justify-content:center">
16891
+ <div class="cover-badge">SOC 2 Evidence Package</div>
16892
+ <h1>AI Conversation Audit Report</h1>
16893
+ <p class="meta">Prepared by <strong>chron-mcp</strong> &nbsp;|&nbsp; Organisation: <strong>${esc(host)}</strong></p>
16894
+ <table style="width:auto;margin-bottom:0">
16895
+ <tr><th>Reporting Period</th><td>${periodStart} \u2013 ${periodEnd}</td></tr>
16896
+ <tr><th>Generated</th><td>${generatedAt}</td></tr>
16897
+ <tr><th>Evidence DB Fingerprint</th><td><span class="fingerprint">${fingerprint}</span></td></tr>
16898
+ <tr><th>Total Sessions</th><td>${overview.total_sessions}</td></tr>
16899
+ <tr><th>Total Messages</th><td>${overview.total_messages}</td></tr>
16900
+ </table>
16901
+ <p style="margin-top:20px;font-size:11px;color:#9ca3af">
16902
+ This report is generated from a locally-stored, tamper-evident SQLite database. All AI conversation
16903
+ activity has been recorded with cryptographic hash chaining. No conversation content is transmitted
16904
+ externally. This document is intended as supporting evidence for SOC 2 Type II audits under the
16905
+ Availability and Confidentiality trust service criteria.
16906
+ </p>
16907
+ </div>
16908
+
16909
+ <!-- \u2461 EXECUTIVE SUMMARY -->
16910
+ <h2 class="section-break">Executive Summary</h2>
16911
+ <div class="stat-grid">
16912
+ <div class="stat"><div class="stat-value">${overview.total_sessions}</div><div class="stat-label">Total Sessions</div></div>
16913
+ <div class="stat"><div class="stat-value">${overview.total_messages}</div><div class="stat-label">Total Messages</div></div>
16914
+ <div class="stat"><div class="stat-value">${overview.total_detections}</div><div class="stat-label">PII / Secret Detections</div></div>
16915
+ <div class="stat"><div class="stat-value">${integrity.broken === 0 ? "\u2713" : "\u2717"}</div><div class="stat-label">Chain Integrity</div></div>
16916
+ </div>
16917
+ <p>Messages: <strong>${overview.user_messages}</strong> user turns, <strong>${overview.ai_messages}</strong> AI turns. Reporting period: <strong>${periodStart}</strong> to <strong>${periodEnd}</strong>.</p>
16918
+ <p>Chain integrity: ${integrityStatus}${integrity.unchained > 0 ? ` (${integrity.unchained} session(s) pre-date hash chaining)` : ""}.</p>
16919
+ ${overview.total_detections > 0 ? `<p class="warn">&#9888; ${overview.total_detections} sensitive data detection(s) recorded. See Section 5 for details.</p>` : `<p class="ok">&#10003; No sensitive data detections recorded in this period.</p>`}
16920
+
16921
+ <!-- \u2462 AI TOOL USAGE -->
16922
+ <h2>AI Tool Usage</h2>
16923
+ <table>
16924
+ <thead><tr><th>AI Tool / Provider</th><th>Sessions</th><th>Messages</th></tr></thead>
16925
+ <tbody>${toolRows || '<tr><td colspan="3" style="color:#6b7280">No data</td></tr>'}</tbody>
16926
+ </table>
16927
+
16928
+ <!-- \u2463 ACTIVITY SUMMARY -->
16929
+ <h2>Activity Summary</h2>
16930
+ <p>All sessions are listed in the Session Inventory (Section 7). The table below shows per-tool message distribution.</p>
16931
+ <table>
16932
+ <thead><tr><th>AI Tool</th><th>User Messages</th><th>AI Messages</th><th>Ratio</th></tr></thead>
16933
+ <tbody>
16934
+ ${toolUsage.map((r) => {
16935
+ const ratio = r.messages > 0 ? (r.messages / 2 / r.sessions).toFixed(1) : "0";
16936
+ return `<tr><td>${esc(r.tool)}</td><td>~${Math.round(r.messages / 2)}</td><td>~${Math.round(r.messages / 2)}</td><td>${ratio} turns/session avg</td></tr>`;
16937
+ }).join("") || '<tr><td colspan="4" style="color:#6b7280">No data</td></tr>'}
16938
+ </tbody>
16939
+ </table>
16940
+ <p style="font-size:11px;color:#6b7280">Note: User/AI message split shown as estimate (\xF72) because exact per-session breakdown requires individual session queries.</p>
16941
+
16942
+ <!-- \u2464 SENSITIVE DATA CONTROLS -->
16943
+ <h2 class="section-break">Sensitive Data Controls</h2>
16944
+ <p>The following sensitive data types were detected during the reporting period. All values are masked \u2014 no plaintext credentials are stored in this report.</p>
16945
+ <table>
16946
+ <thead><tr><th>Detection Type</th><th>Severity</th><th>Count</th><th>Sample Masked Values</th></tr></thead>
16947
+ <tbody>${detectionRows}</tbody>
16948
+ </table>
16949
+ <h3>Control Measures</h3>
16950
+ <ul>
16951
+ <li>All detected values are masked at log time using token substitution (e.g. <code>$CHRON_CREDIT_CARD_1</code>).</li>
16952
+ <li>Original values are never stored in plaintext in the audit database.</li>
16953
+ <li>Detections trigger a <code>secret_detected</code> relay event to any connected SIEM (Splunk, Sentinel, LogScale).</li>
16954
+ <li>Luhn validation is applied to credit card numbers; IBAN mod-97 validation applied to bank account numbers.</li>
16955
+ </ul>
16956
+
16957
+ <!-- \u2465 AUDIT TRAIL INTEGRITY -->
16958
+ <h2>Audit Trail Integrity</h2>
16959
+ <p>Each message is linked to the previous message in its session via SHA-256 hash chaining. The chain is verified by confirming that each message's <code>prev_hash</code> matches the <code>content_hash</code> of the preceding message.</p>
16960
+ <table>
16961
+ <thead><tr><th>Metric</th><th>Value</th></tr></thead>
16962
+ <tbody>
16963
+ <tr><td>Sessions with intact chain</td><td class="ok">${integrity.intact}</td></tr>
16964
+ <tr><td>Sessions with broken chain</td><td class="${integrity.broken > 0 ? "fail" : "ok"}">${integrity.broken}</td></tr>
16965
+ <tr><td>Sessions pre-dating hash chaining</td><td>${integrity.unchained}</td></tr>
16966
+ </tbody>
16967
+ </table>
16968
+ ${brokenList}
16969
+ <p style="font-size:11px;color:#6b7280">
16970
+ Note: This report verifies <em>chain linkage</em> (prev_hash continuity). Full content-hash recomputation
16971
+ (which detects in-place tampering) can be performed with <code>chron verify &lt;session-id&gt;</code>.
16972
+ </p>
16973
+
16974
+ <!-- \u2466 SESSION INVENTORY -->
16975
+ <h2 class="section-break">Session Inventory</h2>
16976
+ <p>All sessions recorded in the audit database (most recent first, capped at 500 rows).</p>
16977
+ <table>
16978
+ <thead><tr><th>ID (prefix)</th><th>Title</th><th>AI Tool</th><th>Started</th><th>Last Active</th><th>Messages</th></tr></thead>
16979
+ <tbody>${inventoryRows}</tbody>
16980
+ </table>
16981
+
16982
+ <!-- \u2467 METHODOLOGY APPENDIX -->
16983
+ <h2>Methodology Appendix</h2>
16984
+ <div class="appendix">
16985
+ <h3>Data Collection</h3>
16986
+ <p>Chron MCP intercepts every AI conversation turn via the Model Context Protocol. Messages are written to a local SQLite database at <code>~/.chron/chron.db</code> (or the path specified by <code>CHRON_DB_PATH</code>).</p>
16987
+
16988
+ <h3>Hash Chain Construction</h3>
16989
+ <ul>
16990
+ <li>Each message record contains a <code>content_hash</code> field: <code>SHA-256(session_id || role || content || created_at || prev_hash)</code></li>
16991
+ <li>The <code>prev_hash</code> field links to the <code>content_hash</code> of the preceding message in the session.</li>
16992
+ <li>Any insertion, deletion, or modification of a message breaks the chain at that point.</li>
16993
+ </ul>
16994
+
16995
+ <h3>PII / Secret Detection</h3>
16996
+ <ul>
16997
+ <li>Detection uses regex patterns with algorithmic validation (Luhn for credit cards, mod-97 for IBAN).</li>
16998
+ <li>Context-aware patterns (DOB, passport) require relevant keywords within 120 characters.</li>
16999
+ <li>Severity levels: Critical (SSN, credit card, private key, IBAN), High (API keys, passwords, passport), Medium (email, phone), Low (internal IP, env vars).</li>
17000
+ </ul>
17001
+
17002
+ <h3>Limitations</h3>
17003
+ <ul>
17004
+ <li>This report covers conversation metadata and detection events. Message content is not included.</li>
17005
+ <li>Developer identity is derived from session titles and AI tool labels, not from authenticated principals.</li>
17006
+ <li>The chain integrity check in this report is a linkage check. Re-run <code>chron verify</code> per session for full content-hash verification.</li>
17007
+ </ul>
17008
+
17009
+ <p style="margin-top:12px;font-size:11px;color:#9ca3af">Generated by chron-mcp &nbsp;|&nbsp; Evidence DB fingerprint: <span class="fingerprint">${fingerprint}</span> &nbsp;|&nbsp; ${generatedAt}</p>
17010
+ </div>
17011
+
17012
+ </div>
17013
+ </body>
17014
+ </html>`;
17015
+ }
17016
+ async function buildSoc2Report(outputPath) {
17017
+ const db = await initDb();
17018
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19) + " UTC";
17019
+ const [overview, toolUsage, detections, inventory, integrity, fingerprint] = await Promise.all([
17020
+ queryOverview(db),
17021
+ queryToolUsage(db),
17022
+ queryDetectionsByType(db),
17023
+ querySessionInventory(db),
17024
+ queryIntegrity(db),
17025
+ dbFingerprint(db)
17026
+ ]);
17027
+ const html = buildHtml({
17028
+ host: (0, import_os2.hostname)(),
17029
+ fingerprint,
17030
+ overview,
17031
+ toolUsage,
17032
+ detections,
17033
+ inventory,
17034
+ integrity,
17035
+ generatedAt
17036
+ });
17037
+ (0, import_fs2.writeFileSync)(outputPath, html, "utf8");
17038
+ }
17039
+ var import_fs2, import_os2, import_crypto, SEVERITY, CSS;
17040
+ var init_soc2 = __esm({
17041
+ "src/cli/soc2.ts"() {
17042
+ "use strict";
17043
+ import_fs2 = require("fs");
17044
+ import_os2 = require("os");
17045
+ import_crypto = require("crypto");
17046
+ init_db2();
17047
+ SEVERITY = {
17048
+ private_key: "Critical",
17049
+ credit_card: "Critical",
17050
+ ssn: "Critical",
17051
+ iban: "Critical",
17052
+ passport: "High",
17053
+ dob: "High",
17054
+ aws_access_key: "High",
17055
+ anthropic_api_key: "High",
17056
+ openai_api_key: "High",
17057
+ google_api_key: "High",
17058
+ github_token: "High",
17059
+ slack_token: "High",
17060
+ stripe_key: "High",
17061
+ sendgrid_key: "High",
17062
+ huggingface_token: "High",
17063
+ jwt: "High",
17064
+ url_credentials: "High",
17065
+ password: "High",
17066
+ credential_pair: "High",
17067
+ email: "Medium",
17068
+ phone_us: "Medium",
17069
+ phone_e164: "Medium",
17070
+ internal_ip: "Low",
17071
+ env_value: "Low"
17072
+ };
17073
+ CSS = `
17074
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
17075
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 13px; color: #111; background: #fff; line-height: 1.5; }
17076
+ .page { max-width: 900px; margin: 0 auto; padding: 40px 48px; }
17077
+ h1 { font-size: 28px; font-weight: 700; margin-bottom: 4px; }
17078
+ h2 { font-size: 17px; font-weight: 700; margin: 32px 0 10px; padding-bottom: 4px; border-bottom: 2px solid #e5e7eb; color: #1f2937; }
17079
+ h3 { font-size: 14px; font-weight: 600; margin: 20px 0 6px; color: #374151; }
17080
+ p { margin-bottom: 8px; color: #374151; }
17081
+ .meta { color: #6b7280; font-size: 12px; margin-bottom: 32px; }
17082
+ .cover-badge { display: inline-block; background: #1d4ed8; color: #fff; padding: 3px 10px; border-radius: 4px; font-size: 12px; font-weight: 600; margin-bottom: 16px; }
17083
+ table { width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 12px; }
17084
+ th { background: #f3f4f6; text-align: left; padding: 6px 10px; font-weight: 600; color: #374151; border: 1px solid #e5e7eb; }
17085
+ td { padding: 5px 10px; border: 1px solid #e5e7eb; vertical-align: top; word-break: break-word; max-width: 300px; }
17086
+ tr:nth-child(even) td { background: #fafafa; }
17087
+ .stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; }
17088
+ .stat { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 12px 16px; }
17089
+ .stat-value { font-size: 24px; font-weight: 700; color: #1f2937; }
17090
+ .stat-label { font-size: 11px; color: #6b7280; margin-top: 2px; }
17091
+ .ok { color: #16a34a; font-weight: 600; }
17092
+ .warn { color: #d97706; font-weight: 600; }
17093
+ .fail { color: #dc2626; font-weight: 600; }
17094
+ .fingerprint { font-family: monospace; background: #f3f4f6; padding: 2px 6px; border-radius: 3px; font-size: 11px; }
17095
+ .section-break { page-break-before: always; }
17096
+ .appendix { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 16px 20px; margin-top: 12px; }
17097
+ .appendix p, .appendix li { color: #374151; font-size: 12px; }
17098
+ ul { padding-left: 20px; margin-bottom: 8px; }
17099
+ li { margin-bottom: 4px; }
17100
+ @media print {
17101
+ body { font-size: 11px; }
17102
+ .page { padding: 20px 24px; max-width: 100%; }
17103
+ h1 { font-size: 22px; }
17104
+ h2 { font-size: 14px; }
17105
+ .stat-value { font-size: 20px; }
17106
+ .section-break { page-break-before: always; }
17107
+ }
17108
+ `;
17109
+ }
17110
+ });
17111
+
16734
17112
  // src/cli/report.ts
16735
17113
  var report_exports = {};
16736
17114
  __export(report_exports, {
@@ -16814,6 +17192,16 @@ ${BOLD2}Chron Report${RESET2} ${dateFrom} \u2192 ${dateTo}
16814
17192
  process.stdout.write("\n");
16815
17193
  }
16816
17194
  async function runReport(args2) {
17195
+ const format = args2.find((a) => a.startsWith("--format="))?.slice("--format=".length);
17196
+ const outputArg = args2.find((a) => a.startsWith("--output="))?.slice("--output=".length);
17197
+ if (format === "soc2") {
17198
+ const output = outputArg ?? "soc2-report.html";
17199
+ const { buildSoc2Report: buildSoc2Report2 } = await Promise.resolve().then(() => (init_soc2(), soc2_exports));
17200
+ await buildSoc2Report2(output);
17201
+ process.stdout.write(`SOC 2 evidence package written to ${output}
17202
+ `);
17203
+ return;
17204
+ }
16817
17205
  const sinceArg = args2.find((a) => a.startsWith("--since="))?.slice("--since=".length);
16818
17206
  let cutoffDate = null;
16819
17207
  if (sinceArg) {
@@ -16914,7 +17302,7 @@ __export(settings_exports, {
16914
17302
  runSettings: () => runSettings
16915
17303
  });
16916
17304
  function dbPath() {
16917
- return process.env.CHRON_DB_PATH ?? (0, import_path2.join)((0, import_os2.homedir)(), ".chron", "chron.db");
17305
+ return process.env.CHRON_DB_PATH ?? (0, import_path2.join)((0, import_os3.homedir)(), ".chron", "chron.db");
16918
17306
  }
16919
17307
  async function runSettings(_args) {
16920
17308
  process.stdout.write(`
@@ -16928,11 +17316,11 @@ ${BOLD3}Chron Settings${RESET3}
16928
17316
 
16929
17317
  `);
16930
17318
  }
16931
- var import_os2, import_path2, RESET3, BOLD3, DIM2, CYAN2;
17319
+ var import_os3, import_path2, RESET3, BOLD3, DIM2, CYAN2;
16932
17320
  var init_settings = __esm({
16933
17321
  "src/cli/settings.ts"() {
16934
17322
  "use strict";
16935
- import_os2 = require("os");
17323
+ import_os3 = require("os");
16936
17324
  import_path2 = require("path");
16937
17325
  RESET3 = "\x1B[0m";
16938
17326
  BOLD3 = "\x1B[1m";
@@ -16946,7 +17334,7 @@ var secrets_exports = {};
16946
17334
  __export(secrets_exports, {
16947
17335
  runSecrets: () => runSecrets
16948
17336
  });
16949
- function fmtDate2(iso) {
17337
+ function fmtDate3(iso) {
16950
17338
  return iso.slice(0, 16).replace("T", " ");
16951
17339
  }
16952
17340
  async function runSecrets(args2) {
@@ -16983,7 +17371,7 @@ ${BOLD4}${session.title}${RESET4}
16983
17371
  `);
16984
17372
  for (const row of rows) {
16985
17373
  process.stdout.write(
16986
- ` ${DIM3}${fmtDate2(row.detected_at)}${RESET4} ${YELLOW2}${row.type.padEnd(maxType)}${RESET4} ${row.masked_value}
17374
+ ` ${DIM3}${fmtDate3(row.detected_at)}${RESET4} ${YELLOW2}${row.type.padEnd(maxType)}${RESET4} ${row.masked_value}
16987
17375
  `
16988
17376
  );
16989
17377
  }
@@ -17004,7 +17392,7 @@ ${BOLD4}${session.title}${RESET4}
17004
17392
  process.stdout.write("\n");
17005
17393
  for (const row of rows) {
17006
17394
  process.stdout.write(
17007
- ` ${DIM3}${fmtDate2(row.detected_at)}${RESET4} ${CYAN3}${row.session_id.slice(0, 8)}${RESET4} ${YELLOW2}${row.type.padEnd(maxType)}${RESET4} ${row.masked_value.padEnd(20)} ${DIM3}${row.title}${RESET4}
17395
+ ` ${DIM3}${fmtDate3(row.detected_at)}${RESET4} ${CYAN3}${row.session_id.slice(0, 8)}${RESET4} ${YELLOW2}${row.type.padEnd(maxType)}${RESET4} ${row.masked_value.padEnd(20)} ${DIM3}${row.title}${RESET4}
17008
17396
  `
17009
17397
  );
17010
17398
  }
@@ -17031,7 +17419,7 @@ var require_package = __commonJS({
17031
17419
  "package.json"(exports2, module2) {
17032
17420
  module2.exports = {
17033
17421
  name: "chron-mcp",
17034
- version: "0.1.17",
17422
+ version: "0.1.19",
17035
17423
  mcpName: "io.github.sirinivask/chron",
17036
17424
  description: "Audit-grade timestamped logs for every AI conversation",
17037
17425
  repository: {
@@ -17108,33 +17496,35 @@ function prompt(rl, question) {
17108
17496
  return new Promise((resolve) => rl.question(question, resolve));
17109
17497
  }
17110
17498
  function configPath() {
17111
- return (0, import_path3.join)((0, import_os3.homedir)(), ".chron", "config.json");
17499
+ return (0, import_path3.join)((0, import_os4.homedir)(), ".chron", "config.json");
17112
17500
  }
17113
17501
  function loadConfig() {
17114
17502
  try {
17115
- return JSON.parse((0, import_fs2.readFileSync)(configPath(), "utf8"));
17503
+ return JSON.parse((0, import_fs3.readFileSync)(configPath(), "utf8"));
17116
17504
  } catch {
17117
17505
  return {};
17118
17506
  }
17119
17507
  }
17120
17508
  function saveConfig(data) {
17121
- const dir = (0, import_path3.join)((0, import_os3.homedir)(), ".chron");
17122
- (0, import_fs2.mkdirSync)(dir, { recursive: true });
17123
- (0, import_fs2.writeFileSync)(configPath(), JSON.stringify(data, null, 2), "utf8");
17509
+ const dir = (0, import_path3.join)((0, import_os4.homedir)(), ".chron");
17510
+ (0, import_fs3.mkdirSync)(dir, { recursive: true });
17511
+ (0, import_fs3.writeFileSync)(configPath(), JSON.stringify(data, null, 2), "utf8");
17124
17512
  }
17125
17513
  function patchClaudeJson(vars) {
17126
- const path = (0, import_path3.join)((0, import_os3.homedir)(), ".claude.json");
17127
- if (!(0, import_fs2.existsSync)(path))
17514
+ const path = (0, import_path3.join)((0, import_os4.homedir)(), ".claude.json");
17515
+ if (!(0, import_fs3.existsSync)(path))
17128
17516
  return false;
17129
17517
  try {
17130
- const raw = (0, import_fs2.readFileSync)(path, "utf8");
17518
+ const raw = (0, import_fs3.readFileSync)(path, "utf8");
17131
17519
  const doc = JSON.parse(raw);
17132
17520
  const servers = doc.mcpServers ?? {};
17133
17521
  const chron = servers.chron ?? {};
17522
+ if (!chron.command)
17523
+ return false;
17134
17524
  chron.env = { ...chron.env ?? {}, ...vars };
17135
17525
  servers.chron = chron;
17136
17526
  doc.mcpServers = servers;
17137
- (0, import_fs2.writeFileSync)(path, JSON.stringify(doc, null, 2), "utf8");
17527
+ (0, import_fs3.writeFileSync)(path, JSON.stringify(doc, null, 2), "utf8");
17138
17528
  return true;
17139
17529
  } catch {
17140
17530
  return false;
@@ -17493,30 +17883,20 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
17493
17883
  process.exit(1);
17494
17884
  }
17495
17885
  const config = loadConfig();
17496
- config.sentinel = { dce, dcrId, stream, tenantId, clientId, connected_at: (/* @__PURE__ */ new Date()).toISOString() };
17886
+ config.sentinel = { dce, dcrId, stream, tenantId, clientId, clientSecret, connected_at: (/* @__PURE__ */ new Date()).toISOString() };
17497
17887
  saveConfig(config);
17888
+ patchClaudeJson({
17889
+ CHRON_SENTINEL_TENANT_ID: tenantId,
17890
+ CHRON_SENTINEL_CLIENT_ID: clientId,
17891
+ CHRON_SENTINEL_CLIENT_SECRET: clientSecret,
17892
+ CHRON_SENTINEL_DCE: dce,
17893
+ CHRON_SENTINEL_DCR_ID: dcrId,
17894
+ CHRON_SENTINEL_STREAM: stream
17895
+ });
17498
17896
  process.stdout.write(`${GREEN}${BOLD5}Connected!${RESET5} Test event sent to Sentinel workspace.
17499
17897
 
17500
17898
  `);
17501
- process.stdout.write(`${BOLD5}Add these env vars to your MCP client config:${RESET5}
17502
-
17503
- `);
17504
- process.stdout.write(` ${CYAN4}CHRON_SENTINEL_TENANT_ID${RESET5} ${tenantId}
17505
- `);
17506
- process.stdout.write(` ${CYAN4}CHRON_SENTINEL_CLIENT_ID${RESET5} ${clientId}
17507
- `);
17508
- process.stdout.write(` ${CYAN4}CHRON_SENTINEL_CLIENT_SECRET${RESET5} ${DIM4}<your-client-secret>${RESET5}
17509
- `);
17510
- process.stdout.write(` ${CYAN4}CHRON_SENTINEL_DCE${RESET5} ${dce}
17511
- `);
17512
- process.stdout.write(` ${CYAN4}CHRON_SENTINEL_DCR_ID${RESET5} ${dcrId}
17513
- `);
17514
- process.stdout.write(` ${CYAN4}CHRON_SENTINEL_STREAM${RESET5} ${stream}
17515
-
17516
- `);
17517
- process.stdout.write(`${DIM4}For Claude Code \u2014 add to the "env" block of the chron entry in ~/.claude.json.
17518
- `);
17519
- process.stdout.write(`Connection saved to ~/.chron/config.json${RESET5}
17899
+ process.stdout.write(`${DIM4}Config saved to ~/.chron/config.json \u2014 events will flow immediately in all running sessions.${RESET5}
17520
17900
 
17521
17901
  `);
17522
17902
  }
@@ -17549,14 +17929,14 @@ Integrations:
17549
17929
  }
17550
17930
  }
17551
17931
  }
17552
- var import_readline, import_os3, import_path3, import_fs2, RESET5, BOLD5, DIM4, CYAN4, GREEN, RED, YELLOW3;
17932
+ var import_readline, import_os4, import_path3, import_fs3, RESET5, BOLD5, DIM4, CYAN4, GREEN, RED, YELLOW3;
17553
17933
  var init_connect = __esm({
17554
17934
  "src/cli/connect.ts"() {
17555
17935
  "use strict";
17556
17936
  import_readline = require("readline");
17557
- import_os3 = require("os");
17937
+ import_os4 = require("os");
17558
17938
  import_path3 = require("path");
17559
- import_fs2 = require("fs");
17939
+ import_fs3 = require("fs");
17560
17940
  RESET5 = "\x1B[0m";
17561
17941
  BOLD5 = "\x1B[1m";
17562
17942
  DIM4 = "\x1B[2m";
@@ -17567,6 +17947,177 @@ var init_connect = __esm({
17567
17947
  }
17568
17948
  });
17569
17949
 
17950
+ // src/cli/summary.ts
17951
+ var summary_exports = {};
17952
+ __export(summary_exports, {
17953
+ buildSummary: () => buildSummary,
17954
+ runSummary: () => runSummary
17955
+ });
17956
+ function detectMutations(content) {
17957
+ const hits = [];
17958
+ for (const { label, re } of MUTATION_PATTERNS) {
17959
+ const m = re.exec(content);
17960
+ if (m)
17961
+ hits.push(`${label}: ${m[0].trim().slice(0, 80)}`);
17962
+ }
17963
+ return hits;
17964
+ }
17965
+ function formatLatency(ms) {
17966
+ if (ms < 1e3)
17967
+ return `${ms}ms`;
17968
+ return `${(ms / 1e3).toFixed(1)}s`;
17969
+ }
17970
+ function formatTs(iso) {
17971
+ return iso.replace("T", " ").replace(/\.\d+.*$/, "");
17972
+ }
17973
+ async function buildSummary(sessionPrefix) {
17974
+ const db = await initDb();
17975
+ const allSessions = await db.select().from(sessions);
17976
+ const session = allSessions.find((s) => s.id.startsWith(sessionPrefix));
17977
+ if (!session)
17978
+ return null;
17979
+ const msgs = await db.select().from(messages).where(eq(messages.session_id, session.id)).orderBy(asc(messages.created_at), asc(sql`rowid`));
17980
+ const secs = await db.select().from(secrets_detected).where(eq(secrets_detected.session_id, session.id)).orderBy(asc(secrets_detected.detected_at));
17981
+ const timeline = [];
17982
+ const mutationsList = [];
17983
+ const prodWrites = [];
17984
+ let userTurns = 0, aiTurns = 0;
17985
+ for (let i = 0; i < msgs.length; i++) {
17986
+ const m = msgs[i];
17987
+ const prev = msgs[i - 1];
17988
+ const latency_ms = prev ? new Date(m.created_at).getTime() - new Date(prev.created_at).getTime() : null;
17989
+ if (m.role === "user")
17990
+ userTurns++;
17991
+ else
17992
+ aiTurns++;
17993
+ const preview = m.content.replace(/\n/g, " ").slice(0, 120) + (m.content.length > 120 ? "\u2026" : "");
17994
+ timeline.push({ turn: i + 1, role: m.role, timestamp: m.created_at, latency_ms, preview });
17995
+ if (m.role === "assistant") {
17996
+ const mutations = detectMutations(m.content);
17997
+ for (const label of mutations) {
17998
+ mutationsList.push({ turn: i + 1, role: "assistant", snippet: label, label: label.split(":")[0] });
17999
+ }
18000
+ if (PROD_PATTERNS.test(m.content)) {
18001
+ prodWrites.push({ turn: i + 1, snippet: m.content.slice(0, 120) });
18002
+ }
18003
+ }
18004
+ }
18005
+ const firstTs = msgs[0]?.created_at;
18006
+ const lastTs = msgs[msgs.length - 1]?.created_at;
18007
+ const duration_minutes = firstTs && lastTs ? Math.round((new Date(lastTs).getTime() - new Date(firstTs).getTime()) / 6e4) : 0;
18008
+ return {
18009
+ session: {
18010
+ id: session.id,
18011
+ title: session.title,
18012
+ ai_tool: session.ai_tool,
18013
+ started: session.created_at,
18014
+ last_active: session.updated_at
18015
+ },
18016
+ stats: { total_messages: msgs.length, user_turns: userTurns, ai_turns: aiTurns, duration_minutes },
18017
+ secrets: secs.map((s) => ({ type: s.type, masked_value: s.masked_value, detected_at: s.detected_at })),
18018
+ mutations: mutationsList,
18019
+ prod_writes: prodWrites,
18020
+ timeline
18021
+ };
18022
+ }
18023
+ function printSummary(data) {
18024
+ const { session, stats, secrets, mutations, prod_writes, timeline } = data;
18025
+ process.stdout.write(`
18026
+ ${BOLD6}Session Summary${RESET6}
18027
+ `);
18028
+ process.stdout.write(`${DIM5}${session.title}${RESET6}
18029
+ `);
18030
+ process.stdout.write(`${DIM5}ID: ${session.id.slice(0, 8)} | Tool: ${session.ai_tool ?? "unknown"} | Started: ${formatTs(session.started)}${RESET6}
18031
+
18032
+ `);
18033
+ process.stdout.write(`${BOLD6}Stats${RESET6}
18034
+ `);
18035
+ process.stdout.write(` Messages ${stats.total_messages} (you: ${stats.user_turns}, ai: ${stats.ai_turns})
18036
+ `);
18037
+ process.stdout.write(` Duration ${stats.duration_minutes}m
18038
+
18039
+ `);
18040
+ process.stdout.write(`${BOLD6}Secrets touched${RESET6} ${secrets.length === 0 ? `${GREEN2}none${RESET6}` : `${RED2}${secrets.length} detection(s)${RESET6}`}
18041
+ `);
18042
+ for (const s of secrets) {
18043
+ process.stdout.write(` ${RED2}[${s.type}]${RESET6} ${DIM5}${s.masked_value}${RESET6}
18044
+ `);
18045
+ }
18046
+ if (secrets.length)
18047
+ process.stdout.write("\n");
18048
+ process.stdout.write(`${BOLD6}Mutations detected${RESET6} ${mutations.length === 0 ? `${DIM5}none${RESET6}` : `${mutations.length}`}
18049
+ `);
18050
+ for (const m of mutations) {
18051
+ process.stdout.write(` ${CYAN5}[turn ${m.turn}]${RESET6} ${m.snippet}
18052
+ `);
18053
+ }
18054
+ if (mutations.length)
18055
+ process.stdout.write("\n");
18056
+ if (prod_writes.length > 0) {
18057
+ process.stdout.write(`${BOLD6}${YELLOW4}\u26A0 Possible prod references${RESET6} ${prod_writes.length}
18058
+ `);
18059
+ for (const p of prod_writes) {
18060
+ process.stdout.write(` ${YELLOW4}[turn ${p.turn}]${RESET6} ${DIM5}${p.snippet}\u2026${RESET6}
18061
+ `);
18062
+ }
18063
+ process.stdout.write("\n");
18064
+ }
18065
+ process.stdout.write(`${BOLD6}Timeline${RESET6}
18066
+ `);
18067
+ for (const t of timeline) {
18068
+ const role = t.role === "user" ? `${BOLD6}you${RESET6}` : `${CYAN5}ai ${RESET6}`;
18069
+ const lat = t.latency_ms !== null ? ` ${DIM5}+${formatLatency(t.latency_ms)}${RESET6}` : "";
18070
+ process.stdout.write(` ${DIM5}[${t.turn.toString().padStart(3)}]${RESET6} ${role}${lat} ${DIM5}${t.preview}${RESET6}
18071
+ `);
18072
+ }
18073
+ process.stdout.write("\n");
18074
+ }
18075
+ async function runSummary(args2) {
18076
+ const [prefix, ...flags] = args2;
18077
+ const jsonMode = flags.includes("--json");
18078
+ if (!prefix) {
18079
+ process.stdout.write("Usage: chron summary <session-id-prefix> [--json]\n");
18080
+ process.exit(1);
18081
+ }
18082
+ const data = await buildSummary(prefix);
18083
+ if (!data) {
18084
+ process.stderr.write(`No session found with prefix: ${prefix}
18085
+ `);
18086
+ process.exit(1);
18087
+ }
18088
+ if (jsonMode) {
18089
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
18090
+ } else {
18091
+ printSummary(data);
18092
+ }
18093
+ }
18094
+ var RESET6, BOLD6, DIM5, CYAN5, GREEN2, YELLOW4, RED2, MUTATION_PATTERNS, PROD_PATTERNS;
18095
+ var init_summary = __esm({
18096
+ "src/cli/summary.ts"() {
18097
+ "use strict";
18098
+ init_drizzle_orm();
18099
+ init_db2();
18100
+ init_schema();
18101
+ RESET6 = "\x1B[0m";
18102
+ BOLD6 = "\x1B[1m";
18103
+ DIM5 = "\x1B[2m";
18104
+ CYAN5 = "\x1B[36m";
18105
+ GREEN2 = "\x1B[32m";
18106
+ YELLOW4 = "\x1B[33m";
18107
+ RED2 = "\x1B[31m";
18108
+ MUTATION_PATTERNS = [
18109
+ { label: "file write", re: /\b(?:wrote?|created?|saved?|updated?) (?:file |the file )?["']?([^\s"']{1,80})/i },
18110
+ { label: "git", re: /\bgit (?:commit|push|merge|rebase|reset|checkout)\b/i },
18111
+ { label: "package", re: /\b(?:npm|pip|yarn|pnpm|cargo|go get) (?:install|add|update|remove)\b/i },
18112
+ { label: "infra", re: /\b(?:kubectl|terraform|helm|pulumi|ansible|docker) (?:apply|deploy|run|push|create|delete)\b/i },
18113
+ { label: "db write", re: /\b(?:INSERT|UPDATE|DELETE|DROP|ALTER|CREATE TABLE)\b/i },
18114
+ { label: "API call", re: /\b(?:POST|PUT|PATCH|DELETE) (?:request|call|to )?\/?(?:https?:\/\/)?[a-z0-9\-._]+\/[^\s]{1,60}/i },
18115
+ { label: "deploy", re: /\b(?:deploy(?:ed|ing)?|ship(?:ped|ping)?|release(?:d|ing)?)\b/i }
18116
+ ];
18117
+ PROD_PATTERNS = /\b(?:production|prod[^a-z]|live\s+server|live\s+env|live\s+database)\b/i;
18118
+ }
18119
+ });
18120
+
17570
18121
  // src/cli/index.ts
17571
18122
  var [, , command, ...args] = process.argv;
17572
18123
  async function main() {
@@ -17601,6 +18152,11 @@ async function main() {
17601
18152
  await runConnect2(args);
17602
18153
  break;
17603
18154
  }
18155
+ case "summary": {
18156
+ const { runSummary: runSummary2 } = await Promise.resolve().then(() => (init_summary(), summary_exports));
18157
+ await runSummary2(args);
18158
+ break;
18159
+ }
17604
18160
  default: {
17605
18161
  const name = command ? `Unknown command: ${command}
17606
18162
 
@@ -17615,13 +18171,16 @@ Commands:
17615
18171
  secrets List detected secrets across sessions
17616
18172
  settings View current configuration
17617
18173
  connect Connect to a SIEM integration (crowdstrike, sentinel, splunk)
18174
+ summary Structured summary of a session (timeline, mutations, secrets)
17618
18175
 
17619
18176
  Options (history):
17620
18177
  --limit=<n> Max sessions to show (default: 20)
17621
18178
  <id-prefix> Show full log for the session with this ID prefix
17622
18179
 
17623
18180
  Options (report):
17624
- --since=<range> Filter by date: 7d, 30d, or YYYY-MM-DD (default: all time)
18181
+ --since=<range> Filter by date: 7d, 30d, or YYYY-MM-DD (default: all time)
18182
+ --format=soc2 Generate SOC 2 HTML evidence package
18183
+ --output=<file> Output file for --format=soc2 (default: soc2-report.html)
17625
18184
 
17626
18185
  Options (export / secrets):
17627
18186
  <id-prefix> Scope to a single session