chron-mcp 0.1.26 → 0.1.29
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/README.md +2 -0
- package/dist/cli/index.js +264 -21
- package/dist/index.js +157 -4
- package/package.json +1 -1
- package/skills/codex.skill.md +73 -0
package/README.md
CHANGED
|
@@ -40,6 +40,8 @@ Chron stores a local audit trail of:
|
|
|
40
40
|
|
|
41
41
|
Chron's SIEM integrations send only metadata: session starts, role-only message events, and masked secret detections. Message content and raw secret values stay local.
|
|
42
42
|
|
|
43
|
+
Need runtime policy enforcement for AI agents? CLAIIM adds agent identity, approval workflows, and ALLOW/DENY gates on top of Chron proof. Try the public preview: https://claiim.io/preview
|
|
44
|
+
|
|
43
45
|
---
|
|
44
46
|
|
|
45
47
|
## Install
|
package/dist/cli/index.js
CHANGED
|
@@ -16582,6 +16582,7 @@ async function initDb(dbPath3) {
|
|
|
16582
16582
|
}
|
|
16583
16583
|
const client = createClient({ url: path.startsWith(":") ? path : `file:${path}` });
|
|
16584
16584
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
16585
|
+
await client.execute("PRAGMA busy_timeout = 5000");
|
|
16585
16586
|
await client.execute("PRAGMA foreign_keys = ON");
|
|
16586
16587
|
for (const sql2 of CREATE_SQL) {
|
|
16587
16588
|
await client.execute(sql2);
|
|
@@ -16614,6 +16615,14 @@ async function initDb(dbPath3) {
|
|
|
16614
16615
|
if (!sessCols.includes("metadata")) {
|
|
16615
16616
|
await client.execute("ALTER TABLE sessions ADD COLUMN metadata TEXT");
|
|
16616
16617
|
}
|
|
16618
|
+
const ftsCount = await client.execute("SELECT count(*) as n FROM messages_fts");
|
|
16619
|
+
const msgCount = await client.execute("SELECT count(*) as n FROM messages");
|
|
16620
|
+
if (Number(ftsCount.rows[0][0]) < Number(msgCount.rows[0][0])) {
|
|
16621
|
+
await client.execute("DELETE FROM messages_fts");
|
|
16622
|
+
await client.execute(
|
|
16623
|
+
"INSERT INTO messages_fts(rowid, content, session_id) SELECT rowid, content, session_id FROM messages"
|
|
16624
|
+
);
|
|
16625
|
+
}
|
|
16617
16626
|
return drizzle(client, { schema: schema_exports });
|
|
16618
16627
|
}
|
|
16619
16628
|
var import_os, import_fs, import_path, CREATE_SQL;
|
|
@@ -16663,11 +16672,81 @@ var init_db2 = __esm({
|
|
|
16663
16672
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_title ON sessions(title)`,
|
|
16664
16673
|
`CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id)`,
|
|
16665
16674
|
`CREATE INDEX IF NOT EXISTS idx_secrets_detected_session_id ON secrets_detected(session_id)`,
|
|
16666
|
-
`CREATE INDEX IF NOT EXISTS idx_secrets_detected_message_id ON secrets_detected(message_id)
|
|
16675
|
+
`CREATE INDEX IF NOT EXISTS idx_secrets_detected_message_id ON secrets_detected(message_id)`,
|
|
16676
|
+
`CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
|
16677
|
+
content,
|
|
16678
|
+
session_id UNINDEXED,
|
|
16679
|
+
tokenize = 'porter unicode61'
|
|
16680
|
+
)`,
|
|
16681
|
+
`CREATE TRIGGER IF NOT EXISTS messages_fts_ai
|
|
16682
|
+
AFTER INSERT ON messages BEGIN
|
|
16683
|
+
INSERT INTO messages_fts(rowid, content, session_id)
|
|
16684
|
+
VALUES (new.rowid, new.content, new.session_id);
|
|
16685
|
+
END`,
|
|
16686
|
+
`CREATE TRIGGER IF NOT EXISTS messages_fts_ad
|
|
16687
|
+
AFTER DELETE ON messages BEGIN
|
|
16688
|
+
DELETE FROM messages_fts WHERE rowid = old.rowid;
|
|
16689
|
+
END`
|
|
16667
16690
|
];
|
|
16668
16691
|
}
|
|
16669
16692
|
});
|
|
16670
16693
|
|
|
16694
|
+
// src/tools/search.ts
|
|
16695
|
+
async function runSearch(db, query, limit = 10) {
|
|
16696
|
+
const client = db.$client;
|
|
16697
|
+
let rows;
|
|
16698
|
+
try {
|
|
16699
|
+
const result = await client.execute({
|
|
16700
|
+
sql: `
|
|
16701
|
+
SELECT
|
|
16702
|
+
messages_fts.session_id AS session_id,
|
|
16703
|
+
snippet(messages_fts, 0, '>>>', '<<<', '\u2026', 24) AS excerpt,
|
|
16704
|
+
s.title AS title,
|
|
16705
|
+
s.ai_tool AS ai_tool,
|
|
16706
|
+
s.updated_at AS updated_at
|
|
16707
|
+
FROM messages_fts
|
|
16708
|
+
JOIN sessions s ON s.id = messages_fts.session_id
|
|
16709
|
+
WHERE messages_fts MATCH ?
|
|
16710
|
+
ORDER BY rank
|
|
16711
|
+
LIMIT ?
|
|
16712
|
+
`,
|
|
16713
|
+
args: [query, limit * 5]
|
|
16714
|
+
// over-fetch so we can group into limit distinct sessions
|
|
16715
|
+
});
|
|
16716
|
+
rows = result.rows;
|
|
16717
|
+
} catch {
|
|
16718
|
+
return [];
|
|
16719
|
+
}
|
|
16720
|
+
const map = /* @__PURE__ */ new Map();
|
|
16721
|
+
for (const r of rows) {
|
|
16722
|
+
const sid = String(r.session_id ?? "");
|
|
16723
|
+
if (!sid)
|
|
16724
|
+
continue;
|
|
16725
|
+
if (!map.has(sid)) {
|
|
16726
|
+
map.set(sid, {
|
|
16727
|
+
session_id: sid,
|
|
16728
|
+
title: String(r.title ?? ""),
|
|
16729
|
+
ai_tool: r.ai_tool != null ? String(r.ai_tool) : null,
|
|
16730
|
+
updated_at: String(r.updated_at ?? ""),
|
|
16731
|
+
match_count: 0,
|
|
16732
|
+
excerpts: []
|
|
16733
|
+
});
|
|
16734
|
+
}
|
|
16735
|
+
const entry = map.get(sid);
|
|
16736
|
+
entry.match_count++;
|
|
16737
|
+
if (entry.excerpts.length < 3)
|
|
16738
|
+
entry.excerpts.push(String(r.excerpt ?? ""));
|
|
16739
|
+
if (map.size >= limit && entry.match_count > 1)
|
|
16740
|
+
break;
|
|
16741
|
+
}
|
|
16742
|
+
return Array.from(map.values()).slice(0, limit);
|
|
16743
|
+
}
|
|
16744
|
+
var init_search = __esm({
|
|
16745
|
+
"src/tools/search.ts"() {
|
|
16746
|
+
"use strict";
|
|
16747
|
+
}
|
|
16748
|
+
});
|
|
16749
|
+
|
|
16671
16750
|
// src/cli/history.ts
|
|
16672
16751
|
var history_exports = {};
|
|
16673
16752
|
__export(history_exports, {
|
|
@@ -16787,12 +16866,43 @@ ${msg.content}
|
|
|
16787
16866
|
}
|
|
16788
16867
|
}
|
|
16789
16868
|
}
|
|
16869
|
+
async function printSearch(query, limit) {
|
|
16870
|
+
const db = await initDb();
|
|
16871
|
+
const results = await runSearch(db, query, limit);
|
|
16872
|
+
if (results.length === 0) {
|
|
16873
|
+
process.stdout.write(`No sessions match: ${query}
|
|
16874
|
+
`);
|
|
16875
|
+
return;
|
|
16876
|
+
}
|
|
16877
|
+
process.stdout.write(`
|
|
16878
|
+
${BOLD}Search: ${query}${RESET} ${DIM}(${results.length} session${results.length === 1 ? "" : "s"})${RESET}
|
|
16879
|
+
|
|
16880
|
+
`);
|
|
16881
|
+
for (const r of results) {
|
|
16882
|
+
const date = fmtDate(r.updated_at);
|
|
16883
|
+
const prefix = r.session_id.slice(0, 8);
|
|
16884
|
+
const hits = `${r.match_count} hit${r.match_count === 1 ? "" : "s"}`;
|
|
16885
|
+
process.stdout.write(
|
|
16886
|
+
` ${DIM}${date}${RESET} ${CYAN}${prefix}${RESET} ${BOLD}${hits}${RESET} ${r.title}
|
|
16887
|
+
`
|
|
16888
|
+
);
|
|
16889
|
+
for (const excerpt of r.excerpts) {
|
|
16890
|
+
const highlighted = excerpt.replace(/>>>/g, YELLOW).replace(/<<</g, RESET);
|
|
16891
|
+
process.stdout.write(` ${DIM}\u2026${RESET} ${highlighted}
|
|
16892
|
+
`);
|
|
16893
|
+
}
|
|
16894
|
+
process.stdout.write("\n");
|
|
16895
|
+
}
|
|
16896
|
+
}
|
|
16790
16897
|
async function runHistory(args2) {
|
|
16791
16898
|
const limitArg = args2.find((a) => a.startsWith("--limit="));
|
|
16792
16899
|
const limit = limitArg ? parseInt(limitArg.split("=")[1], 10) : 20;
|
|
16793
16900
|
const refArg = args2.find((a) => a.startsWith("--ref="))?.split("=").slice(1).join("=");
|
|
16901
|
+
const searchArg = args2.find((a) => a.startsWith("--search="))?.split("=").slice(1).join("=");
|
|
16794
16902
|
const positional = args2.filter((a) => !a.startsWith("--"));
|
|
16795
|
-
if (
|
|
16903
|
+
if (searchArg) {
|
|
16904
|
+
await printSearch(searchArg, limit);
|
|
16905
|
+
} else if (positional.length === 0) {
|
|
16796
16906
|
await printList(limit, refArg);
|
|
16797
16907
|
} else {
|
|
16798
16908
|
await printDetail(positional[0]);
|
|
@@ -16805,6 +16915,7 @@ var init_history = __esm({
|
|
|
16805
16915
|
init_drizzle_orm();
|
|
16806
16916
|
init_db2();
|
|
16807
16917
|
init_schema();
|
|
16918
|
+
init_search();
|
|
16808
16919
|
RESET = "\x1B[0m";
|
|
16809
16920
|
BOLD = "\x1B[1m";
|
|
16810
16921
|
DIM = "\x1B[2m";
|
|
@@ -17032,12 +17143,16 @@ ${overview.total_detections > 0 ? `<p class="warn">⚠ ${overview.total_dete
|
|
|
17032
17143
|
</tbody>
|
|
17033
17144
|
</table>
|
|
17034
17145
|
${brokenList}
|
|
17035
|
-
<p style="font-size:11px;color:#6b7280">
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
</p>
|
|
17146
|
+
<p style="font-size:11px;color:#6b7280">
|
|
17147
|
+
Note: This report verifies <em>chain linkage</em> (prev_hash continuity). Full content-hash recomputation
|
|
17148
|
+
(which detects in-place tampering) can be performed with <code>chron verify <session-id></code>.
|
|
17149
|
+
</p>
|
|
17150
|
+
<p class="governance-note">
|
|
17151
|
+
For formal AI governance, CLAIIM extends Chron evidence with agent identity, policy gates,
|
|
17152
|
+
approvals, and runtime ALLOW/DENY enforcement. See <a href="https://claiim.io/preview">https://claiim.io/preview</a>.
|
|
17153
|
+
</p>
|
|
17039
17154
|
|
|
17040
|
-
<!-- \u2466 SESSION INVENTORY -->
|
|
17155
|
+
<!-- \u2466 SESSION INVENTORY -->
|
|
17041
17156
|
<h2 class="section-break">Session Inventory</h2>
|
|
17042
17157
|
<p>All sessions recorded in the audit database (most recent first, capped at 500 rows).</p>
|
|
17043
17158
|
<table>
|
|
@@ -17161,6 +17276,7 @@ tr:nth-child(even) td { background: #fafafa; }
|
|
|
17161
17276
|
.section-break { page-break-before: always; }
|
|
17162
17277
|
.appendix { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 16px 20px; margin-top: 12px; }
|
|
17163
17278
|
.appendix p, .appendix li { color: #374151; font-size: 12px; }
|
|
17279
|
+
.governance-note { background: #f8fafc; border: 1px solid #dbeafe; border-left: 4px solid #2563eb; border-radius: 4px; padding: 12px 14px; margin: 14px 0 20px; font-size: 12px; color: #374151; }
|
|
17164
17280
|
ul { padding-left: 20px; margin-bottom: 8px; }
|
|
17165
17281
|
li { margin-bottom: 4px; }
|
|
17166
17282
|
@media print {
|
|
@@ -17370,7 +17486,7 @@ var require_package = __commonJS({
|
|
|
17370
17486
|
"package.json"(exports2, module2) {
|
|
17371
17487
|
module2.exports = {
|
|
17372
17488
|
name: "chron-mcp",
|
|
17373
|
-
version: "0.1.
|
|
17489
|
+
version: "0.1.29",
|
|
17374
17490
|
mcpName: "io.github.sirinivask/chron",
|
|
17375
17491
|
description: "Audit-grade timestamped logs for every AI conversation",
|
|
17376
17492
|
repository: {
|
|
@@ -18177,6 +18293,93 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
|
|
|
18177
18293
|
`);
|
|
18178
18294
|
process.stdout.write(`${DIM4}Config saved to ~/.chron/config.json \u2014 events will flow immediately in all running sessions.${RESET5}
|
|
18179
18295
|
|
|
18296
|
+
`);
|
|
18297
|
+
}
|
|
18298
|
+
function codexGlobalConfigPath() {
|
|
18299
|
+
return (0, import_path5.join)((0, import_os6.homedir)(), ".codex", "config.toml");
|
|
18300
|
+
}
|
|
18301
|
+
function isChronInCodexConfig(content) {
|
|
18302
|
+
return content.includes("[mcp_servers.chron]");
|
|
18303
|
+
}
|
|
18304
|
+
function appendChronToToml(content) {
|
|
18305
|
+
return content.trimEnd() + "\n" + CODEX_MCP_BLOCK;
|
|
18306
|
+
}
|
|
18307
|
+
async function connectCodex() {
|
|
18308
|
+
process.stdout.write(`
|
|
18309
|
+
${BOLD5}Connect Chron \u2192 Codex${RESET5}
|
|
18310
|
+
|
|
18311
|
+
`);
|
|
18312
|
+
const globalPath = codexGlobalConfigPath();
|
|
18313
|
+
const projectPath = (0, import_path5.join)(process.cwd(), ".codex", "config.toml");
|
|
18314
|
+
const hasGlobal = (0, import_fs5.existsSync)(globalPath);
|
|
18315
|
+
const hasProject = (0, import_fs5.existsSync)(projectPath);
|
|
18316
|
+
let targetPath;
|
|
18317
|
+
let isProject = false;
|
|
18318
|
+
if (hasGlobal && hasProject) {
|
|
18319
|
+
process.stdout.write(`${DIM4}Found both global and project Codex configs.${RESET5}
|
|
18320
|
+
`);
|
|
18321
|
+
process.stdout.write(`${DIM4} Global: ${globalPath}${RESET5}
|
|
18322
|
+
`);
|
|
18323
|
+
process.stdout.write(`${DIM4} Project: ${projectPath}${RESET5}
|
|
18324
|
+
|
|
18325
|
+
`);
|
|
18326
|
+
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
18327
|
+
const choice = await new Promise(
|
|
18328
|
+
(resolve) => rl.question(` Add Chron to (g)lobal or (p)roject config? [g/p]: `, resolve)
|
|
18329
|
+
);
|
|
18330
|
+
rl.close();
|
|
18331
|
+
isProject = choice.trim().toLowerCase() === "p";
|
|
18332
|
+
targetPath = isProject ? projectPath : globalPath;
|
|
18333
|
+
} else if (hasProject) {
|
|
18334
|
+
targetPath = projectPath;
|
|
18335
|
+
isProject = true;
|
|
18336
|
+
} else if (hasGlobal) {
|
|
18337
|
+
targetPath = globalPath;
|
|
18338
|
+
} else {
|
|
18339
|
+
process.stdout.write(`${YELLOW3}!${RESET5} Codex config not found. Creating ${globalPath}
|
|
18340
|
+
|
|
18341
|
+
`);
|
|
18342
|
+
(0, import_fs5.mkdirSync)((0, import_path5.join)((0, import_os6.homedir)(), ".codex"), { recursive: true });
|
|
18343
|
+
(0, import_fs5.writeFileSync)(globalPath, CODEX_MCP_BLOCK.trimStart());
|
|
18344
|
+
process.stdout.write(`${GREEN2}\u2713${RESET5} Created ${globalPath} with Chron MCP config.
|
|
18345
|
+
`);
|
|
18346
|
+
_printCodexNextSteps();
|
|
18347
|
+
return;
|
|
18348
|
+
}
|
|
18349
|
+
const existing = (0, import_fs5.readFileSync)(targetPath, "utf8");
|
|
18350
|
+
if (isChronInCodexConfig(existing)) {
|
|
18351
|
+
process.stdout.write(`${GREEN2}\u2713${RESET5} Chron MCP is already configured in ${targetPath}
|
|
18352
|
+
|
|
18353
|
+
`);
|
|
18354
|
+
_printCodexNextSteps();
|
|
18355
|
+
return;
|
|
18356
|
+
}
|
|
18357
|
+
const updated = appendChronToToml(existing);
|
|
18358
|
+
(0, import_fs5.writeFileSync)(targetPath, updated, "utf8");
|
|
18359
|
+
process.stdout.write(`${GREEN2}\u2713${RESET5} Added Chron MCP to ${targetPath}
|
|
18360
|
+
|
|
18361
|
+
`);
|
|
18362
|
+
_printCodexNextSteps();
|
|
18363
|
+
}
|
|
18364
|
+
function _printCodexNextSteps() {
|
|
18365
|
+
process.stdout.write(`${BOLD5}Next steps:${RESET5}
|
|
18366
|
+
|
|
18367
|
+
`);
|
|
18368
|
+
process.stdout.write(` 1. Restart Codex to pick up the new MCP server.
|
|
18369
|
+
`);
|
|
18370
|
+
process.stdout.write(` 2. Ask Codex to start a Chron session:
|
|
18371
|
+
`);
|
|
18372
|
+
process.stdout.write(` ${DIM4}"Use init_session to start a Chron audit session for this work."${RESET5}
|
|
18373
|
+
`);
|
|
18374
|
+
process.stdout.write(` 3. Verify setup:
|
|
18375
|
+
`);
|
|
18376
|
+
process.stdout.write(` ${CYAN4}chron doctor${RESET5}
|
|
18377
|
+
|
|
18378
|
+
`);
|
|
18379
|
+
process.stdout.write(`${DIM4}Codex will automatically call log_tool_call, log_tool_result, and
|
|
18380
|
+
`);
|
|
18381
|
+
process.stdout.write(`log_code_change during its work once the session is started.${RESET5}
|
|
18382
|
+
|
|
18180
18383
|
`);
|
|
18181
18384
|
}
|
|
18182
18385
|
async function runConnect(args2) {
|
|
@@ -18191,6 +18394,9 @@ async function runConnect(args2) {
|
|
|
18191
18394
|
case "splunk":
|
|
18192
18395
|
await connectSplunk();
|
|
18193
18396
|
break;
|
|
18397
|
+
case "codex":
|
|
18398
|
+
await connectCodex();
|
|
18399
|
+
break;
|
|
18194
18400
|
default: {
|
|
18195
18401
|
const name = subcommand ? `Unknown integration: ${subcommand}
|
|
18196
18402
|
|
|
@@ -18199,6 +18405,7 @@ async function runConnect(args2) {
|
|
|
18199
18405
|
`${name}Usage: chron connect <integration>
|
|
18200
18406
|
|
|
18201
18407
|
Integrations:
|
|
18408
|
+
codex Add Chron MCP to Codex config (global or project)
|
|
18202
18409
|
crowdstrike Connect to CrowdStrike LogScale (direct ingest)
|
|
18203
18410
|
sentinel Connect to Microsoft Sentinel (Azure Monitor Logs Ingestion API)
|
|
18204
18411
|
splunk Connect to Splunk via HTTP Event Collector (HEC)
|
|
@@ -18208,7 +18415,7 @@ Integrations:
|
|
|
18208
18415
|
}
|
|
18209
18416
|
}
|
|
18210
18417
|
}
|
|
18211
|
-
var import_readline, import_os6, import_path5, import_fs5, RESET5, BOLD5, DIM4, CYAN4, GREEN2, RED, YELLOW3;
|
|
18418
|
+
var import_readline, import_os6, import_path5, import_fs5, RESET5, BOLD5, DIM4, CYAN4, GREEN2, RED, YELLOW3, CODEX_MCP_BLOCK;
|
|
18212
18419
|
var init_connect = __esm({
|
|
18213
18420
|
"src/cli/connect.ts"() {
|
|
18214
18421
|
"use strict";
|
|
@@ -18223,6 +18430,11 @@ var init_connect = __esm({
|
|
|
18223
18430
|
GREEN2 = "\x1B[32m";
|
|
18224
18431
|
RED = "\x1B[31m";
|
|
18225
18432
|
YELLOW3 = "\x1B[33m";
|
|
18433
|
+
CODEX_MCP_BLOCK = `
|
|
18434
|
+
[mcp_servers.chron]
|
|
18435
|
+
command = "npx"
|
|
18436
|
+
args = ["-y", "chron-mcp"]
|
|
18437
|
+
`;
|
|
18226
18438
|
}
|
|
18227
18439
|
});
|
|
18228
18440
|
|
|
@@ -18545,7 +18757,15 @@ async function runSign(args2) {
|
|
|
18545
18757
|
process.exit(1);
|
|
18546
18758
|
}
|
|
18547
18759
|
const signature = signSession(session.id, finalContentHash, messageCount, firstCreatedAt);
|
|
18548
|
-
|
|
18760
|
+
try {
|
|
18761
|
+
await db.update(sessions).set({ signature }).where(eq(sessions.id, session.id));
|
|
18762
|
+
} catch (err) {
|
|
18763
|
+
const cause = err?.cause ?? err;
|
|
18764
|
+
process.stderr.write(`Failed to save signature: ${cause?.message ?? err.message}
|
|
18765
|
+
`);
|
|
18766
|
+
process.stderr.write("Tip: if the Chron MCP server is running, it may hold a write lock. Retry or stop the MCP server first.\n");
|
|
18767
|
+
process.exit(1);
|
|
18768
|
+
}
|
|
18549
18769
|
const sigData = {
|
|
18550
18770
|
chron_signature: "v1",
|
|
18551
18771
|
session_id: session.id,
|
|
@@ -18870,7 +19090,7 @@ function loadConfig2() {
|
|
|
18870
19090
|
function mcpConfigs() {
|
|
18871
19091
|
const home = (0, import_os9.homedir)();
|
|
18872
19092
|
const plat = (0, import_os9.platform)();
|
|
18873
|
-
const
|
|
19093
|
+
const jsonCandidates = [
|
|
18874
19094
|
{
|
|
18875
19095
|
name: "Claude Desktop",
|
|
18876
19096
|
path: plat === "win32" ? (0, import_path9.join)(process.env.APPDATA ?? (0, import_path9.join)(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : (0, import_path9.join)(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
|
|
@@ -18879,7 +19099,7 @@ function mcpConfigs() {
|
|
|
18879
19099
|
{ name: "Cursor", path: (0, import_path9.join)(home, ".cursor", "mcp.json") },
|
|
18880
19100
|
{ name: "Windsurf", path: (0, import_path9.join)(home, ".codeium", "windsurf", "mcp_config.json") }
|
|
18881
19101
|
];
|
|
18882
|
-
|
|
19102
|
+
const results = jsonCandidates.map((c) => {
|
|
18883
19103
|
if (!(0, import_fs10.existsSync)(c.path)) {
|
|
18884
19104
|
return { name: c.name, path: c.path, exists: false, chronConfigured: null };
|
|
18885
19105
|
}
|
|
@@ -18894,6 +19114,22 @@ function mcpConfigs() {
|
|
|
18894
19114
|
return { name: c.name, path: c.path, exists: true, chronConfigured: null, note: "Could not parse config" };
|
|
18895
19115
|
}
|
|
18896
19116
|
});
|
|
19117
|
+
const codexGlobal = (0, import_path9.join)(home, ".codex", "config.toml");
|
|
19118
|
+
const codexProject = (0, import_path9.join)(process.cwd(), ".codex", "config.toml");
|
|
19119
|
+
for (const p of [codexGlobal, codexProject]) {
|
|
19120
|
+
if (!(0, import_fs10.existsSync)(p)) {
|
|
19121
|
+
results.push({ name: p === codexGlobal ? "Codex (global)" : "Codex (project)", path: p, exists: false, chronConfigured: null });
|
|
19122
|
+
} else {
|
|
19123
|
+
try {
|
|
19124
|
+
const content = (0, import_fs10.readFileSync)(p, "utf8");
|
|
19125
|
+
const configured = content.includes("[mcp_servers.chron]");
|
|
19126
|
+
results.push({ name: p === codexGlobal ? "Codex (global)" : "Codex (project)", path: p, exists: true, chronConfigured: configured });
|
|
19127
|
+
} catch {
|
|
19128
|
+
results.push({ name: p === codexGlobal ? "Codex (global)" : "Codex (project)", path: p, exists: true, chronConfigured: null, note: "Could not read config" });
|
|
19129
|
+
}
|
|
19130
|
+
}
|
|
19131
|
+
}
|
|
19132
|
+
return results;
|
|
18897
19133
|
}
|
|
18898
19134
|
function npmLatestVersion() {
|
|
18899
19135
|
try {
|
|
@@ -19023,14 +19259,16 @@ ${BOLD8}chron doctor${RESET8} ${DIM7}v${import_package2.version}${RESET8}
|
|
|
19023
19259
|
if (!jsonMode)
|
|
19024
19260
|
ok2(`${t.name}`, "chron configured");
|
|
19025
19261
|
} else {
|
|
19262
|
+
const isCodex = t.name.startsWith("Codex");
|
|
19263
|
+
const fixHint = isCodex ? `chron connect codex` : `Add chron to mcpServers in ${t.path}
|
|
19264
|
+
See: https://github.com/sirinivask/chron#installation`;
|
|
19026
19265
|
results.push({
|
|
19027
19266
|
pass: "warn",
|
|
19028
|
-
label: `${t.name}: chron
|
|
19029
|
-
fix:
|
|
19030
|
-
See: https://github.com/sirinivask/chron#installation`
|
|
19267
|
+
label: `${t.name}: chron not configured`,
|
|
19268
|
+
fix: fixHint
|
|
19031
19269
|
});
|
|
19032
19270
|
if (!jsonMode)
|
|
19033
|
-
warn2(`${t.name}`, `chron not in mcpServers \u2014 add it to ${t.path}`);
|
|
19271
|
+
warn2(`${t.name}`, isCodex ? "chron not in MCP servers \u2014 run: chron connect codex" : `chron not in mcpServers \u2014 add it to ${t.path}`);
|
|
19034
19272
|
}
|
|
19035
19273
|
}
|
|
19036
19274
|
if (!jsonMode)
|
|
@@ -19089,6 +19327,9 @@ ${BOLD8}chron doctor${RESET8} ${DIM7}v${import_package2.version}${RESET8}
|
|
|
19089
19327
|
if (failures.length === 0 && warnings.length === 0) {
|
|
19090
19328
|
process.stdout.write(`${GREEN5}${BOLD8}All checks passed.${RESET8} Chron is correctly set up.
|
|
19091
19329
|
|
|
19330
|
+
`);
|
|
19331
|
+
process.stdout.write(`${DIM7}Team AI governance: CLAIIM adds runtime ALLOW/DENY gates on top of Chron proof \u2014 ${CLAIIM_PREVIEW_URL}${RESET8}
|
|
19332
|
+
|
|
19092
19333
|
`);
|
|
19093
19334
|
} else {
|
|
19094
19335
|
if (failures.length > 0) {
|
|
@@ -19137,7 +19378,7 @@ ${BOLD8}chron doctor${RESET8} ${DIM7}v${import_package2.version}${RESET8}
|
|
|
19137
19378
|
}
|
|
19138
19379
|
process.exit(failures.length > 0 ? 1 : 0);
|
|
19139
19380
|
}
|
|
19140
|
-
var import_os9, import_path9, import_fs10, import_child_process3, import_package2, RESET8, BOLD8, DIM7, GREEN5, RED4, YELLOW6, CYAN6;
|
|
19381
|
+
var import_os9, import_path9, import_fs10, import_child_process3, import_package2, RESET8, BOLD8, DIM7, GREEN5, RED4, YELLOW6, CYAN6, CLAIIM_PREVIEW_URL;
|
|
19141
19382
|
var init_doctor = __esm({
|
|
19142
19383
|
"src/cli/doctor.ts"() {
|
|
19143
19384
|
"use strict";
|
|
@@ -19153,6 +19394,7 @@ var init_doctor = __esm({
|
|
|
19153
19394
|
RED4 = "\x1B[31m";
|
|
19154
19395
|
YELLOW6 = "\x1B[33m";
|
|
19155
19396
|
CYAN6 = "\x1B[36m";
|
|
19397
|
+
CLAIIM_PREVIEW_URL = "https://claiim.io/preview";
|
|
19156
19398
|
}
|
|
19157
19399
|
});
|
|
19158
19400
|
|
|
@@ -19845,7 +20087,7 @@ Commands:
|
|
|
19845
20087
|
export Export a session as markdown
|
|
19846
20088
|
secrets List detected secrets across sessions
|
|
19847
20089
|
settings View current configuration
|
|
19848
|
-
connect Connect to a SIEM
|
|
20090
|
+
connect Connect to a SIEM or AI tool (codex, crowdstrike, sentinel, splunk)
|
|
19849
20091
|
summary Structured summary of a session (timeline, mutations, secrets)
|
|
19850
20092
|
sign Sign a session with its Ed25519 key \u2014 produces a .chron.sig file
|
|
19851
20093
|
verify Verify a session's hash chain and Ed25519 signature
|
|
@@ -19854,9 +20096,10 @@ Commands:
|
|
|
19854
20096
|
import Import conversations from external AI tools
|
|
19855
20097
|
|
|
19856
20098
|
Options (history):
|
|
19857
|
-
--limit=<n>
|
|
19858
|
-
--
|
|
19859
|
-
|
|
20099
|
+
--limit=<n> Max sessions to show (default: 20)
|
|
20100
|
+
--search=<query> Full-text search across all sessions (FTS5: phrases, boolean, prefix*)
|
|
20101
|
+
--ref=<value> Filter by external_ref prefix
|
|
20102
|
+
<id-prefix> Show full log for the session with this ID prefix
|
|
19860
20103
|
|
|
19861
20104
|
Options (report):
|
|
19862
20105
|
--since=<range> Filter by date: 7d, 30d, or YYYY-MM-DD (default: all time)
|
package/dist/index.js
CHANGED
|
@@ -22792,6 +22792,7 @@ async function initDb(dbPath) {
|
|
|
22792
22792
|
}
|
|
22793
22793
|
const client = createClient({ url: path.startsWith(":") ? path : `file:${path}` });
|
|
22794
22794
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
22795
|
+
await client.execute("PRAGMA busy_timeout = 5000");
|
|
22795
22796
|
await client.execute("PRAGMA foreign_keys = ON");
|
|
22796
22797
|
for (const sql2 of CREATE_SQL) {
|
|
22797
22798
|
await client.execute(sql2);
|
|
@@ -22824,6 +22825,14 @@ async function initDb(dbPath) {
|
|
|
22824
22825
|
if (!sessCols.includes("metadata")) {
|
|
22825
22826
|
await client.execute("ALTER TABLE sessions ADD COLUMN metadata TEXT");
|
|
22826
22827
|
}
|
|
22828
|
+
const ftsCount = await client.execute("SELECT count(*) as n FROM messages_fts");
|
|
22829
|
+
const msgCount = await client.execute("SELECT count(*) as n FROM messages");
|
|
22830
|
+
if (Number(ftsCount.rows[0][0]) < Number(msgCount.rows[0][0])) {
|
|
22831
|
+
await client.execute("DELETE FROM messages_fts");
|
|
22832
|
+
await client.execute(
|
|
22833
|
+
"INSERT INTO messages_fts(rowid, content, session_id) SELECT rowid, content, session_id FROM messages"
|
|
22834
|
+
);
|
|
22835
|
+
}
|
|
22827
22836
|
return drizzle(client, { schema: schema_exports });
|
|
22828
22837
|
}
|
|
22829
22838
|
var import_os, import_fs, import_path, CREATE_SQL;
|
|
@@ -22873,7 +22882,21 @@ var init_db2 = __esm({
|
|
|
22873
22882
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_title ON sessions(title)`,
|
|
22874
22883
|
`CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id)`,
|
|
22875
22884
|
`CREATE INDEX IF NOT EXISTS idx_secrets_detected_session_id ON secrets_detected(session_id)`,
|
|
22876
|
-
`CREATE INDEX IF NOT EXISTS idx_secrets_detected_message_id ON secrets_detected(message_id)
|
|
22885
|
+
`CREATE INDEX IF NOT EXISTS idx_secrets_detected_message_id ON secrets_detected(message_id)`,
|
|
22886
|
+
`CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
|
22887
|
+
content,
|
|
22888
|
+
session_id UNINDEXED,
|
|
22889
|
+
tokenize = 'porter unicode61'
|
|
22890
|
+
)`,
|
|
22891
|
+
`CREATE TRIGGER IF NOT EXISTS messages_fts_ai
|
|
22892
|
+
AFTER INSERT ON messages BEGIN
|
|
22893
|
+
INSERT INTO messages_fts(rowid, content, session_id)
|
|
22894
|
+
VALUES (new.rowid, new.content, new.session_id);
|
|
22895
|
+
END`,
|
|
22896
|
+
`CREATE TRIGGER IF NOT EXISTS messages_fts_ad
|
|
22897
|
+
AFTER DELETE ON messages BEGIN
|
|
22898
|
+
DELETE FROM messages_fts WHERE rowid = old.rowid;
|
|
22899
|
+
END`
|
|
22877
22900
|
];
|
|
22878
22901
|
}
|
|
22879
22902
|
});
|
|
@@ -38479,7 +38502,7 @@ var init_time = __esm({
|
|
|
38479
38502
|
var version4;
|
|
38480
38503
|
var init_package = __esm({
|
|
38481
38504
|
"package.json"() {
|
|
38482
|
-
version4 = "0.1.
|
|
38505
|
+
version4 = "0.1.29";
|
|
38483
38506
|
}
|
|
38484
38507
|
});
|
|
38485
38508
|
|
|
@@ -38837,6 +38860,44 @@ var init_ntp = __esm({
|
|
|
38837
38860
|
}
|
|
38838
38861
|
});
|
|
38839
38862
|
|
|
38863
|
+
// src/utils/terminal.ts
|
|
38864
|
+
function resolvedLevel() {
|
|
38865
|
+
const raw = (process.env.CHRON_LOG_LEVEL ?? "").toLowerCase();
|
|
38866
|
+
if (raw === "off" || raw === "false" || raw === "0")
|
|
38867
|
+
return "off";
|
|
38868
|
+
if (raw === "summary")
|
|
38869
|
+
return "summary";
|
|
38870
|
+
const legacy = process.env.CHRON_TERMINAL_AUDIT;
|
|
38871
|
+
if (legacy && DISABLE_VALUES.has(legacy.toLowerCase()))
|
|
38872
|
+
return "off";
|
|
38873
|
+
return "verbose";
|
|
38874
|
+
}
|
|
38875
|
+
function terminalAudit(message, level = "message") {
|
|
38876
|
+
const mode = resolvedLevel();
|
|
38877
|
+
if (mode === "off")
|
|
38878
|
+
return;
|
|
38879
|
+
if (mode === "summary" && level === "message")
|
|
38880
|
+
return;
|
|
38881
|
+
process.stderr.write(`[chron] ${message}
|
|
38882
|
+
`);
|
|
38883
|
+
}
|
|
38884
|
+
function formatDuration(ms) {
|
|
38885
|
+
if (ms < 1e3)
|
|
38886
|
+
return `${ms}ms`;
|
|
38887
|
+
if (ms < 6e4)
|
|
38888
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
38889
|
+
const minutes = Math.floor(ms / 6e4);
|
|
38890
|
+
const seconds = Math.round(ms % 6e4 / 1e3);
|
|
38891
|
+
return `${minutes}m ${seconds}s`;
|
|
38892
|
+
}
|
|
38893
|
+
var DISABLE_VALUES;
|
|
38894
|
+
var init_terminal = __esm({
|
|
38895
|
+
"src/utils/terminal.ts"() {
|
|
38896
|
+
"use strict";
|
|
38897
|
+
DISABLE_VALUES = /* @__PURE__ */ new Set(["0", "false", "off", "no"]);
|
|
38898
|
+
}
|
|
38899
|
+
});
|
|
38900
|
+
|
|
38840
38901
|
// src/tools/sessions.ts
|
|
38841
38902
|
function attachNtpMetadata(db, sessionId) {
|
|
38842
38903
|
setImmediate(() => {
|
|
@@ -38867,6 +38928,7 @@ function startSession(db) {
|
|
|
38867
38928
|
});
|
|
38868
38929
|
attachNtpMetadata(db, id);
|
|
38869
38930
|
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: id.slice(0, 8), ai_tool: args.ai_tool ?? null } });
|
|
38931
|
+
terminalAudit(`session ${id.slice(0, 8)} started at ${now} (${args.ai_tool ?? "unknown"}: ${args.title})`, "session");
|
|
38870
38932
|
return {
|
|
38871
38933
|
content: [{
|
|
38872
38934
|
type: "text",
|
|
@@ -38882,6 +38944,7 @@ function startSession(db) {
|
|
|
38882
38944
|
const [countRow] = await db.select({ count: sql`count(*)` }).from(messages).where(eq(messages.session_id, session.id));
|
|
38883
38945
|
await db.update(sessions).set({ updated_at: now }).where(eq(sessions.id, session.id));
|
|
38884
38946
|
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: session.id.slice(0, 8), ai_tool: session.ai_tool } });
|
|
38947
|
+
terminalAudit(`session ${session.id.slice(0, 8)} resumed at ${now} (${session.ai_tool ?? "unknown"}: ${session.title})`, "session");
|
|
38885
38948
|
return {
|
|
38886
38949
|
content: [{
|
|
38887
38950
|
type: "text",
|
|
@@ -38918,6 +38981,7 @@ function initSession(db) {
|
|
|
38918
38981
|
created = true;
|
|
38919
38982
|
ai_tool = args.ai_tool ?? null;
|
|
38920
38983
|
attachNtpMetadata(db, id);
|
|
38984
|
+
terminalAudit(`session ${id.slice(0, 8)} started at ${now} (${ai_tool ?? "unknown"}: ${args.title})`, "session");
|
|
38921
38985
|
} catch (e) {
|
|
38922
38986
|
const isUnique = e?.message?.includes("UNIQUE constraint failed") || e?.code === "SQLITE_CONSTRAINT_UNIQUE" || e?.cause?.message?.includes("UNIQUE constraint failed") || e?.cause?.extendedCode === "SQLITE_CONSTRAINT_UNIQUE";
|
|
38923
38987
|
if (!isUnique)
|
|
@@ -38928,6 +38992,7 @@ function initSession(db) {
|
|
|
38928
38992
|
session_id = session.id;
|
|
38929
38993
|
created = false;
|
|
38930
38994
|
ai_tool = session.ai_tool;
|
|
38995
|
+
terminalAudit(`session ${session.id.slice(0, 8)} resumed at ${now} (${ai_tool ?? "unknown"}: ${session.title})`, "session");
|
|
38931
38996
|
}
|
|
38932
38997
|
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: session_id.slice(0, 8), ai_tool } });
|
|
38933
38998
|
const limit = args.limit ?? 10;
|
|
@@ -39016,6 +39081,7 @@ var init_sessions = __esm({
|
|
|
39016
39081
|
init_relay();
|
|
39017
39082
|
init_signing();
|
|
39018
39083
|
init_ntp();
|
|
39084
|
+
init_terminal();
|
|
39019
39085
|
}
|
|
39020
39086
|
});
|
|
39021
39087
|
|
|
@@ -39328,7 +39394,7 @@ function isFkError(e) {
|
|
|
39328
39394
|
}
|
|
39329
39395
|
async function insertMessage(db, session_id, role, content, event_type) {
|
|
39330
39396
|
return db.transaction(async (tx) => {
|
|
39331
|
-
const last = await tx.select({ content_hash: messages.content_hash }).from(messages).where(eq(messages.session_id, session_id)).orderBy(desc(messages.created_at), desc(sql`rowid`)).limit(1);
|
|
39397
|
+
const last = await tx.select({ content_hash: messages.content_hash, created_at: messages.created_at }).from(messages).where(eq(messages.session_id, session_id)).orderBy(desc(messages.created_at), desc(sql`rowid`)).limit(1);
|
|
39332
39398
|
const ts = localISOString();
|
|
39333
39399
|
const msgId = v4_default();
|
|
39334
39400
|
const prevHash = last[0]?.content_hash ?? null;
|
|
@@ -39345,7 +39411,13 @@ async function insertMessage(db, session_id, role, content, event_type) {
|
|
|
39345
39411
|
});
|
|
39346
39412
|
await tx.update(sessions).set({ updated_at: ts }).where(eq(sessions.id, session_id));
|
|
39347
39413
|
const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, session_id)).limit(1);
|
|
39348
|
-
return {
|
|
39414
|
+
return {
|
|
39415
|
+
id: msgId,
|
|
39416
|
+
now: ts,
|
|
39417
|
+
contentHash: hash,
|
|
39418
|
+
ai_tool: sessionRow?.ai_tool ?? null,
|
|
39419
|
+
previous_created_at: last[0]?.created_at ?? null
|
|
39420
|
+
};
|
|
39349
39421
|
});
|
|
39350
39422
|
}
|
|
39351
39423
|
function logMessage(db) {
|
|
@@ -39361,6 +39433,9 @@ function logMessage(db) {
|
|
|
39361
39433
|
now = result.now;
|
|
39362
39434
|
contentHash = result.contentHash;
|
|
39363
39435
|
sessionAiTool = result.ai_tool;
|
|
39436
|
+
const elapsed = result.previous_created_at ? ` (+${formatDuration(new Date(now).getTime() - new Date(result.previous_created_at).getTime())})` : "";
|
|
39437
|
+
const marker = args.role === "user" ? "user start" : "assistant end";
|
|
39438
|
+
terminalAudit(`${marker} ${now}${elapsed} session ${args.session_id.slice(0, 8)}`);
|
|
39364
39439
|
} catch (e) {
|
|
39365
39440
|
if (isFkError(e)) {
|
|
39366
39441
|
return { content: [{ type: "text", text: `Session not found: ${args.session_id}` }], isError: true };
|
|
@@ -39508,6 +39583,7 @@ var init_messages = __esm({
|
|
|
39508
39583
|
init_hash();
|
|
39509
39584
|
init_detect();
|
|
39510
39585
|
init_relay();
|
|
39586
|
+
init_terminal();
|
|
39511
39587
|
}
|
|
39512
39588
|
});
|
|
39513
39589
|
|
|
@@ -39831,6 +39907,73 @@ var init_summary2 = __esm({
|
|
|
39831
39907
|
}
|
|
39832
39908
|
});
|
|
39833
39909
|
|
|
39910
|
+
// src/tools/search.ts
|
|
39911
|
+
async function runSearch(db, query, limit = 10) {
|
|
39912
|
+
const client = db.$client;
|
|
39913
|
+
let rows;
|
|
39914
|
+
try {
|
|
39915
|
+
const result = await client.execute({
|
|
39916
|
+
sql: `
|
|
39917
|
+
SELECT
|
|
39918
|
+
messages_fts.session_id AS session_id,
|
|
39919
|
+
snippet(messages_fts, 0, '>>>', '<<<', '\u2026', 24) AS excerpt,
|
|
39920
|
+
s.title AS title,
|
|
39921
|
+
s.ai_tool AS ai_tool,
|
|
39922
|
+
s.updated_at AS updated_at
|
|
39923
|
+
FROM messages_fts
|
|
39924
|
+
JOIN sessions s ON s.id = messages_fts.session_id
|
|
39925
|
+
WHERE messages_fts MATCH ?
|
|
39926
|
+
ORDER BY rank
|
|
39927
|
+
LIMIT ?
|
|
39928
|
+
`,
|
|
39929
|
+
args: [query, limit * 5]
|
|
39930
|
+
// over-fetch so we can group into limit distinct sessions
|
|
39931
|
+
});
|
|
39932
|
+
rows = result.rows;
|
|
39933
|
+
} catch {
|
|
39934
|
+
return [];
|
|
39935
|
+
}
|
|
39936
|
+
const map = /* @__PURE__ */ new Map();
|
|
39937
|
+
for (const r of rows) {
|
|
39938
|
+
const sid = String(r.session_id ?? "");
|
|
39939
|
+
if (!sid)
|
|
39940
|
+
continue;
|
|
39941
|
+
if (!map.has(sid)) {
|
|
39942
|
+
map.set(sid, {
|
|
39943
|
+
session_id: sid,
|
|
39944
|
+
title: String(r.title ?? ""),
|
|
39945
|
+
ai_tool: r.ai_tool != null ? String(r.ai_tool) : null,
|
|
39946
|
+
updated_at: String(r.updated_at ?? ""),
|
|
39947
|
+
match_count: 0,
|
|
39948
|
+
excerpts: []
|
|
39949
|
+
});
|
|
39950
|
+
}
|
|
39951
|
+
const entry = map.get(sid);
|
|
39952
|
+
entry.match_count++;
|
|
39953
|
+
if (entry.excerpts.length < 3)
|
|
39954
|
+
entry.excerpts.push(String(r.excerpt ?? ""));
|
|
39955
|
+
if (map.size >= limit && entry.match_count > 1)
|
|
39956
|
+
break;
|
|
39957
|
+
}
|
|
39958
|
+
return Array.from(map.values()).slice(0, limit);
|
|
39959
|
+
}
|
|
39960
|
+
function searchSessions(db) {
|
|
39961
|
+
return async (args) => {
|
|
39962
|
+
const results = await runSearch(db, args.query, args.limit ?? 10);
|
|
39963
|
+
return {
|
|
39964
|
+
content: [{
|
|
39965
|
+
type: "text",
|
|
39966
|
+
text: JSON.stringify({ query: args.query, total: results.length, results })
|
|
39967
|
+
}]
|
|
39968
|
+
};
|
|
39969
|
+
};
|
|
39970
|
+
}
|
|
39971
|
+
var init_search = __esm({
|
|
39972
|
+
"src/tools/search.ts"() {
|
|
39973
|
+
"use strict";
|
|
39974
|
+
}
|
|
39975
|
+
});
|
|
39976
|
+
|
|
39834
39977
|
// src/server.ts
|
|
39835
39978
|
function createServer(db) {
|
|
39836
39979
|
const server = new McpServer({
|
|
@@ -39954,6 +40097,15 @@ function createServer(db) {
|
|
|
39954
40097
|
},
|
|
39955
40098
|
deleteSession(db)
|
|
39956
40099
|
);
|
|
40100
|
+
server.tool(
|
|
40101
|
+
"search_sessions",
|
|
40102
|
+
'Full-text search across all logged AI conversations. Returns matching sessions with excerpts. Uses SQLite FTS5 with porter stemming \u2014 supports phrases ("exact match"), boolean (term1 OR term2), and prefix (migrat*).',
|
|
40103
|
+
{
|
|
40104
|
+
query: external_exports.string().describe('FTS5 search query, e.g. "database migration" or "auth bug" or "deploy OR release"'),
|
|
40105
|
+
limit: external_exports.number().int().positive().optional().describe("Max sessions to return (default: 10)")
|
|
40106
|
+
},
|
|
40107
|
+
searchSessions(db)
|
|
40108
|
+
);
|
|
39957
40109
|
server.tool(
|
|
39958
40110
|
"summarize_session",
|
|
39959
40111
|
"Return a structured summary of a session: timeline with latencies, mutations detected, secrets touched, and possible prod-account references. Useful for compliance review and change-control evidence.",
|
|
@@ -39984,6 +40136,7 @@ var init_server3 = __esm({
|
|
|
39984
40136
|
init_verify();
|
|
39985
40137
|
init_detect2();
|
|
39986
40138
|
init_summary2();
|
|
40139
|
+
init_search();
|
|
39987
40140
|
init_package();
|
|
39988
40141
|
roleEnum = external_exports.enum(["user", "assistant"]);
|
|
39989
40142
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Chron — Codex Audit Skill
|
|
2
|
+
|
|
3
|
+
Chron creates a tamper-evident audit trail of every Codex session — messages, tool calls, tool results, and code changes — stored in a local SQLite database the user owns. No data leaves the machine.
|
|
4
|
+
|
|
5
|
+
## Session Start
|
|
6
|
+
|
|
7
|
+
At the beginning of every Codex thread, call:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
init_session(title="<short description of the task>", ai_tool="codex")
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This creates or resumes a session and returns `session_id`, `message_count`, and recent `messages`. Save `session_id` — you will use it for every subsequent call.
|
|
14
|
+
|
|
15
|
+
If `message_count > 0`, you are resuming an existing session. Acknowledge this to the user.
|
|
16
|
+
|
|
17
|
+
## Every Exchange
|
|
18
|
+
|
|
19
|
+
For every user message and your response, log both ends before outputting:
|
|
20
|
+
|
|
21
|
+
1. `log_message(session_id, role="user", content=<exact user message>)` — log the user's message first
|
|
22
|
+
2. Compose your response internally
|
|
23
|
+
3. `log_message(session_id, role="assistant", content=<your response>)` — log before outputting
|
|
24
|
+
4. Output the response
|
|
25
|
+
|
|
26
|
+
## Tool Calls and Results
|
|
27
|
+
|
|
28
|
+
Before executing any tool (shell command, file read, browser action, code execution):
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
log_tool_call(session_id, tool_name="<tool>", tool_input={<args>})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
After receiving the tool output:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
log_tool_result(session_id, tool_call_id=<id from log_tool_call>, output="<result>", exit_code=<n>)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Code Changes
|
|
41
|
+
|
|
42
|
+
After writing or editing any file:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
log_code_change(
|
|
46
|
+
session_id,
|
|
47
|
+
file_path="<absolute or repo-relative path>",
|
|
48
|
+
operation="create" | "edit" | "delete",
|
|
49
|
+
diff="<unified diff of the change>"
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This creates a cryptographically linked record of every file edit, tied to the conversation that caused it.
|
|
54
|
+
|
|
55
|
+
## Audit Order (per turn)
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
log_message(role="user") ← before doing anything
|
|
59
|
+
log_tool_call(...) ← before each tool
|
|
60
|
+
log_tool_result(...) ← after each tool result
|
|
61
|
+
log_code_change(...) ← after each file edit
|
|
62
|
+
log_message(role="assistant") ← before final response output
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Verify Integrity
|
|
66
|
+
|
|
67
|
+
At any point, the user can run:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
chron verify <session-id-prefix>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This walks the SHA-256 hash chain and verifies the Ed25519 session signature, confirming the audit record has not been modified since it was written.
|