chron-mcp 0.1.26 → 0.1.28
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 +148 -16
- package/dist/index.js +44 -3
- 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);
|
|
@@ -17032,12 +17033,16 @@ ${overview.total_detections > 0 ? `<p class="warn">⚠ ${overview.total_dete
|
|
|
17032
17033
|
</tbody>
|
|
17033
17034
|
</table>
|
|
17034
17035
|
${brokenList}
|
|
17035
|
-
<p style="font-size:11px;color:#6b7280">
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
</p>
|
|
17036
|
+
<p style="font-size:11px;color:#6b7280">
|
|
17037
|
+
Note: This report verifies <em>chain linkage</em> (prev_hash continuity). Full content-hash recomputation
|
|
17038
|
+
(which detects in-place tampering) can be performed with <code>chron verify <session-id></code>.
|
|
17039
|
+
</p>
|
|
17040
|
+
<p class="governance-note">
|
|
17041
|
+
For formal AI governance, CLAIIM extends Chron evidence with agent identity, policy gates,
|
|
17042
|
+
approvals, and runtime ALLOW/DENY enforcement. See <a href="https://claiim.io/preview">https://claiim.io/preview</a>.
|
|
17043
|
+
</p>
|
|
17039
17044
|
|
|
17040
|
-
<!-- \u2466 SESSION INVENTORY -->
|
|
17045
|
+
<!-- \u2466 SESSION INVENTORY -->
|
|
17041
17046
|
<h2 class="section-break">Session Inventory</h2>
|
|
17042
17047
|
<p>All sessions recorded in the audit database (most recent first, capped at 500 rows).</p>
|
|
17043
17048
|
<table>
|
|
@@ -17161,6 +17166,7 @@ tr:nth-child(even) td { background: #fafafa; }
|
|
|
17161
17166
|
.section-break { page-break-before: always; }
|
|
17162
17167
|
.appendix { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 16px 20px; margin-top: 12px; }
|
|
17163
17168
|
.appendix p, .appendix li { color: #374151; font-size: 12px; }
|
|
17169
|
+
.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
17170
|
ul { padding-left: 20px; margin-bottom: 8px; }
|
|
17165
17171
|
li { margin-bottom: 4px; }
|
|
17166
17172
|
@media print {
|
|
@@ -17370,7 +17376,7 @@ var require_package = __commonJS({
|
|
|
17370
17376
|
"package.json"(exports2, module2) {
|
|
17371
17377
|
module2.exports = {
|
|
17372
17378
|
name: "chron-mcp",
|
|
17373
|
-
version: "0.1.
|
|
17379
|
+
version: "0.1.28",
|
|
17374
17380
|
mcpName: "io.github.sirinivask/chron",
|
|
17375
17381
|
description: "Audit-grade timestamped logs for every AI conversation",
|
|
17376
17382
|
repository: {
|
|
@@ -18177,6 +18183,93 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
|
|
|
18177
18183
|
`);
|
|
18178
18184
|
process.stdout.write(`${DIM4}Config saved to ~/.chron/config.json \u2014 events will flow immediately in all running sessions.${RESET5}
|
|
18179
18185
|
|
|
18186
|
+
`);
|
|
18187
|
+
}
|
|
18188
|
+
function codexGlobalConfigPath() {
|
|
18189
|
+
return (0, import_path5.join)((0, import_os6.homedir)(), ".codex", "config.toml");
|
|
18190
|
+
}
|
|
18191
|
+
function isChronInCodexConfig(content) {
|
|
18192
|
+
return content.includes("[mcp_servers.chron]");
|
|
18193
|
+
}
|
|
18194
|
+
function appendChronToToml(content) {
|
|
18195
|
+
return content.trimEnd() + "\n" + CODEX_MCP_BLOCK;
|
|
18196
|
+
}
|
|
18197
|
+
async function connectCodex() {
|
|
18198
|
+
process.stdout.write(`
|
|
18199
|
+
${BOLD5}Connect Chron \u2192 Codex${RESET5}
|
|
18200
|
+
|
|
18201
|
+
`);
|
|
18202
|
+
const globalPath = codexGlobalConfigPath();
|
|
18203
|
+
const projectPath = (0, import_path5.join)(process.cwd(), ".codex", "config.toml");
|
|
18204
|
+
const hasGlobal = (0, import_fs5.existsSync)(globalPath);
|
|
18205
|
+
const hasProject = (0, import_fs5.existsSync)(projectPath);
|
|
18206
|
+
let targetPath;
|
|
18207
|
+
let isProject = false;
|
|
18208
|
+
if (hasGlobal && hasProject) {
|
|
18209
|
+
process.stdout.write(`${DIM4}Found both global and project Codex configs.${RESET5}
|
|
18210
|
+
`);
|
|
18211
|
+
process.stdout.write(`${DIM4} Global: ${globalPath}${RESET5}
|
|
18212
|
+
`);
|
|
18213
|
+
process.stdout.write(`${DIM4} Project: ${projectPath}${RESET5}
|
|
18214
|
+
|
|
18215
|
+
`);
|
|
18216
|
+
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
18217
|
+
const choice = await new Promise(
|
|
18218
|
+
(resolve) => rl.question(` Add Chron to (g)lobal or (p)roject config? [g/p]: `, resolve)
|
|
18219
|
+
);
|
|
18220
|
+
rl.close();
|
|
18221
|
+
isProject = choice.trim().toLowerCase() === "p";
|
|
18222
|
+
targetPath = isProject ? projectPath : globalPath;
|
|
18223
|
+
} else if (hasProject) {
|
|
18224
|
+
targetPath = projectPath;
|
|
18225
|
+
isProject = true;
|
|
18226
|
+
} else if (hasGlobal) {
|
|
18227
|
+
targetPath = globalPath;
|
|
18228
|
+
} else {
|
|
18229
|
+
process.stdout.write(`${YELLOW3}!${RESET5} Codex config not found. Creating ${globalPath}
|
|
18230
|
+
|
|
18231
|
+
`);
|
|
18232
|
+
(0, import_fs5.mkdirSync)((0, import_path5.join)((0, import_os6.homedir)(), ".codex"), { recursive: true });
|
|
18233
|
+
(0, import_fs5.writeFileSync)(globalPath, CODEX_MCP_BLOCK.trimStart());
|
|
18234
|
+
process.stdout.write(`${GREEN2}\u2713${RESET5} Created ${globalPath} with Chron MCP config.
|
|
18235
|
+
`);
|
|
18236
|
+
_printCodexNextSteps();
|
|
18237
|
+
return;
|
|
18238
|
+
}
|
|
18239
|
+
const existing = (0, import_fs5.readFileSync)(targetPath, "utf8");
|
|
18240
|
+
if (isChronInCodexConfig(existing)) {
|
|
18241
|
+
process.stdout.write(`${GREEN2}\u2713${RESET5} Chron MCP is already configured in ${targetPath}
|
|
18242
|
+
|
|
18243
|
+
`);
|
|
18244
|
+
_printCodexNextSteps();
|
|
18245
|
+
return;
|
|
18246
|
+
}
|
|
18247
|
+
const updated = appendChronToToml(existing);
|
|
18248
|
+
(0, import_fs5.writeFileSync)(targetPath, updated, "utf8");
|
|
18249
|
+
process.stdout.write(`${GREEN2}\u2713${RESET5} Added Chron MCP to ${targetPath}
|
|
18250
|
+
|
|
18251
|
+
`);
|
|
18252
|
+
_printCodexNextSteps();
|
|
18253
|
+
}
|
|
18254
|
+
function _printCodexNextSteps() {
|
|
18255
|
+
process.stdout.write(`${BOLD5}Next steps:${RESET5}
|
|
18256
|
+
|
|
18257
|
+
`);
|
|
18258
|
+
process.stdout.write(` 1. Restart Codex to pick up the new MCP server.
|
|
18259
|
+
`);
|
|
18260
|
+
process.stdout.write(` 2. Ask Codex to start a Chron session:
|
|
18261
|
+
`);
|
|
18262
|
+
process.stdout.write(` ${DIM4}"Use init_session to start a Chron audit session for this work."${RESET5}
|
|
18263
|
+
`);
|
|
18264
|
+
process.stdout.write(` 3. Verify setup:
|
|
18265
|
+
`);
|
|
18266
|
+
process.stdout.write(` ${CYAN4}chron doctor${RESET5}
|
|
18267
|
+
|
|
18268
|
+
`);
|
|
18269
|
+
process.stdout.write(`${DIM4}Codex will automatically call log_tool_call, log_tool_result, and
|
|
18270
|
+
`);
|
|
18271
|
+
process.stdout.write(`log_code_change during its work once the session is started.${RESET5}
|
|
18272
|
+
|
|
18180
18273
|
`);
|
|
18181
18274
|
}
|
|
18182
18275
|
async function runConnect(args2) {
|
|
@@ -18191,6 +18284,9 @@ async function runConnect(args2) {
|
|
|
18191
18284
|
case "splunk":
|
|
18192
18285
|
await connectSplunk();
|
|
18193
18286
|
break;
|
|
18287
|
+
case "codex":
|
|
18288
|
+
await connectCodex();
|
|
18289
|
+
break;
|
|
18194
18290
|
default: {
|
|
18195
18291
|
const name = subcommand ? `Unknown integration: ${subcommand}
|
|
18196
18292
|
|
|
@@ -18199,6 +18295,7 @@ async function runConnect(args2) {
|
|
|
18199
18295
|
`${name}Usage: chron connect <integration>
|
|
18200
18296
|
|
|
18201
18297
|
Integrations:
|
|
18298
|
+
codex Add Chron MCP to Codex config (global or project)
|
|
18202
18299
|
crowdstrike Connect to CrowdStrike LogScale (direct ingest)
|
|
18203
18300
|
sentinel Connect to Microsoft Sentinel (Azure Monitor Logs Ingestion API)
|
|
18204
18301
|
splunk Connect to Splunk via HTTP Event Collector (HEC)
|
|
@@ -18208,7 +18305,7 @@ Integrations:
|
|
|
18208
18305
|
}
|
|
18209
18306
|
}
|
|
18210
18307
|
}
|
|
18211
|
-
var import_readline, import_os6, import_path5, import_fs5, RESET5, BOLD5, DIM4, CYAN4, GREEN2, RED, YELLOW3;
|
|
18308
|
+
var import_readline, import_os6, import_path5, import_fs5, RESET5, BOLD5, DIM4, CYAN4, GREEN2, RED, YELLOW3, CODEX_MCP_BLOCK;
|
|
18212
18309
|
var init_connect = __esm({
|
|
18213
18310
|
"src/cli/connect.ts"() {
|
|
18214
18311
|
"use strict";
|
|
@@ -18223,6 +18320,11 @@ var init_connect = __esm({
|
|
|
18223
18320
|
GREEN2 = "\x1B[32m";
|
|
18224
18321
|
RED = "\x1B[31m";
|
|
18225
18322
|
YELLOW3 = "\x1B[33m";
|
|
18323
|
+
CODEX_MCP_BLOCK = `
|
|
18324
|
+
[mcp_servers.chron]
|
|
18325
|
+
command = "npx"
|
|
18326
|
+
args = ["-y", "chron-mcp"]
|
|
18327
|
+
`;
|
|
18226
18328
|
}
|
|
18227
18329
|
});
|
|
18228
18330
|
|
|
@@ -18545,7 +18647,15 @@ async function runSign(args2) {
|
|
|
18545
18647
|
process.exit(1);
|
|
18546
18648
|
}
|
|
18547
18649
|
const signature = signSession(session.id, finalContentHash, messageCount, firstCreatedAt);
|
|
18548
|
-
|
|
18650
|
+
try {
|
|
18651
|
+
await db.update(sessions).set({ signature }).where(eq(sessions.id, session.id));
|
|
18652
|
+
} catch (err) {
|
|
18653
|
+
const cause = err?.cause ?? err;
|
|
18654
|
+
process.stderr.write(`Failed to save signature: ${cause?.message ?? err.message}
|
|
18655
|
+
`);
|
|
18656
|
+
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");
|
|
18657
|
+
process.exit(1);
|
|
18658
|
+
}
|
|
18549
18659
|
const sigData = {
|
|
18550
18660
|
chron_signature: "v1",
|
|
18551
18661
|
session_id: session.id,
|
|
@@ -18870,7 +18980,7 @@ function loadConfig2() {
|
|
|
18870
18980
|
function mcpConfigs() {
|
|
18871
18981
|
const home = (0, import_os9.homedir)();
|
|
18872
18982
|
const plat = (0, import_os9.platform)();
|
|
18873
|
-
const
|
|
18983
|
+
const jsonCandidates = [
|
|
18874
18984
|
{
|
|
18875
18985
|
name: "Claude Desktop",
|
|
18876
18986
|
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 +18989,7 @@ function mcpConfigs() {
|
|
|
18879
18989
|
{ name: "Cursor", path: (0, import_path9.join)(home, ".cursor", "mcp.json") },
|
|
18880
18990
|
{ name: "Windsurf", path: (0, import_path9.join)(home, ".codeium", "windsurf", "mcp_config.json") }
|
|
18881
18991
|
];
|
|
18882
|
-
|
|
18992
|
+
const results = jsonCandidates.map((c) => {
|
|
18883
18993
|
if (!(0, import_fs10.existsSync)(c.path)) {
|
|
18884
18994
|
return { name: c.name, path: c.path, exists: false, chronConfigured: null };
|
|
18885
18995
|
}
|
|
@@ -18894,6 +19004,22 @@ function mcpConfigs() {
|
|
|
18894
19004
|
return { name: c.name, path: c.path, exists: true, chronConfigured: null, note: "Could not parse config" };
|
|
18895
19005
|
}
|
|
18896
19006
|
});
|
|
19007
|
+
const codexGlobal = (0, import_path9.join)(home, ".codex", "config.toml");
|
|
19008
|
+
const codexProject = (0, import_path9.join)(process.cwd(), ".codex", "config.toml");
|
|
19009
|
+
for (const p of [codexGlobal, codexProject]) {
|
|
19010
|
+
if (!(0, import_fs10.existsSync)(p)) {
|
|
19011
|
+
results.push({ name: p === codexGlobal ? "Codex (global)" : "Codex (project)", path: p, exists: false, chronConfigured: null });
|
|
19012
|
+
} else {
|
|
19013
|
+
try {
|
|
19014
|
+
const content = (0, import_fs10.readFileSync)(p, "utf8");
|
|
19015
|
+
const configured = content.includes("[mcp_servers.chron]");
|
|
19016
|
+
results.push({ name: p === codexGlobal ? "Codex (global)" : "Codex (project)", path: p, exists: true, chronConfigured: configured });
|
|
19017
|
+
} catch {
|
|
19018
|
+
results.push({ name: p === codexGlobal ? "Codex (global)" : "Codex (project)", path: p, exists: true, chronConfigured: null, note: "Could not read config" });
|
|
19019
|
+
}
|
|
19020
|
+
}
|
|
19021
|
+
}
|
|
19022
|
+
return results;
|
|
18897
19023
|
}
|
|
18898
19024
|
function npmLatestVersion() {
|
|
18899
19025
|
try {
|
|
@@ -19023,14 +19149,16 @@ ${BOLD8}chron doctor${RESET8} ${DIM7}v${import_package2.version}${RESET8}
|
|
|
19023
19149
|
if (!jsonMode)
|
|
19024
19150
|
ok2(`${t.name}`, "chron configured");
|
|
19025
19151
|
} else {
|
|
19152
|
+
const isCodex = t.name.startsWith("Codex");
|
|
19153
|
+
const fixHint = isCodex ? `chron connect codex` : `Add chron to mcpServers in ${t.path}
|
|
19154
|
+
See: https://github.com/sirinivask/chron#installation`;
|
|
19026
19155
|
results.push({
|
|
19027
19156
|
pass: "warn",
|
|
19028
|
-
label: `${t.name}: chron
|
|
19029
|
-
fix:
|
|
19030
|
-
See: https://github.com/sirinivask/chron#installation`
|
|
19157
|
+
label: `${t.name}: chron not configured`,
|
|
19158
|
+
fix: fixHint
|
|
19031
19159
|
});
|
|
19032
19160
|
if (!jsonMode)
|
|
19033
|
-
warn2(`${t.name}`, `chron not in mcpServers \u2014 add it to ${t.path}`);
|
|
19161
|
+
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
19162
|
}
|
|
19035
19163
|
}
|
|
19036
19164
|
if (!jsonMode)
|
|
@@ -19089,6 +19217,9 @@ ${BOLD8}chron doctor${RESET8} ${DIM7}v${import_package2.version}${RESET8}
|
|
|
19089
19217
|
if (failures.length === 0 && warnings.length === 0) {
|
|
19090
19218
|
process.stdout.write(`${GREEN5}${BOLD8}All checks passed.${RESET8} Chron is correctly set up.
|
|
19091
19219
|
|
|
19220
|
+
`);
|
|
19221
|
+
process.stdout.write(`${DIM7}Team AI governance: CLAIIM adds runtime ALLOW/DENY gates on top of Chron proof \u2014 ${CLAIIM_PREVIEW_URL}${RESET8}
|
|
19222
|
+
|
|
19092
19223
|
`);
|
|
19093
19224
|
} else {
|
|
19094
19225
|
if (failures.length > 0) {
|
|
@@ -19137,7 +19268,7 @@ ${BOLD8}chron doctor${RESET8} ${DIM7}v${import_package2.version}${RESET8}
|
|
|
19137
19268
|
}
|
|
19138
19269
|
process.exit(failures.length > 0 ? 1 : 0);
|
|
19139
19270
|
}
|
|
19140
|
-
var import_os9, import_path9, import_fs10, import_child_process3, import_package2, RESET8, BOLD8, DIM7, GREEN5, RED4, YELLOW6, CYAN6;
|
|
19271
|
+
var import_os9, import_path9, import_fs10, import_child_process3, import_package2, RESET8, BOLD8, DIM7, GREEN5, RED4, YELLOW6, CYAN6, CLAIIM_PREVIEW_URL;
|
|
19141
19272
|
var init_doctor = __esm({
|
|
19142
19273
|
"src/cli/doctor.ts"() {
|
|
19143
19274
|
"use strict";
|
|
@@ -19153,6 +19284,7 @@ var init_doctor = __esm({
|
|
|
19153
19284
|
RED4 = "\x1B[31m";
|
|
19154
19285
|
YELLOW6 = "\x1B[33m";
|
|
19155
19286
|
CYAN6 = "\x1B[36m";
|
|
19287
|
+
CLAIIM_PREVIEW_URL = "https://claiim.io/preview";
|
|
19156
19288
|
}
|
|
19157
19289
|
});
|
|
19158
19290
|
|
|
@@ -19845,7 +19977,7 @@ Commands:
|
|
|
19845
19977
|
export Export a session as markdown
|
|
19846
19978
|
secrets List detected secrets across sessions
|
|
19847
19979
|
settings View current configuration
|
|
19848
|
-
connect Connect to a SIEM
|
|
19980
|
+
connect Connect to a SIEM or AI tool (codex, crowdstrike, sentinel, splunk)
|
|
19849
19981
|
summary Structured summary of a session (timeline, mutations, secrets)
|
|
19850
19982
|
sign Sign a session with its Ed25519 key \u2014 produces a .chron.sig file
|
|
19851
19983
|
verify Verify a session's hash chain and Ed25519 signature
|
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);
|
|
@@ -38479,7 +38480,7 @@ var init_time = __esm({
|
|
|
38479
38480
|
var version4;
|
|
38480
38481
|
var init_package = __esm({
|
|
38481
38482
|
"package.json"() {
|
|
38482
|
-
version4 = "0.1.
|
|
38483
|
+
version4 = "0.1.28";
|
|
38483
38484
|
}
|
|
38484
38485
|
});
|
|
38485
38486
|
|
|
@@ -38837,6 +38838,31 @@ var init_ntp = __esm({
|
|
|
38837
38838
|
}
|
|
38838
38839
|
});
|
|
38839
38840
|
|
|
38841
|
+
// src/utils/terminal.ts
|
|
38842
|
+
function terminalAudit(message) {
|
|
38843
|
+
const setting = process.env.CHRON_TERMINAL_AUDIT;
|
|
38844
|
+
if (setting && DISABLE_VALUES.has(setting.toLowerCase()))
|
|
38845
|
+
return;
|
|
38846
|
+
process.stderr.write(`[chron] ${message}
|
|
38847
|
+
`);
|
|
38848
|
+
}
|
|
38849
|
+
function formatDuration(ms) {
|
|
38850
|
+
if (ms < 1e3)
|
|
38851
|
+
return `${ms}ms`;
|
|
38852
|
+
if (ms < 6e4)
|
|
38853
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
38854
|
+
const minutes = Math.floor(ms / 6e4);
|
|
38855
|
+
const seconds = Math.round(ms % 6e4 / 1e3);
|
|
38856
|
+
return `${minutes}m ${seconds}s`;
|
|
38857
|
+
}
|
|
38858
|
+
var DISABLE_VALUES;
|
|
38859
|
+
var init_terminal = __esm({
|
|
38860
|
+
"src/utils/terminal.ts"() {
|
|
38861
|
+
"use strict";
|
|
38862
|
+
DISABLE_VALUES = /* @__PURE__ */ new Set(["0", "false", "off", "no"]);
|
|
38863
|
+
}
|
|
38864
|
+
});
|
|
38865
|
+
|
|
38840
38866
|
// src/tools/sessions.ts
|
|
38841
38867
|
function attachNtpMetadata(db, sessionId) {
|
|
38842
38868
|
setImmediate(() => {
|
|
@@ -38867,6 +38893,7 @@ function startSession(db) {
|
|
|
38867
38893
|
});
|
|
38868
38894
|
attachNtpMetadata(db, id);
|
|
38869
38895
|
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: id.slice(0, 8), ai_tool: args.ai_tool ?? null } });
|
|
38896
|
+
terminalAudit(`session ${id.slice(0, 8)} started at ${now} (${args.ai_tool ?? "unknown"}: ${args.title})`);
|
|
38870
38897
|
return {
|
|
38871
38898
|
content: [{
|
|
38872
38899
|
type: "text",
|
|
@@ -38882,6 +38909,7 @@ function startSession(db) {
|
|
|
38882
38909
|
const [countRow] = await db.select({ count: sql`count(*)` }).from(messages).where(eq(messages.session_id, session.id));
|
|
38883
38910
|
await db.update(sessions).set({ updated_at: now }).where(eq(sessions.id, session.id));
|
|
38884
38911
|
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: session.id.slice(0, 8), ai_tool: session.ai_tool } });
|
|
38912
|
+
terminalAudit(`session ${session.id.slice(0, 8)} resumed at ${now} (${session.ai_tool ?? "unknown"}: ${session.title})`);
|
|
38885
38913
|
return {
|
|
38886
38914
|
content: [{
|
|
38887
38915
|
type: "text",
|
|
@@ -38918,6 +38946,7 @@ function initSession(db) {
|
|
|
38918
38946
|
created = true;
|
|
38919
38947
|
ai_tool = args.ai_tool ?? null;
|
|
38920
38948
|
attachNtpMetadata(db, id);
|
|
38949
|
+
terminalAudit(`session ${id.slice(0, 8)} started at ${now} (${ai_tool ?? "unknown"}: ${args.title})`);
|
|
38921
38950
|
} catch (e) {
|
|
38922
38951
|
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
38952
|
if (!isUnique)
|
|
@@ -38928,6 +38957,7 @@ function initSession(db) {
|
|
|
38928
38957
|
session_id = session.id;
|
|
38929
38958
|
created = false;
|
|
38930
38959
|
ai_tool = session.ai_tool;
|
|
38960
|
+
terminalAudit(`session ${session.id.slice(0, 8)} resumed at ${now} (${ai_tool ?? "unknown"}: ${session.title})`);
|
|
38931
38961
|
}
|
|
38932
38962
|
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: session_id.slice(0, 8), ai_tool } });
|
|
38933
38963
|
const limit = args.limit ?? 10;
|
|
@@ -39016,6 +39046,7 @@ var init_sessions = __esm({
|
|
|
39016
39046
|
init_relay();
|
|
39017
39047
|
init_signing();
|
|
39018
39048
|
init_ntp();
|
|
39049
|
+
init_terminal();
|
|
39019
39050
|
}
|
|
39020
39051
|
});
|
|
39021
39052
|
|
|
@@ -39328,7 +39359,7 @@ function isFkError(e) {
|
|
|
39328
39359
|
}
|
|
39329
39360
|
async function insertMessage(db, session_id, role, content, event_type) {
|
|
39330
39361
|
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);
|
|
39362
|
+
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
39363
|
const ts = localISOString();
|
|
39333
39364
|
const msgId = v4_default();
|
|
39334
39365
|
const prevHash = last[0]?.content_hash ?? null;
|
|
@@ -39345,7 +39376,13 @@ async function insertMessage(db, session_id, role, content, event_type) {
|
|
|
39345
39376
|
});
|
|
39346
39377
|
await tx.update(sessions).set({ updated_at: ts }).where(eq(sessions.id, session_id));
|
|
39347
39378
|
const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, session_id)).limit(1);
|
|
39348
|
-
return {
|
|
39379
|
+
return {
|
|
39380
|
+
id: msgId,
|
|
39381
|
+
now: ts,
|
|
39382
|
+
contentHash: hash,
|
|
39383
|
+
ai_tool: sessionRow?.ai_tool ?? null,
|
|
39384
|
+
previous_created_at: last[0]?.created_at ?? null
|
|
39385
|
+
};
|
|
39349
39386
|
});
|
|
39350
39387
|
}
|
|
39351
39388
|
function logMessage(db) {
|
|
@@ -39361,6 +39398,9 @@ function logMessage(db) {
|
|
|
39361
39398
|
now = result.now;
|
|
39362
39399
|
contentHash = result.contentHash;
|
|
39363
39400
|
sessionAiTool = result.ai_tool;
|
|
39401
|
+
const elapsed = result.previous_created_at ? ` (+${formatDuration(new Date(now).getTime() - new Date(result.previous_created_at).getTime())})` : "";
|
|
39402
|
+
const marker = args.role === "user" ? "user start" : "assistant end";
|
|
39403
|
+
terminalAudit(`${marker} ${now}${elapsed} session ${args.session_id.slice(0, 8)}`);
|
|
39364
39404
|
} catch (e) {
|
|
39365
39405
|
if (isFkError(e)) {
|
|
39366
39406
|
return { content: [{ type: "text", text: `Session not found: ${args.session_id}` }], isError: true };
|
|
@@ -39508,6 +39548,7 @@ var init_messages = __esm({
|
|
|
39508
39548
|
init_hash();
|
|
39509
39549
|
init_detect();
|
|
39510
39550
|
init_relay();
|
|
39551
|
+
init_terminal();
|
|
39511
39552
|
}
|
|
39512
39553
|
});
|
|
39513
39554
|
|
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.
|