chron-mcp 0.1.17 → 0.1.18
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 +585 -44
- package/dist/index.js +53611 -52817
- package/package.json +1 -1
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 =
|
|
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 =
|
|
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",
|
|
@@ -16731,6 +16731,366 @@ var init_history = __esm({
|
|
|
16731
16731
|
}
|
|
16732
16732
|
});
|
|
16733
16733
|
|
|
16734
|
+
// src/cli/soc2.ts
|
|
16735
|
+
var soc2_exports = {};
|
|
16736
|
+
__export(soc2_exports, {
|
|
16737
|
+
buildSoc2Report: () => buildSoc2Report
|
|
16738
|
+
});
|
|
16739
|
+
async function q(db, sql2) {
|
|
16740
|
+
const res = await db.$client.execute(sql2);
|
|
16741
|
+
return res.rows;
|
|
16742
|
+
}
|
|
16743
|
+
async function queryOverview(db) {
|
|
16744
|
+
const rows = await q(db, `
|
|
16745
|
+
SELECT
|
|
16746
|
+
(SELECT COUNT(*) FROM sessions) AS total_sessions,
|
|
16747
|
+
(SELECT COUNT(*) FROM messages) AS total_messages,
|
|
16748
|
+
(SELECT COUNT(*) FROM messages WHERE role = 'user') AS user_messages,
|
|
16749
|
+
(SELECT COUNT(*) FROM messages WHERE role = 'assistant') AS ai_messages,
|
|
16750
|
+
(SELECT COUNT(*) FROM secrets_detected) AS total_detections,
|
|
16751
|
+
(SELECT MIN(created_at) FROM sessions) AS period_start,
|
|
16752
|
+
(SELECT MAX(updated_at) FROM sessions) AS period_end
|
|
16753
|
+
`);
|
|
16754
|
+
return rows[0];
|
|
16755
|
+
}
|
|
16756
|
+
async function queryToolUsage(db) {
|
|
16757
|
+
return await q(db, `
|
|
16758
|
+
SELECT
|
|
16759
|
+
COALESCE(s.ai_tool, 'unknown') AS tool,
|
|
16760
|
+
COUNT(DISTINCT s.id) AS sessions,
|
|
16761
|
+
COUNT(m.id) AS messages
|
|
16762
|
+
FROM sessions s
|
|
16763
|
+
LEFT JOIN messages m ON m.session_id = s.id
|
|
16764
|
+
GROUP BY s.ai_tool
|
|
16765
|
+
ORDER BY sessions DESC
|
|
16766
|
+
`);
|
|
16767
|
+
}
|
|
16768
|
+
async function queryDetectionsByType(db) {
|
|
16769
|
+
return await q(db, `
|
|
16770
|
+
SELECT type, COUNT(*) AS cnt, GROUP_CONCAT(masked_value, ', ') AS samples
|
|
16771
|
+
FROM secrets_detected
|
|
16772
|
+
GROUP BY type
|
|
16773
|
+
ORDER BY cnt DESC
|
|
16774
|
+
`);
|
|
16775
|
+
}
|
|
16776
|
+
async function querySessionInventory(db) {
|
|
16777
|
+
return await q(db, `
|
|
16778
|
+
SELECT
|
|
16779
|
+
s.id, s.title, s.ai_tool,
|
|
16780
|
+
s.created_at, s.updated_at,
|
|
16781
|
+
COUNT(m.id) AS message_count
|
|
16782
|
+
FROM sessions s
|
|
16783
|
+
LEFT JOIN messages m ON m.session_id = s.id
|
|
16784
|
+
GROUP BY s.id
|
|
16785
|
+
ORDER BY s.created_at DESC
|
|
16786
|
+
LIMIT 500
|
|
16787
|
+
`);
|
|
16788
|
+
}
|
|
16789
|
+
async function queryIntegrity(db) {
|
|
16790
|
+
const rows = await q(db, `
|
|
16791
|
+
SELECT session_id, content_hash, prev_hash
|
|
16792
|
+
FROM messages
|
|
16793
|
+
ORDER BY session_id, created_at, rowid
|
|
16794
|
+
`);
|
|
16795
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
16796
|
+
for (const r of rows) {
|
|
16797
|
+
const list = bySession.get(r.session_id) ?? [];
|
|
16798
|
+
list.push({ content_hash: r.content_hash, prev_hash: r.prev_hash });
|
|
16799
|
+
bySession.set(r.session_id, list);
|
|
16800
|
+
}
|
|
16801
|
+
let intact = 0, broken = 0, unchained = 0;
|
|
16802
|
+
const brokenSessions = [];
|
|
16803
|
+
for (const [sid, chain] of bySession) {
|
|
16804
|
+
const chained = chain.filter((r) => r.content_hash !== null);
|
|
16805
|
+
if (chained.length === 0) {
|
|
16806
|
+
unchained++;
|
|
16807
|
+
continue;
|
|
16808
|
+
}
|
|
16809
|
+
let ok = true;
|
|
16810
|
+
for (let i = 1; i < chained.length; i++) {
|
|
16811
|
+
if (chained[i].prev_hash !== chained[i - 1].content_hash) {
|
|
16812
|
+
ok = false;
|
|
16813
|
+
break;
|
|
16814
|
+
}
|
|
16815
|
+
}
|
|
16816
|
+
if (ok)
|
|
16817
|
+
intact++;
|
|
16818
|
+
else {
|
|
16819
|
+
broken++;
|
|
16820
|
+
brokenSessions.push(sid.slice(0, 8));
|
|
16821
|
+
}
|
|
16822
|
+
}
|
|
16823
|
+
return { intact, broken, unchained, brokenSessions };
|
|
16824
|
+
}
|
|
16825
|
+
async function dbFingerprint(db) {
|
|
16826
|
+
const rows = await q(db, `SELECT COUNT(*) AS n, MAX(created_at) AS latest FROM messages`);
|
|
16827
|
+
const row = rows[0];
|
|
16828
|
+
const h = (0, import_crypto.createHash)("sha256").update(`${row?.n ?? 0}:${row?.latest ?? ""}`).digest("hex");
|
|
16829
|
+
return h.slice(0, 16);
|
|
16830
|
+
}
|
|
16831
|
+
function esc(s) {
|
|
16832
|
+
return (s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
16833
|
+
}
|
|
16834
|
+
function fmtDate2(iso) {
|
|
16835
|
+
if (!iso)
|
|
16836
|
+
return "\u2014";
|
|
16837
|
+
return iso.slice(0, 10);
|
|
16838
|
+
}
|
|
16839
|
+
function severityBadge(type) {
|
|
16840
|
+
const level = SEVERITY[type] ?? "Low";
|
|
16841
|
+
const color = level === "Critical" ? "#dc2626" : level === "High" ? "#d97706" : level === "Medium" ? "#2563eb" : "#6b7280";
|
|
16842
|
+
return `<span style="background:${color};color:#fff;padding:1px 6px;border-radius:3px;font-size:11px;font-weight:600">${level}</span>`;
|
|
16843
|
+
}
|
|
16844
|
+
function buildHtml(params) {
|
|
16845
|
+
const { host, fingerprint, overview, toolUsage, detections, inventory, integrity, generatedAt } = params;
|
|
16846
|
+
const periodStart = fmtDate2(overview.period_start) || "\u2014";
|
|
16847
|
+
const periodEnd = fmtDate2(overview.period_end) || "\u2014";
|
|
16848
|
+
const integrityStatus = integrity.broken === 0 ? `<span class="ok">✓ All ${integrity.intact} chained session(s) intact</span>` : `<span class="fail">⚠ ${integrity.broken} session(s) with broken chain</span>`;
|
|
16849
|
+
const toolRows = toolUsage.map(
|
|
16850
|
+
(r) => `<tr><td>${esc(r.tool)}</td><td>${r.sessions}</td><td>${r.messages}</td></tr>`
|
|
16851
|
+
).join("");
|
|
16852
|
+
const detectionRows = detections.map((r) => {
|
|
16853
|
+
const samples = (r.samples ?? "").split(", ").slice(0, 3).map(esc).join(", ");
|
|
16854
|
+
return `<tr><td>${esc(r.type)}</td><td>${severityBadge(r.type)}</td><td>${r.cnt}</td><td>${samples}</td></tr>`;
|
|
16855
|
+
}).join("") || '<tr><td colspan="4" style="color:#6b7280">No detections recorded</td></tr>';
|
|
16856
|
+
const inventoryRows = inventory.map(
|
|
16857
|
+
(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>`
|
|
16858
|
+
).join("") || '<tr><td colspan="6" style="color:#6b7280">No sessions</td></tr>';
|
|
16859
|
+
const brokenList = integrity.brokenSessions.length > 0 ? `<p class="fail">Sessions with broken chains: ${integrity.brokenSessions.map(esc).join(", ")}</p>` : "";
|
|
16860
|
+
return `<!DOCTYPE html>
|
|
16861
|
+
<html lang="en">
|
|
16862
|
+
<head>
|
|
16863
|
+
<meta charset="UTF-8">
|
|
16864
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
16865
|
+
<title>Chron AI Audit \u2014 SOC 2 Evidence Package</title>
|
|
16866
|
+
<style>${CSS}</style>
|
|
16867
|
+
</head>
|
|
16868
|
+
<body>
|
|
16869
|
+
<div class="page">
|
|
16870
|
+
|
|
16871
|
+
<!-- \u2460 COVER PAGE -->
|
|
16872
|
+
<div style="min-height:60vh;display:flex;flex-direction:column;justify-content:center">
|
|
16873
|
+
<div class="cover-badge">SOC 2 Evidence Package</div>
|
|
16874
|
+
<h1>AI Conversation Audit Report</h1>
|
|
16875
|
+
<p class="meta">Prepared by <strong>chron-mcp</strong> | Organisation: <strong>${esc(host)}</strong></p>
|
|
16876
|
+
<table style="width:auto;margin-bottom:0">
|
|
16877
|
+
<tr><th>Reporting Period</th><td>${periodStart} \u2013 ${periodEnd}</td></tr>
|
|
16878
|
+
<tr><th>Generated</th><td>${generatedAt}</td></tr>
|
|
16879
|
+
<tr><th>Evidence DB Fingerprint</th><td><span class="fingerprint">${fingerprint}</span></td></tr>
|
|
16880
|
+
<tr><th>Total Sessions</th><td>${overview.total_sessions}</td></tr>
|
|
16881
|
+
<tr><th>Total Messages</th><td>${overview.total_messages}</td></tr>
|
|
16882
|
+
</table>
|
|
16883
|
+
<p style="margin-top:20px;font-size:11px;color:#9ca3af">
|
|
16884
|
+
This report is generated from a locally-stored, tamper-evident SQLite database. All AI conversation
|
|
16885
|
+
activity has been recorded with cryptographic hash chaining. No conversation content is transmitted
|
|
16886
|
+
externally. This document is intended as supporting evidence for SOC 2 Type II audits under the
|
|
16887
|
+
Availability and Confidentiality trust service criteria.
|
|
16888
|
+
</p>
|
|
16889
|
+
</div>
|
|
16890
|
+
|
|
16891
|
+
<!-- \u2461 EXECUTIVE SUMMARY -->
|
|
16892
|
+
<h2 class="section-break">Executive Summary</h2>
|
|
16893
|
+
<div class="stat-grid">
|
|
16894
|
+
<div class="stat"><div class="stat-value">${overview.total_sessions}</div><div class="stat-label">Total Sessions</div></div>
|
|
16895
|
+
<div class="stat"><div class="stat-value">${overview.total_messages}</div><div class="stat-label">Total Messages</div></div>
|
|
16896
|
+
<div class="stat"><div class="stat-value">${overview.total_detections}</div><div class="stat-label">PII / Secret Detections</div></div>
|
|
16897
|
+
<div class="stat"><div class="stat-value">${integrity.broken === 0 ? "\u2713" : "\u2717"}</div><div class="stat-label">Chain Integrity</div></div>
|
|
16898
|
+
</div>
|
|
16899
|
+
<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>
|
|
16900
|
+
<p>Chain integrity: ${integrityStatus}${integrity.unchained > 0 ? ` (${integrity.unchained} session(s) pre-date hash chaining)` : ""}.</p>
|
|
16901
|
+
${overview.total_detections > 0 ? `<p class="warn">⚠ ${overview.total_detections} sensitive data detection(s) recorded. See Section 5 for details.</p>` : `<p class="ok">✓ No sensitive data detections recorded in this period.</p>`}
|
|
16902
|
+
|
|
16903
|
+
<!-- \u2462 AI TOOL USAGE -->
|
|
16904
|
+
<h2>AI Tool Usage</h2>
|
|
16905
|
+
<table>
|
|
16906
|
+
<thead><tr><th>AI Tool / Provider</th><th>Sessions</th><th>Messages</th></tr></thead>
|
|
16907
|
+
<tbody>${toolRows || '<tr><td colspan="3" style="color:#6b7280">No data</td></tr>'}</tbody>
|
|
16908
|
+
</table>
|
|
16909
|
+
|
|
16910
|
+
<!-- \u2463 ACTIVITY SUMMARY -->
|
|
16911
|
+
<h2>Activity Summary</h2>
|
|
16912
|
+
<p>All sessions are listed in the Session Inventory (Section 7). The table below shows per-tool message distribution.</p>
|
|
16913
|
+
<table>
|
|
16914
|
+
<thead><tr><th>AI Tool</th><th>User Messages</th><th>AI Messages</th><th>Ratio</th></tr></thead>
|
|
16915
|
+
<tbody>
|
|
16916
|
+
${toolUsage.map((r) => {
|
|
16917
|
+
const ratio = r.messages > 0 ? (r.messages / 2 / r.sessions).toFixed(1) : "0";
|
|
16918
|
+
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>`;
|
|
16919
|
+
}).join("") || '<tr><td colspan="4" style="color:#6b7280">No data</td></tr>'}
|
|
16920
|
+
</tbody>
|
|
16921
|
+
</table>
|
|
16922
|
+
<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>
|
|
16923
|
+
|
|
16924
|
+
<!-- \u2464 SENSITIVE DATA CONTROLS -->
|
|
16925
|
+
<h2 class="section-break">Sensitive Data Controls</h2>
|
|
16926
|
+
<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>
|
|
16927
|
+
<table>
|
|
16928
|
+
<thead><tr><th>Detection Type</th><th>Severity</th><th>Count</th><th>Sample Masked Values</th></tr></thead>
|
|
16929
|
+
<tbody>${detectionRows}</tbody>
|
|
16930
|
+
</table>
|
|
16931
|
+
<h3>Control Measures</h3>
|
|
16932
|
+
<ul>
|
|
16933
|
+
<li>All detected values are masked at log time using token substitution (e.g. <code>$CHRON_CREDIT_CARD_1</code>).</li>
|
|
16934
|
+
<li>Original values are never stored in plaintext in the audit database.</li>
|
|
16935
|
+
<li>Detections trigger a <code>secret_detected</code> relay event to any connected SIEM (Splunk, Sentinel, LogScale).</li>
|
|
16936
|
+
<li>Luhn validation is applied to credit card numbers; IBAN mod-97 validation applied to bank account numbers.</li>
|
|
16937
|
+
</ul>
|
|
16938
|
+
|
|
16939
|
+
<!-- \u2465 AUDIT TRAIL INTEGRITY -->
|
|
16940
|
+
<h2>Audit Trail Integrity</h2>
|
|
16941
|
+
<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>
|
|
16942
|
+
<table>
|
|
16943
|
+
<thead><tr><th>Metric</th><th>Value</th></tr></thead>
|
|
16944
|
+
<tbody>
|
|
16945
|
+
<tr><td>Sessions with intact chain</td><td class="ok">${integrity.intact}</td></tr>
|
|
16946
|
+
<tr><td>Sessions with broken chain</td><td class="${integrity.broken > 0 ? "fail" : "ok"}">${integrity.broken}</td></tr>
|
|
16947
|
+
<tr><td>Sessions pre-dating hash chaining</td><td>${integrity.unchained}</td></tr>
|
|
16948
|
+
</tbody>
|
|
16949
|
+
</table>
|
|
16950
|
+
${brokenList}
|
|
16951
|
+
<p style="font-size:11px;color:#6b7280">
|
|
16952
|
+
Note: This report verifies <em>chain linkage</em> (prev_hash continuity). Full content-hash recomputation
|
|
16953
|
+
(which detects in-place tampering) can be performed with <code>chron verify <session-id></code>.
|
|
16954
|
+
</p>
|
|
16955
|
+
|
|
16956
|
+
<!-- \u2466 SESSION INVENTORY -->
|
|
16957
|
+
<h2 class="section-break">Session Inventory</h2>
|
|
16958
|
+
<p>All sessions recorded in the audit database (most recent first, capped at 500 rows).</p>
|
|
16959
|
+
<table>
|
|
16960
|
+
<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>
|
|
16961
|
+
<tbody>${inventoryRows}</tbody>
|
|
16962
|
+
</table>
|
|
16963
|
+
|
|
16964
|
+
<!-- \u2467 METHODOLOGY APPENDIX -->
|
|
16965
|
+
<h2>Methodology Appendix</h2>
|
|
16966
|
+
<div class="appendix">
|
|
16967
|
+
<h3>Data Collection</h3>
|
|
16968
|
+
<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>
|
|
16969
|
+
|
|
16970
|
+
<h3>Hash Chain Construction</h3>
|
|
16971
|
+
<ul>
|
|
16972
|
+
<li>Each message record contains a <code>content_hash</code> field: <code>SHA-256(session_id || role || content || created_at || prev_hash)</code></li>
|
|
16973
|
+
<li>The <code>prev_hash</code> field links to the <code>content_hash</code> of the preceding message in the session.</li>
|
|
16974
|
+
<li>Any insertion, deletion, or modification of a message breaks the chain at that point.</li>
|
|
16975
|
+
</ul>
|
|
16976
|
+
|
|
16977
|
+
<h3>PII / Secret Detection</h3>
|
|
16978
|
+
<ul>
|
|
16979
|
+
<li>Detection uses regex patterns with algorithmic validation (Luhn for credit cards, mod-97 for IBAN).</li>
|
|
16980
|
+
<li>Context-aware patterns (DOB, passport) require relevant keywords within 120 characters.</li>
|
|
16981
|
+
<li>Severity levels: Critical (SSN, credit card, private key, IBAN), High (API keys, passwords, passport), Medium (email, phone), Low (internal IP, env vars).</li>
|
|
16982
|
+
</ul>
|
|
16983
|
+
|
|
16984
|
+
<h3>Limitations</h3>
|
|
16985
|
+
<ul>
|
|
16986
|
+
<li>This report covers conversation metadata and detection events. Message content is not included.</li>
|
|
16987
|
+
<li>Developer identity is derived from session titles and AI tool labels, not from authenticated principals.</li>
|
|
16988
|
+
<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>
|
|
16989
|
+
</ul>
|
|
16990
|
+
|
|
16991
|
+
<p style="margin-top:12px;font-size:11px;color:#9ca3af">Generated by chron-mcp | Evidence DB fingerprint: <span class="fingerprint">${fingerprint}</span> | ${generatedAt}</p>
|
|
16992
|
+
</div>
|
|
16993
|
+
|
|
16994
|
+
</div>
|
|
16995
|
+
</body>
|
|
16996
|
+
</html>`;
|
|
16997
|
+
}
|
|
16998
|
+
async function buildSoc2Report(outputPath) {
|
|
16999
|
+
const db = await initDb();
|
|
17000
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19) + " UTC";
|
|
17001
|
+
const [overview, toolUsage, detections, inventory, integrity, fingerprint] = await Promise.all([
|
|
17002
|
+
queryOverview(db),
|
|
17003
|
+
queryToolUsage(db),
|
|
17004
|
+
queryDetectionsByType(db),
|
|
17005
|
+
querySessionInventory(db),
|
|
17006
|
+
queryIntegrity(db),
|
|
17007
|
+
dbFingerprint(db)
|
|
17008
|
+
]);
|
|
17009
|
+
const html = buildHtml({
|
|
17010
|
+
host: (0, import_os2.hostname)(),
|
|
17011
|
+
fingerprint,
|
|
17012
|
+
overview,
|
|
17013
|
+
toolUsage,
|
|
17014
|
+
detections,
|
|
17015
|
+
inventory,
|
|
17016
|
+
integrity,
|
|
17017
|
+
generatedAt
|
|
17018
|
+
});
|
|
17019
|
+
(0, import_fs2.writeFileSync)(outputPath, html, "utf8");
|
|
17020
|
+
}
|
|
17021
|
+
var import_fs2, import_os2, import_crypto, SEVERITY, CSS;
|
|
17022
|
+
var init_soc2 = __esm({
|
|
17023
|
+
"src/cli/soc2.ts"() {
|
|
17024
|
+
"use strict";
|
|
17025
|
+
import_fs2 = require("fs");
|
|
17026
|
+
import_os2 = require("os");
|
|
17027
|
+
import_crypto = require("crypto");
|
|
17028
|
+
init_db2();
|
|
17029
|
+
SEVERITY = {
|
|
17030
|
+
private_key: "Critical",
|
|
17031
|
+
credit_card: "Critical",
|
|
17032
|
+
ssn: "Critical",
|
|
17033
|
+
iban: "Critical",
|
|
17034
|
+
passport: "High",
|
|
17035
|
+
dob: "High",
|
|
17036
|
+
aws_access_key: "High",
|
|
17037
|
+
anthropic_api_key: "High",
|
|
17038
|
+
openai_api_key: "High",
|
|
17039
|
+
google_api_key: "High",
|
|
17040
|
+
github_token: "High",
|
|
17041
|
+
slack_token: "High",
|
|
17042
|
+
stripe_key: "High",
|
|
17043
|
+
sendgrid_key: "High",
|
|
17044
|
+
huggingface_token: "High",
|
|
17045
|
+
jwt: "High",
|
|
17046
|
+
url_credentials: "High",
|
|
17047
|
+
password: "High",
|
|
17048
|
+
credential_pair: "High",
|
|
17049
|
+
email: "Medium",
|
|
17050
|
+
phone_us: "Medium",
|
|
17051
|
+
phone_e164: "Medium",
|
|
17052
|
+
internal_ip: "Low",
|
|
17053
|
+
env_value: "Low"
|
|
17054
|
+
};
|
|
17055
|
+
CSS = `
|
|
17056
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
17057
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 13px; color: #111; background: #fff; line-height: 1.5; }
|
|
17058
|
+
.page { max-width: 900px; margin: 0 auto; padding: 40px 48px; }
|
|
17059
|
+
h1 { font-size: 28px; font-weight: 700; margin-bottom: 4px; }
|
|
17060
|
+
h2 { font-size: 17px; font-weight: 700; margin: 32px 0 10px; padding-bottom: 4px; border-bottom: 2px solid #e5e7eb; color: #1f2937; }
|
|
17061
|
+
h3 { font-size: 14px; font-weight: 600; margin: 20px 0 6px; color: #374151; }
|
|
17062
|
+
p { margin-bottom: 8px; color: #374151; }
|
|
17063
|
+
.meta { color: #6b7280; font-size: 12px; margin-bottom: 32px; }
|
|
17064
|
+
.cover-badge { display: inline-block; background: #1d4ed8; color: #fff; padding: 3px 10px; border-radius: 4px; font-size: 12px; font-weight: 600; margin-bottom: 16px; }
|
|
17065
|
+
table { width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 12px; }
|
|
17066
|
+
th { background: #f3f4f6; text-align: left; padding: 6px 10px; font-weight: 600; color: #374151; border: 1px solid #e5e7eb; }
|
|
17067
|
+
td { padding: 5px 10px; border: 1px solid #e5e7eb; vertical-align: top; word-break: break-word; max-width: 300px; }
|
|
17068
|
+
tr:nth-child(even) td { background: #fafafa; }
|
|
17069
|
+
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; }
|
|
17070
|
+
.stat { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 12px 16px; }
|
|
17071
|
+
.stat-value { font-size: 24px; font-weight: 700; color: #1f2937; }
|
|
17072
|
+
.stat-label { font-size: 11px; color: #6b7280; margin-top: 2px; }
|
|
17073
|
+
.ok { color: #16a34a; font-weight: 600; }
|
|
17074
|
+
.warn { color: #d97706; font-weight: 600; }
|
|
17075
|
+
.fail { color: #dc2626; font-weight: 600; }
|
|
17076
|
+
.fingerprint { font-family: monospace; background: #f3f4f6; padding: 2px 6px; border-radius: 3px; font-size: 11px; }
|
|
17077
|
+
.section-break { page-break-before: always; }
|
|
17078
|
+
.appendix { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 16px 20px; margin-top: 12px; }
|
|
17079
|
+
.appendix p, .appendix li { color: #374151; font-size: 12px; }
|
|
17080
|
+
ul { padding-left: 20px; margin-bottom: 8px; }
|
|
17081
|
+
li { margin-bottom: 4px; }
|
|
17082
|
+
@media print {
|
|
17083
|
+
body { font-size: 11px; }
|
|
17084
|
+
.page { padding: 20px 24px; max-width: 100%; }
|
|
17085
|
+
h1 { font-size: 22px; }
|
|
17086
|
+
h2 { font-size: 14px; }
|
|
17087
|
+
.stat-value { font-size: 20px; }
|
|
17088
|
+
.section-break { page-break-before: always; }
|
|
17089
|
+
}
|
|
17090
|
+
`;
|
|
17091
|
+
}
|
|
17092
|
+
});
|
|
17093
|
+
|
|
16734
17094
|
// src/cli/report.ts
|
|
16735
17095
|
var report_exports = {};
|
|
16736
17096
|
__export(report_exports, {
|
|
@@ -16814,6 +17174,16 @@ ${BOLD2}Chron Report${RESET2} ${dateFrom} \u2192 ${dateTo}
|
|
|
16814
17174
|
process.stdout.write("\n");
|
|
16815
17175
|
}
|
|
16816
17176
|
async function runReport(args2) {
|
|
17177
|
+
const format = args2.find((a) => a.startsWith("--format="))?.slice("--format=".length);
|
|
17178
|
+
const outputArg = args2.find((a) => a.startsWith("--output="))?.slice("--output=".length);
|
|
17179
|
+
if (format === "soc2") {
|
|
17180
|
+
const output = outputArg ?? "soc2-report.html";
|
|
17181
|
+
const { buildSoc2Report: buildSoc2Report2 } = await Promise.resolve().then(() => (init_soc2(), soc2_exports));
|
|
17182
|
+
await buildSoc2Report2(output);
|
|
17183
|
+
process.stdout.write(`SOC 2 evidence package written to ${output}
|
|
17184
|
+
`);
|
|
17185
|
+
return;
|
|
17186
|
+
}
|
|
16817
17187
|
const sinceArg = args2.find((a) => a.startsWith("--since="))?.slice("--since=".length);
|
|
16818
17188
|
let cutoffDate = null;
|
|
16819
17189
|
if (sinceArg) {
|
|
@@ -16914,7 +17284,7 @@ __export(settings_exports, {
|
|
|
16914
17284
|
runSettings: () => runSettings
|
|
16915
17285
|
});
|
|
16916
17286
|
function dbPath() {
|
|
16917
|
-
return process.env.CHRON_DB_PATH ?? (0, import_path2.join)((0,
|
|
17287
|
+
return process.env.CHRON_DB_PATH ?? (0, import_path2.join)((0, import_os3.homedir)(), ".chron", "chron.db");
|
|
16918
17288
|
}
|
|
16919
17289
|
async function runSettings(_args) {
|
|
16920
17290
|
process.stdout.write(`
|
|
@@ -16928,11 +17298,11 @@ ${BOLD3}Chron Settings${RESET3}
|
|
|
16928
17298
|
|
|
16929
17299
|
`);
|
|
16930
17300
|
}
|
|
16931
|
-
var
|
|
17301
|
+
var import_os3, import_path2, RESET3, BOLD3, DIM2, CYAN2;
|
|
16932
17302
|
var init_settings = __esm({
|
|
16933
17303
|
"src/cli/settings.ts"() {
|
|
16934
17304
|
"use strict";
|
|
16935
|
-
|
|
17305
|
+
import_os3 = require("os");
|
|
16936
17306
|
import_path2 = require("path");
|
|
16937
17307
|
RESET3 = "\x1B[0m";
|
|
16938
17308
|
BOLD3 = "\x1B[1m";
|
|
@@ -16946,7 +17316,7 @@ var secrets_exports = {};
|
|
|
16946
17316
|
__export(secrets_exports, {
|
|
16947
17317
|
runSecrets: () => runSecrets
|
|
16948
17318
|
});
|
|
16949
|
-
function
|
|
17319
|
+
function fmtDate3(iso) {
|
|
16950
17320
|
return iso.slice(0, 16).replace("T", " ");
|
|
16951
17321
|
}
|
|
16952
17322
|
async function runSecrets(args2) {
|
|
@@ -16983,7 +17353,7 @@ ${BOLD4}${session.title}${RESET4}
|
|
|
16983
17353
|
`);
|
|
16984
17354
|
for (const row of rows) {
|
|
16985
17355
|
process.stdout.write(
|
|
16986
|
-
` ${DIM3}${
|
|
17356
|
+
` ${DIM3}${fmtDate3(row.detected_at)}${RESET4} ${YELLOW2}${row.type.padEnd(maxType)}${RESET4} ${row.masked_value}
|
|
16987
17357
|
`
|
|
16988
17358
|
);
|
|
16989
17359
|
}
|
|
@@ -17004,7 +17374,7 @@ ${BOLD4}${session.title}${RESET4}
|
|
|
17004
17374
|
process.stdout.write("\n");
|
|
17005
17375
|
for (const row of rows) {
|
|
17006
17376
|
process.stdout.write(
|
|
17007
|
-
` ${DIM3}${
|
|
17377
|
+
` ${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
17378
|
`
|
|
17009
17379
|
);
|
|
17010
17380
|
}
|
|
@@ -17031,7 +17401,7 @@ var require_package = __commonJS({
|
|
|
17031
17401
|
"package.json"(exports2, module2) {
|
|
17032
17402
|
module2.exports = {
|
|
17033
17403
|
name: "chron-mcp",
|
|
17034
|
-
version: "0.1.
|
|
17404
|
+
version: "0.1.18",
|
|
17035
17405
|
mcpName: "io.github.sirinivask/chron",
|
|
17036
17406
|
description: "Audit-grade timestamped logs for every AI conversation",
|
|
17037
17407
|
repository: {
|
|
@@ -17108,33 +17478,35 @@ function prompt(rl, question) {
|
|
|
17108
17478
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
17109
17479
|
}
|
|
17110
17480
|
function configPath() {
|
|
17111
|
-
return (0, import_path3.join)((0,
|
|
17481
|
+
return (0, import_path3.join)((0, import_os4.homedir)(), ".chron", "config.json");
|
|
17112
17482
|
}
|
|
17113
17483
|
function loadConfig() {
|
|
17114
17484
|
try {
|
|
17115
|
-
return JSON.parse((0,
|
|
17485
|
+
return JSON.parse((0, import_fs3.readFileSync)(configPath(), "utf8"));
|
|
17116
17486
|
} catch {
|
|
17117
17487
|
return {};
|
|
17118
17488
|
}
|
|
17119
17489
|
}
|
|
17120
17490
|
function saveConfig(data) {
|
|
17121
|
-
const dir = (0, import_path3.join)((0,
|
|
17122
|
-
(0,
|
|
17123
|
-
(0,
|
|
17491
|
+
const dir = (0, import_path3.join)((0, import_os4.homedir)(), ".chron");
|
|
17492
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
17493
|
+
(0, import_fs3.writeFileSync)(configPath(), JSON.stringify(data, null, 2), "utf8");
|
|
17124
17494
|
}
|
|
17125
17495
|
function patchClaudeJson(vars) {
|
|
17126
|
-
const path = (0, import_path3.join)((0,
|
|
17127
|
-
if (!(0,
|
|
17496
|
+
const path = (0, import_path3.join)((0, import_os4.homedir)(), ".claude.json");
|
|
17497
|
+
if (!(0, import_fs3.existsSync)(path))
|
|
17128
17498
|
return false;
|
|
17129
17499
|
try {
|
|
17130
|
-
const raw = (0,
|
|
17500
|
+
const raw = (0, import_fs3.readFileSync)(path, "utf8");
|
|
17131
17501
|
const doc = JSON.parse(raw);
|
|
17132
17502
|
const servers = doc.mcpServers ?? {};
|
|
17133
17503
|
const chron = servers.chron ?? {};
|
|
17504
|
+
if (!chron.command)
|
|
17505
|
+
return false;
|
|
17134
17506
|
chron.env = { ...chron.env ?? {}, ...vars };
|
|
17135
17507
|
servers.chron = chron;
|
|
17136
17508
|
doc.mcpServers = servers;
|
|
17137
|
-
(0,
|
|
17509
|
+
(0, import_fs3.writeFileSync)(path, JSON.stringify(doc, null, 2), "utf8");
|
|
17138
17510
|
return true;
|
|
17139
17511
|
} catch {
|
|
17140
17512
|
return false;
|
|
@@ -17493,30 +17865,20 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
|
|
|
17493
17865
|
process.exit(1);
|
|
17494
17866
|
}
|
|
17495
17867
|
const config = loadConfig();
|
|
17496
|
-
config.sentinel = { dce, dcrId, stream, tenantId, clientId, connected_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
17868
|
+
config.sentinel = { dce, dcrId, stream, tenantId, clientId, clientSecret, connected_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
17497
17869
|
saveConfig(config);
|
|
17870
|
+
patchClaudeJson({
|
|
17871
|
+
CHRON_SENTINEL_TENANT_ID: tenantId,
|
|
17872
|
+
CHRON_SENTINEL_CLIENT_ID: clientId,
|
|
17873
|
+
CHRON_SENTINEL_CLIENT_SECRET: clientSecret,
|
|
17874
|
+
CHRON_SENTINEL_DCE: dce,
|
|
17875
|
+
CHRON_SENTINEL_DCR_ID: dcrId,
|
|
17876
|
+
CHRON_SENTINEL_STREAM: stream
|
|
17877
|
+
});
|
|
17498
17878
|
process.stdout.write(`${GREEN}${BOLD5}Connected!${RESET5} Test event sent to Sentinel workspace.
|
|
17499
17879
|
|
|
17500
17880
|
`);
|
|
17501
|
-
process.stdout.write(`${
|
|
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}
|
|
17881
|
+
process.stdout.write(`${DIM4}Config saved to ~/.chron/config.json \u2014 events will flow immediately in all running sessions.${RESET5}
|
|
17520
17882
|
|
|
17521
17883
|
`);
|
|
17522
17884
|
}
|
|
@@ -17549,14 +17911,14 @@ Integrations:
|
|
|
17549
17911
|
}
|
|
17550
17912
|
}
|
|
17551
17913
|
}
|
|
17552
|
-
var import_readline,
|
|
17914
|
+
var import_readline, import_os4, import_path3, import_fs3, RESET5, BOLD5, DIM4, CYAN4, GREEN, RED, YELLOW3;
|
|
17553
17915
|
var init_connect = __esm({
|
|
17554
17916
|
"src/cli/connect.ts"() {
|
|
17555
17917
|
"use strict";
|
|
17556
17918
|
import_readline = require("readline");
|
|
17557
|
-
|
|
17919
|
+
import_os4 = require("os");
|
|
17558
17920
|
import_path3 = require("path");
|
|
17559
|
-
|
|
17921
|
+
import_fs3 = require("fs");
|
|
17560
17922
|
RESET5 = "\x1B[0m";
|
|
17561
17923
|
BOLD5 = "\x1B[1m";
|
|
17562
17924
|
DIM4 = "\x1B[2m";
|
|
@@ -17567,6 +17929,177 @@ var init_connect = __esm({
|
|
|
17567
17929
|
}
|
|
17568
17930
|
});
|
|
17569
17931
|
|
|
17932
|
+
// src/cli/summary.ts
|
|
17933
|
+
var summary_exports = {};
|
|
17934
|
+
__export(summary_exports, {
|
|
17935
|
+
buildSummary: () => buildSummary,
|
|
17936
|
+
runSummary: () => runSummary
|
|
17937
|
+
});
|
|
17938
|
+
function detectMutations(content) {
|
|
17939
|
+
const hits = [];
|
|
17940
|
+
for (const { label, re } of MUTATION_PATTERNS) {
|
|
17941
|
+
const m = re.exec(content);
|
|
17942
|
+
if (m)
|
|
17943
|
+
hits.push(`${label}: ${m[0].trim().slice(0, 80)}`);
|
|
17944
|
+
}
|
|
17945
|
+
return hits;
|
|
17946
|
+
}
|
|
17947
|
+
function formatLatency(ms) {
|
|
17948
|
+
if (ms < 1e3)
|
|
17949
|
+
return `${ms}ms`;
|
|
17950
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
17951
|
+
}
|
|
17952
|
+
function formatTs(iso) {
|
|
17953
|
+
return iso.replace("T", " ").replace(/\.\d+.*$/, "");
|
|
17954
|
+
}
|
|
17955
|
+
async function buildSummary(sessionPrefix) {
|
|
17956
|
+
const db = await initDb();
|
|
17957
|
+
const allSessions = await db.select().from(sessions);
|
|
17958
|
+
const session = allSessions.find((s) => s.id.startsWith(sessionPrefix));
|
|
17959
|
+
if (!session)
|
|
17960
|
+
return null;
|
|
17961
|
+
const msgs = await db.select().from(messages).where(eq(messages.session_id, session.id)).orderBy(asc(messages.created_at), asc(sql`rowid`));
|
|
17962
|
+
const secs = await db.select().from(secrets_detected).where(eq(secrets_detected.session_id, session.id)).orderBy(asc(secrets_detected.detected_at));
|
|
17963
|
+
const timeline = [];
|
|
17964
|
+
const mutationsList = [];
|
|
17965
|
+
const prodWrites = [];
|
|
17966
|
+
let userTurns = 0, aiTurns = 0;
|
|
17967
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
17968
|
+
const m = msgs[i];
|
|
17969
|
+
const prev = msgs[i - 1];
|
|
17970
|
+
const latency_ms = prev ? new Date(m.created_at).getTime() - new Date(prev.created_at).getTime() : null;
|
|
17971
|
+
if (m.role === "user")
|
|
17972
|
+
userTurns++;
|
|
17973
|
+
else
|
|
17974
|
+
aiTurns++;
|
|
17975
|
+
const preview = m.content.replace(/\n/g, " ").slice(0, 120) + (m.content.length > 120 ? "\u2026" : "");
|
|
17976
|
+
timeline.push({ turn: i + 1, role: m.role, timestamp: m.created_at, latency_ms, preview });
|
|
17977
|
+
if (m.role === "assistant") {
|
|
17978
|
+
const mutations = detectMutations(m.content);
|
|
17979
|
+
for (const label of mutations) {
|
|
17980
|
+
mutationsList.push({ turn: i + 1, role: "assistant", snippet: label, label: label.split(":")[0] });
|
|
17981
|
+
}
|
|
17982
|
+
if (PROD_PATTERNS.test(m.content)) {
|
|
17983
|
+
prodWrites.push({ turn: i + 1, snippet: m.content.slice(0, 120) });
|
|
17984
|
+
}
|
|
17985
|
+
}
|
|
17986
|
+
}
|
|
17987
|
+
const firstTs = msgs[0]?.created_at;
|
|
17988
|
+
const lastTs = msgs[msgs.length - 1]?.created_at;
|
|
17989
|
+
const duration_minutes = firstTs && lastTs ? Math.round((new Date(lastTs).getTime() - new Date(firstTs).getTime()) / 6e4) : 0;
|
|
17990
|
+
return {
|
|
17991
|
+
session: {
|
|
17992
|
+
id: session.id,
|
|
17993
|
+
title: session.title,
|
|
17994
|
+
ai_tool: session.ai_tool,
|
|
17995
|
+
started: session.created_at,
|
|
17996
|
+
last_active: session.updated_at
|
|
17997
|
+
},
|
|
17998
|
+
stats: { total_messages: msgs.length, user_turns: userTurns, ai_turns: aiTurns, duration_minutes },
|
|
17999
|
+
secrets: secs.map((s) => ({ type: s.type, masked_value: s.masked_value, detected_at: s.detected_at })),
|
|
18000
|
+
mutations: mutationsList,
|
|
18001
|
+
prod_writes: prodWrites,
|
|
18002
|
+
timeline
|
|
18003
|
+
};
|
|
18004
|
+
}
|
|
18005
|
+
function printSummary(data) {
|
|
18006
|
+
const { session, stats, secrets, mutations, prod_writes, timeline } = data;
|
|
18007
|
+
process.stdout.write(`
|
|
18008
|
+
${BOLD6}Session Summary${RESET6}
|
|
18009
|
+
`);
|
|
18010
|
+
process.stdout.write(`${DIM5}${session.title}${RESET6}
|
|
18011
|
+
`);
|
|
18012
|
+
process.stdout.write(`${DIM5}ID: ${session.id.slice(0, 8)} | Tool: ${session.ai_tool ?? "unknown"} | Started: ${formatTs(session.started)}${RESET6}
|
|
18013
|
+
|
|
18014
|
+
`);
|
|
18015
|
+
process.stdout.write(`${BOLD6}Stats${RESET6}
|
|
18016
|
+
`);
|
|
18017
|
+
process.stdout.write(` Messages ${stats.total_messages} (you: ${stats.user_turns}, ai: ${stats.ai_turns})
|
|
18018
|
+
`);
|
|
18019
|
+
process.stdout.write(` Duration ${stats.duration_minutes}m
|
|
18020
|
+
|
|
18021
|
+
`);
|
|
18022
|
+
process.stdout.write(`${BOLD6}Secrets touched${RESET6} ${secrets.length === 0 ? `${GREEN2}none${RESET6}` : `${RED2}${secrets.length} detection(s)${RESET6}`}
|
|
18023
|
+
`);
|
|
18024
|
+
for (const s of secrets) {
|
|
18025
|
+
process.stdout.write(` ${RED2}[${s.type}]${RESET6} ${DIM5}${s.masked_value}${RESET6}
|
|
18026
|
+
`);
|
|
18027
|
+
}
|
|
18028
|
+
if (secrets.length)
|
|
18029
|
+
process.stdout.write("\n");
|
|
18030
|
+
process.stdout.write(`${BOLD6}Mutations detected${RESET6} ${mutations.length === 0 ? `${DIM5}none${RESET6}` : `${mutations.length}`}
|
|
18031
|
+
`);
|
|
18032
|
+
for (const m of mutations) {
|
|
18033
|
+
process.stdout.write(` ${CYAN5}[turn ${m.turn}]${RESET6} ${m.snippet}
|
|
18034
|
+
`);
|
|
18035
|
+
}
|
|
18036
|
+
if (mutations.length)
|
|
18037
|
+
process.stdout.write("\n");
|
|
18038
|
+
if (prod_writes.length > 0) {
|
|
18039
|
+
process.stdout.write(`${BOLD6}${YELLOW4}\u26A0 Possible prod references${RESET6} ${prod_writes.length}
|
|
18040
|
+
`);
|
|
18041
|
+
for (const p of prod_writes) {
|
|
18042
|
+
process.stdout.write(` ${YELLOW4}[turn ${p.turn}]${RESET6} ${DIM5}${p.snippet}\u2026${RESET6}
|
|
18043
|
+
`);
|
|
18044
|
+
}
|
|
18045
|
+
process.stdout.write("\n");
|
|
18046
|
+
}
|
|
18047
|
+
process.stdout.write(`${BOLD6}Timeline${RESET6}
|
|
18048
|
+
`);
|
|
18049
|
+
for (const t of timeline) {
|
|
18050
|
+
const role = t.role === "user" ? `${BOLD6}you${RESET6}` : `${CYAN5}ai ${RESET6}`;
|
|
18051
|
+
const lat = t.latency_ms !== null ? ` ${DIM5}+${formatLatency(t.latency_ms)}${RESET6}` : "";
|
|
18052
|
+
process.stdout.write(` ${DIM5}[${t.turn.toString().padStart(3)}]${RESET6} ${role}${lat} ${DIM5}${t.preview}${RESET6}
|
|
18053
|
+
`);
|
|
18054
|
+
}
|
|
18055
|
+
process.stdout.write("\n");
|
|
18056
|
+
}
|
|
18057
|
+
async function runSummary(args2) {
|
|
18058
|
+
const [prefix, ...flags] = args2;
|
|
18059
|
+
const jsonMode = flags.includes("--json");
|
|
18060
|
+
if (!prefix) {
|
|
18061
|
+
process.stdout.write("Usage: chron summary <session-id-prefix> [--json]\n");
|
|
18062
|
+
process.exit(1);
|
|
18063
|
+
}
|
|
18064
|
+
const data = await buildSummary(prefix);
|
|
18065
|
+
if (!data) {
|
|
18066
|
+
process.stderr.write(`No session found with prefix: ${prefix}
|
|
18067
|
+
`);
|
|
18068
|
+
process.exit(1);
|
|
18069
|
+
}
|
|
18070
|
+
if (jsonMode) {
|
|
18071
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
18072
|
+
} else {
|
|
18073
|
+
printSummary(data);
|
|
18074
|
+
}
|
|
18075
|
+
}
|
|
18076
|
+
var RESET6, BOLD6, DIM5, CYAN5, GREEN2, YELLOW4, RED2, MUTATION_PATTERNS, PROD_PATTERNS;
|
|
18077
|
+
var init_summary = __esm({
|
|
18078
|
+
"src/cli/summary.ts"() {
|
|
18079
|
+
"use strict";
|
|
18080
|
+
init_drizzle_orm();
|
|
18081
|
+
init_db2();
|
|
18082
|
+
init_schema();
|
|
18083
|
+
RESET6 = "\x1B[0m";
|
|
18084
|
+
BOLD6 = "\x1B[1m";
|
|
18085
|
+
DIM5 = "\x1B[2m";
|
|
18086
|
+
CYAN5 = "\x1B[36m";
|
|
18087
|
+
GREEN2 = "\x1B[32m";
|
|
18088
|
+
YELLOW4 = "\x1B[33m";
|
|
18089
|
+
RED2 = "\x1B[31m";
|
|
18090
|
+
MUTATION_PATTERNS = [
|
|
18091
|
+
{ label: "file write", re: /\b(?:wrote?|created?|saved?|updated?) (?:file |the file )?["']?([^\s"']{1,80})/i },
|
|
18092
|
+
{ label: "git", re: /\bgit (?:commit|push|merge|rebase|reset|checkout)\b/i },
|
|
18093
|
+
{ label: "package", re: /\b(?:npm|pip|yarn|pnpm|cargo|go get) (?:install|add|update|remove)\b/i },
|
|
18094
|
+
{ label: "infra", re: /\b(?:kubectl|terraform|helm|pulumi|ansible|docker) (?:apply|deploy|run|push|create|delete)\b/i },
|
|
18095
|
+
{ label: "db write", re: /\b(?:INSERT|UPDATE|DELETE|DROP|ALTER|CREATE TABLE)\b/i },
|
|
18096
|
+
{ label: "API call", re: /\b(?:POST|PUT|PATCH|DELETE) (?:request|call|to )?\/?(?:https?:\/\/)?[a-z0-9\-._]+\/[^\s]{1,60}/i },
|
|
18097
|
+
{ label: "deploy", re: /\b(?:deploy(?:ed|ing)?|ship(?:ped|ping)?|release(?:d|ing)?)\b/i }
|
|
18098
|
+
];
|
|
18099
|
+
PROD_PATTERNS = /\b(?:production|prod[^a-z]|live\s+server|live\s+env|live\s+database)\b/i;
|
|
18100
|
+
}
|
|
18101
|
+
});
|
|
18102
|
+
|
|
17570
18103
|
// src/cli/index.ts
|
|
17571
18104
|
var [, , command, ...args] = process.argv;
|
|
17572
18105
|
async function main() {
|
|
@@ -17601,6 +18134,11 @@ async function main() {
|
|
|
17601
18134
|
await runConnect2(args);
|
|
17602
18135
|
break;
|
|
17603
18136
|
}
|
|
18137
|
+
case "summary": {
|
|
18138
|
+
const { runSummary: runSummary2 } = await Promise.resolve().then(() => (init_summary(), summary_exports));
|
|
18139
|
+
await runSummary2(args);
|
|
18140
|
+
break;
|
|
18141
|
+
}
|
|
17604
18142
|
default: {
|
|
17605
18143
|
const name = command ? `Unknown command: ${command}
|
|
17606
18144
|
|
|
@@ -17615,13 +18153,16 @@ Commands:
|
|
|
17615
18153
|
secrets List detected secrets across sessions
|
|
17616
18154
|
settings View current configuration
|
|
17617
18155
|
connect Connect to a SIEM integration (crowdstrike, sentinel, splunk)
|
|
18156
|
+
summary Structured summary of a session (timeline, mutations, secrets)
|
|
17618
18157
|
|
|
17619
18158
|
Options (history):
|
|
17620
18159
|
--limit=<n> Max sessions to show (default: 20)
|
|
17621
18160
|
<id-prefix> Show full log for the session with this ID prefix
|
|
17622
18161
|
|
|
17623
18162
|
Options (report):
|
|
17624
|
-
--since=<range>
|
|
18163
|
+
--since=<range> Filter by date: 7d, 30d, or YYYY-MM-DD (default: all time)
|
|
18164
|
+
--format=soc2 Generate SOC 2 HTML evidence package
|
|
18165
|
+
--output=<file> Output file for --format=soc2 (default: soc2-report.html)
|
|
17625
18166
|
|
|
17626
18167
|
Options (export / secrets):
|
|
17627
18168
|
<id-prefix> Scope to a single session
|