chron-mcp 0.1.18 → 0.1.20
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/.claude-plugin/plugin.json +2 -3
- package/dist/cli/index.js +316 -28
- package/dist/index.js +261 -91
- package/package.json +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chron",
|
|
3
3
|
"description": "Audit-grade timestamped logs for every AI conversation — stored locally, owned by you.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.20",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "Nivaya"
|
|
7
|
-
"email": "ksrinivas07530@gmail.com"
|
|
6
|
+
"name": "Nivaya"
|
|
8
7
|
},
|
|
9
8
|
"homepage": "https://github.com/sirinivask/chron",
|
|
10
9
|
"repository": "https://github.com/sirinivask/chron",
|
package/dist/cli/index.js
CHANGED
|
@@ -1184,7 +1184,7 @@ var init_sql = __esm({
|
|
|
1184
1184
|
return new SQL([new StringChunk(str)]);
|
|
1185
1185
|
}
|
|
1186
1186
|
sql2.raw = raw;
|
|
1187
|
-
function
|
|
1187
|
+
function join6(chunks, separator) {
|
|
1188
1188
|
const result = [];
|
|
1189
1189
|
for (const [i, chunk] of chunks.entries()) {
|
|
1190
1190
|
if (i > 0 && separator !== void 0) {
|
|
@@ -1194,7 +1194,7 @@ var init_sql = __esm({
|
|
|
1194
1194
|
}
|
|
1195
1195
|
return new SQL(result);
|
|
1196
1196
|
}
|
|
1197
|
-
sql2.join =
|
|
1197
|
+
sql2.join = join6;
|
|
1198
1198
|
function identifier(value) {
|
|
1199
1199
|
return new Name(value);
|
|
1200
1200
|
}
|
|
@@ -2806,7 +2806,7 @@ var require_filesystem = __commonJS({
|
|
|
2806
2806
|
"use strict";
|
|
2807
2807
|
var fs = require("fs");
|
|
2808
2808
|
var LDD_PATH = "/usr/bin/ldd";
|
|
2809
|
-
var
|
|
2809
|
+
var readFileSync3 = (path) => fs.readFileSync(path, "utf-8");
|
|
2810
2810
|
var readFile = (path) => new Promise((resolve, reject) => {
|
|
2811
2811
|
fs.readFile(path, "utf-8", (err, data) => {
|
|
2812
2812
|
if (err) {
|
|
@@ -2818,7 +2818,7 @@ var require_filesystem = __commonJS({
|
|
|
2818
2818
|
});
|
|
2819
2819
|
module2.exports = {
|
|
2820
2820
|
LDD_PATH,
|
|
2821
|
-
readFileSync:
|
|
2821
|
+
readFileSync: readFileSync3,
|
|
2822
2822
|
readFile
|
|
2823
2823
|
};
|
|
2824
2824
|
}
|
|
@@ -2830,7 +2830,7 @@ var require_detect_libc = __commonJS({
|
|
|
2830
2830
|
"use strict";
|
|
2831
2831
|
var childProcess = require("child_process");
|
|
2832
2832
|
var { isLinux, getReport } = require_process();
|
|
2833
|
-
var { LDD_PATH, readFile, readFileSync:
|
|
2833
|
+
var { LDD_PATH, readFile, readFileSync: readFileSync3 } = require_filesystem();
|
|
2834
2834
|
var cachedFamilyFilesystem;
|
|
2835
2835
|
var cachedVersionFilesystem;
|
|
2836
2836
|
var command2 = "getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true";
|
|
@@ -2911,7 +2911,7 @@ var require_detect_libc = __commonJS({
|
|
|
2911
2911
|
}
|
|
2912
2912
|
cachedFamilyFilesystem = null;
|
|
2913
2913
|
try {
|
|
2914
|
-
const lddContent =
|
|
2914
|
+
const lddContent = readFileSync3(LDD_PATH);
|
|
2915
2915
|
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
|
|
2916
2916
|
} catch (e) {
|
|
2917
2917
|
}
|
|
@@ -2968,7 +2968,7 @@ var require_detect_libc = __commonJS({
|
|
|
2968
2968
|
}
|
|
2969
2969
|
cachedVersionFilesystem = null;
|
|
2970
2970
|
try {
|
|
2971
|
-
const lddContent =
|
|
2971
|
+
const lddContent = readFileSync3(LDD_PATH);
|
|
2972
2972
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
|
2973
2973
|
if (versionMatch) {
|
|
2974
2974
|
cachedVersionFilesystem = versionMatch[1];
|
|
@@ -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:
|
|
6165
|
+
var { randomBytes, createHash: createHash4 } = 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 = createHash4("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:
|
|
7232
|
+
var { createHash: createHash4 } = 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 = createHash4("sha1").update(key + GUID).digest("base64");
|
|
7542
7542
|
const headers = [
|
|
7543
7543
|
"HTTP/1.1 101 Switching Protocols",
|
|
7544
7544
|
"Upgrade: websocket",
|
|
@@ -14252,7 +14252,7 @@ var init_select2 = __esm({
|
|
|
14252
14252
|
const tableName = getTableLikeName(table);
|
|
14253
14253
|
for (const item of extractUsedTable(table))
|
|
14254
14254
|
this.usedTables.add(item);
|
|
14255
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
14255
|
+
if (typeof tableName === "string" && this.config.joins?.some((join6) => join6.alias === tableName)) {
|
|
14256
14256
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
14257
14257
|
}
|
|
14258
14258
|
if (!this.isPartialSelect) {
|
|
@@ -15141,7 +15141,7 @@ var init_update = __esm({
|
|
|
15141
15141
|
createJoin(joinType) {
|
|
15142
15142
|
return (table, on) => {
|
|
15143
15143
|
const tableName = getTableLikeName(table);
|
|
15144
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
15144
|
+
if (typeof tableName === "string" && this.config.joins.some((join6) => join6.alias === tableName)) {
|
|
15145
15145
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
15146
15146
|
}
|
|
15147
15147
|
if (typeof on === "function") {
|
|
@@ -16539,7 +16539,11 @@ var init_schema = __esm({
|
|
|
16539
16539
|
title: text("title").notNull().unique(),
|
|
16540
16540
|
ai_tool: text("ai_tool"),
|
|
16541
16541
|
created_at: text("created_at").notNull(),
|
|
16542
|
-
updated_at: text("updated_at").notNull()
|
|
16542
|
+
updated_at: text("updated_at").notNull(),
|
|
16543
|
+
parent_session_id: text("parent_session_id"),
|
|
16544
|
+
external_ref: text("external_ref"),
|
|
16545
|
+
public_key: text("public_key"),
|
|
16546
|
+
signature: text("signature")
|
|
16543
16547
|
});
|
|
16544
16548
|
messages = sqliteTable("messages", {
|
|
16545
16549
|
id: text("id").primaryKey(),
|
|
@@ -16548,7 +16552,9 @@ var init_schema = __esm({
|
|
|
16548
16552
|
content: text("content").notNull(),
|
|
16549
16553
|
created_at: text("created_at").notNull(),
|
|
16550
16554
|
prev_hash: text("prev_hash"),
|
|
16551
|
-
content_hash: text("content_hash")
|
|
16555
|
+
content_hash: text("content_hash"),
|
|
16556
|
+
// NULL = pre-migration row (treated as 'message'); set for all new rows
|
|
16557
|
+
event_type: text("event_type")
|
|
16552
16558
|
});
|
|
16553
16559
|
secrets_detected = sqliteTable("secrets_detected", {
|
|
16554
16560
|
id: text("id").primaryKey(),
|
|
@@ -16579,14 +16585,31 @@ async function initDb(dbPath2) {
|
|
|
16579
16585
|
for (const sql2 of CREATE_SQL) {
|
|
16580
16586
|
await client.execute(sql2);
|
|
16581
16587
|
}
|
|
16582
|
-
const
|
|
16583
|
-
const
|
|
16584
|
-
if (!
|
|
16588
|
+
const msgInfo = await client.execute("PRAGMA table_info(messages)");
|
|
16589
|
+
const msgCols = msgInfo.rows.map((r) => r[1]);
|
|
16590
|
+
if (!msgCols.includes("prev_hash")) {
|
|
16585
16591
|
await client.execute("ALTER TABLE messages ADD COLUMN prev_hash TEXT");
|
|
16586
16592
|
}
|
|
16587
|
-
if (!
|
|
16593
|
+
if (!msgCols.includes("content_hash")) {
|
|
16588
16594
|
await client.execute("ALTER TABLE messages ADD COLUMN content_hash TEXT");
|
|
16589
16595
|
}
|
|
16596
|
+
if (!msgCols.includes("event_type")) {
|
|
16597
|
+
await client.execute("ALTER TABLE messages ADD COLUMN event_type TEXT");
|
|
16598
|
+
}
|
|
16599
|
+
const sessInfo = await client.execute("PRAGMA table_info(sessions)");
|
|
16600
|
+
const sessCols = sessInfo.rows.map((r) => r[1]);
|
|
16601
|
+
if (!sessCols.includes("parent_session_id")) {
|
|
16602
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
|
|
16603
|
+
}
|
|
16604
|
+
if (!sessCols.includes("external_ref")) {
|
|
16605
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN external_ref TEXT");
|
|
16606
|
+
}
|
|
16607
|
+
if (!sessCols.includes("public_key")) {
|
|
16608
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN public_key TEXT");
|
|
16609
|
+
}
|
|
16610
|
+
if (!sessCols.includes("signature")) {
|
|
16611
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN signature TEXT");
|
|
16612
|
+
}
|
|
16590
16613
|
return drizzle(client, { schema: schema_exports });
|
|
16591
16614
|
}
|
|
16592
16615
|
var import_os, import_fs, import_path, CREATE_SQL;
|
|
@@ -16605,7 +16628,11 @@ var init_db2 = __esm({
|
|
|
16605
16628
|
title TEXT NOT NULL UNIQUE,
|
|
16606
16629
|
ai_tool TEXT,
|
|
16607
16630
|
created_at TEXT NOT NULL,
|
|
16608
|
-
updated_at TEXT NOT NULL
|
|
16631
|
+
updated_at TEXT NOT NULL,
|
|
16632
|
+
parent_session_id TEXT,
|
|
16633
|
+
external_ref TEXT,
|
|
16634
|
+
public_key TEXT,
|
|
16635
|
+
signature TEXT
|
|
16609
16636
|
)`,
|
|
16610
16637
|
`CREATE TABLE IF NOT EXISTS messages (
|
|
16611
16638
|
id TEXT PRIMARY KEY,
|
|
@@ -16615,6 +16642,7 @@ var init_db2 = __esm({
|
|
|
16615
16642
|
created_at TEXT NOT NULL,
|
|
16616
16643
|
prev_hash TEXT,
|
|
16617
16644
|
content_hash TEXT,
|
|
16645
|
+
event_type TEXT,
|
|
16618
16646
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
16619
16647
|
)`,
|
|
16620
16648
|
`CREATE TABLE IF NOT EXISTS secrets_detected (
|
|
@@ -16806,14 +16834,14 @@ async function queryIntegrity(db) {
|
|
|
16806
16834
|
unchained++;
|
|
16807
16835
|
continue;
|
|
16808
16836
|
}
|
|
16809
|
-
let
|
|
16837
|
+
let ok2 = true;
|
|
16810
16838
|
for (let i = 1; i < chained.length; i++) {
|
|
16811
16839
|
if (chained[i].prev_hash !== chained[i - 1].content_hash) {
|
|
16812
|
-
|
|
16840
|
+
ok2 = false;
|
|
16813
16841
|
break;
|
|
16814
16842
|
}
|
|
16815
16843
|
}
|
|
16816
|
-
if (
|
|
16844
|
+
if (ok2)
|
|
16817
16845
|
intact++;
|
|
16818
16846
|
else {
|
|
16819
16847
|
broken++;
|
|
@@ -17401,7 +17429,7 @@ var require_package = __commonJS({
|
|
|
17401
17429
|
"package.json"(exports2, module2) {
|
|
17402
17430
|
module2.exports = {
|
|
17403
17431
|
name: "chron-mcp",
|
|
17404
|
-
version: "0.1.
|
|
17432
|
+
version: "0.1.20",
|
|
17405
17433
|
mcpName: "io.github.sirinivask/chron",
|
|
17406
17434
|
description: "Audit-grade timestamped logs for every AI conversation",
|
|
17407
17435
|
repository: {
|
|
@@ -17560,7 +17588,7 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17560
17588
|
}
|
|
17561
17589
|
}]
|
|
17562
17590
|
}]);
|
|
17563
|
-
let
|
|
17591
|
+
let ok2 = false;
|
|
17564
17592
|
let statusCode = 0;
|
|
17565
17593
|
try {
|
|
17566
17594
|
const res = await fetch(url, {
|
|
@@ -17572,7 +17600,7 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17572
17600
|
body: testPayload
|
|
17573
17601
|
});
|
|
17574
17602
|
statusCode = res.status;
|
|
17575
|
-
|
|
17603
|
+
ok2 = res.ok;
|
|
17576
17604
|
} catch (e) {
|
|
17577
17605
|
process.stdout.write(`${RED}failed${RESET5}
|
|
17578
17606
|
`);
|
|
@@ -17581,7 +17609,7 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17581
17609
|
`);
|
|
17582
17610
|
process.exit(1);
|
|
17583
17611
|
}
|
|
17584
|
-
if (!
|
|
17612
|
+
if (!ok2) {
|
|
17585
17613
|
process.stdout.write(`${RED}failed (HTTP ${statusCode})${RESET5}
|
|
17586
17614
|
`);
|
|
17587
17615
|
process.stderr.write(` ${RED}Check your URL and token, then try again.${RESET5}
|
|
@@ -18100,6 +18128,254 @@ var init_summary = __esm({
|
|
|
18100
18128
|
}
|
|
18101
18129
|
});
|
|
18102
18130
|
|
|
18131
|
+
// src/utils/signing.ts
|
|
18132
|
+
function keysDir() {
|
|
18133
|
+
return (0, import_path4.join)((0, import_os5.homedir)(), ".chron", "keys");
|
|
18134
|
+
}
|
|
18135
|
+
function privKeyPath(sessionId) {
|
|
18136
|
+
return (0, import_path4.join)(keysDir(), `${sessionId}.key`);
|
|
18137
|
+
}
|
|
18138
|
+
function sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
18139
|
+
const input = `${sessionId}|${finalContentHash}|${messageCount}|${firstCreatedAt}`;
|
|
18140
|
+
return (0, import_crypto2.createHash)("sha256").update(input).digest();
|
|
18141
|
+
}
|
|
18142
|
+
function signSession(sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
18143
|
+
const privPath = privKeyPath(sessionId);
|
|
18144
|
+
if (!(0, import_fs4.existsSync)(privPath)) {
|
|
18145
|
+
throw new Error(`Private key not found: ${privPath}`);
|
|
18146
|
+
}
|
|
18147
|
+
const privateKey = (0, import_fs4.readFileSync)(privPath, "utf8");
|
|
18148
|
+
const digest = sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt);
|
|
18149
|
+
const sig = (0, import_crypto2.sign)(null, digest, privateKey);
|
|
18150
|
+
return sig.toString("base64");
|
|
18151
|
+
}
|
|
18152
|
+
function verifySignature(publicKeyPem, signatureB64, sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
18153
|
+
try {
|
|
18154
|
+
const digest = sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt);
|
|
18155
|
+
const sigBuf = Buffer.from(signatureB64, "base64");
|
|
18156
|
+
return (0, import_crypto2.verify)(null, digest, publicKeyPem, sigBuf);
|
|
18157
|
+
} catch {
|
|
18158
|
+
return false;
|
|
18159
|
+
}
|
|
18160
|
+
}
|
|
18161
|
+
var import_crypto2, import_fs4, import_path4, import_os5;
|
|
18162
|
+
var init_signing = __esm({
|
|
18163
|
+
"src/utils/signing.ts"() {
|
|
18164
|
+
"use strict";
|
|
18165
|
+
import_crypto2 = require("crypto");
|
|
18166
|
+
import_fs4 = require("fs");
|
|
18167
|
+
import_path4 = require("path");
|
|
18168
|
+
import_os5 = require("os");
|
|
18169
|
+
}
|
|
18170
|
+
});
|
|
18171
|
+
|
|
18172
|
+
// src/cli/sign.ts
|
|
18173
|
+
var sign_exports = {};
|
|
18174
|
+
__export(sign_exports, {
|
|
18175
|
+
runSign: () => runSign
|
|
18176
|
+
});
|
|
18177
|
+
async function runSign(args2) {
|
|
18178
|
+
const prefix = args2[0];
|
|
18179
|
+
if (!prefix) {
|
|
18180
|
+
process.stderr.write("Usage: chron sign <session-id-prefix>\n");
|
|
18181
|
+
process.exit(1);
|
|
18182
|
+
}
|
|
18183
|
+
const db = await initDb();
|
|
18184
|
+
const allSessions = await db.select().from(sessions);
|
|
18185
|
+
const session = allSessions.find((s) => s.id.startsWith(prefix));
|
|
18186
|
+
if (!session) {
|
|
18187
|
+
process.stderr.write(`Session not found: ${prefix}
|
|
18188
|
+
`);
|
|
18189
|
+
process.exit(1);
|
|
18190
|
+
}
|
|
18191
|
+
if (!session.public_key) {
|
|
18192
|
+
process.stderr.write(`Session ${session.id.slice(0, 8)} has no public key \u2014 was it created before v0.1.19?
|
|
18193
|
+
`);
|
|
18194
|
+
process.exit(1);
|
|
18195
|
+
}
|
|
18196
|
+
if (!(0, import_fs6.existsSync)(privKeyPath(session.id))) {
|
|
18197
|
+
process.stderr.write(`Private key not found: ${privKeyPath(session.id)}
|
|
18198
|
+
`);
|
|
18199
|
+
process.stderr.write("The key is stored on the machine where the session was created.\n");
|
|
18200
|
+
process.exit(1);
|
|
18201
|
+
}
|
|
18202
|
+
const [countRow] = await db.select({
|
|
18203
|
+
count: sql`count(*)`,
|
|
18204
|
+
first_created_at: sql`min(${messages.created_at})`
|
|
18205
|
+
}).from(messages).where(eq(messages.session_id, session.id));
|
|
18206
|
+
const messageCount = countRow?.count ?? 0;
|
|
18207
|
+
const firstCreatedAt = countRow?.first_created_at ?? "";
|
|
18208
|
+
if (messageCount === 0) {
|
|
18209
|
+
process.stderr.write("Session has no messages \u2014 nothing to sign.\n");
|
|
18210
|
+
process.exit(1);
|
|
18211
|
+
}
|
|
18212
|
+
const lastMsg = await db.select({ content_hash: messages.content_hash }).from(messages).where(eq(messages.session_id, session.id)).orderBy(asc(sql`rowid`)).limit(1e3);
|
|
18213
|
+
const finalContentHash = lastMsg[lastMsg.length - 1]?.content_hash ?? "";
|
|
18214
|
+
if (!finalContentHash) {
|
|
18215
|
+
process.stderr.write("Session has no hash chain \u2014 cannot sign.\n");
|
|
18216
|
+
process.exit(1);
|
|
18217
|
+
}
|
|
18218
|
+
const signature = signSession(session.id, finalContentHash, messageCount, firstCreatedAt);
|
|
18219
|
+
await db.update(sessions).set({ signature }).where(eq(sessions.id, session.id));
|
|
18220
|
+
const sigData = {
|
|
18221
|
+
chron_signature: "v1",
|
|
18222
|
+
session_id: session.id,
|
|
18223
|
+
session_title: session.title,
|
|
18224
|
+
public_key_pem: session.public_key,
|
|
18225
|
+
message_count: messageCount,
|
|
18226
|
+
first_message_at: firstCreatedAt,
|
|
18227
|
+
final_content_hash: finalContentHash,
|
|
18228
|
+
signature,
|
|
18229
|
+
signed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
18230
|
+
};
|
|
18231
|
+
const sigFile = (0, import_path5.join)(process.cwd(), `${session.id.slice(0, 8)}.chron.sig`);
|
|
18232
|
+
(0, import_fs5.writeFileSync)(sigFile, JSON.stringify(sigData, null, 2));
|
|
18233
|
+
process.stdout.write(`Signed session ${session.id.slice(0, 8)}
|
|
18234
|
+
`);
|
|
18235
|
+
process.stdout.write(` messages : ${messageCount}
|
|
18236
|
+
`);
|
|
18237
|
+
process.stdout.write(` final hash: ${finalContentHash.slice(0, 16)}\u2026
|
|
18238
|
+
`);
|
|
18239
|
+
process.stdout.write(` sig file : ${sigFile}
|
|
18240
|
+
`);
|
|
18241
|
+
}
|
|
18242
|
+
var import_fs5, import_path5, import_fs6;
|
|
18243
|
+
var init_sign = __esm({
|
|
18244
|
+
"src/cli/sign.ts"() {
|
|
18245
|
+
"use strict";
|
|
18246
|
+
init_drizzle_orm();
|
|
18247
|
+
import_fs5 = require("fs");
|
|
18248
|
+
import_path5 = require("path");
|
|
18249
|
+
init_db2();
|
|
18250
|
+
init_schema();
|
|
18251
|
+
init_signing();
|
|
18252
|
+
import_fs6 = require("fs");
|
|
18253
|
+
}
|
|
18254
|
+
});
|
|
18255
|
+
|
|
18256
|
+
// src/utils/hash.ts
|
|
18257
|
+
function computeContentHash(sessionId, role, content, createdAt, prevHash, eventType) {
|
|
18258
|
+
const base = `${sessionId}|${role}|${content}|${createdAt}|${prevHash ?? ""}`;
|
|
18259
|
+
const input = eventType != null ? `${base}|${eventType}` : base;
|
|
18260
|
+
return (0, import_crypto3.createHash)("sha256").update(input).digest("hex");
|
|
18261
|
+
}
|
|
18262
|
+
var import_crypto3;
|
|
18263
|
+
var init_hash = __esm({
|
|
18264
|
+
"src/utils/hash.ts"() {
|
|
18265
|
+
"use strict";
|
|
18266
|
+
import_crypto3 = require("crypto");
|
|
18267
|
+
}
|
|
18268
|
+
});
|
|
18269
|
+
|
|
18270
|
+
// src/cli/verify.ts
|
|
18271
|
+
var verify_exports = {};
|
|
18272
|
+
__export(verify_exports, {
|
|
18273
|
+
runVerify: () => runVerify
|
|
18274
|
+
});
|
|
18275
|
+
function ok(msg) {
|
|
18276
|
+
process.stdout.write(` ${GREEN3}\u2713${RESET7} ${msg}
|
|
18277
|
+
`);
|
|
18278
|
+
}
|
|
18279
|
+
function fail(msg) {
|
|
18280
|
+
process.stdout.write(` ${RED3}\u2717${RESET7} ${msg}
|
|
18281
|
+
`);
|
|
18282
|
+
}
|
|
18283
|
+
function warn(msg) {
|
|
18284
|
+
process.stdout.write(` ${YELLOW5}!${RESET7} ${msg}
|
|
18285
|
+
`);
|
|
18286
|
+
}
|
|
18287
|
+
async function runVerify(args2) {
|
|
18288
|
+
const prefix = args2[0];
|
|
18289
|
+
if (!prefix) {
|
|
18290
|
+
process.stderr.write("Usage: chron verify <session-id-prefix>\n");
|
|
18291
|
+
process.exit(1);
|
|
18292
|
+
}
|
|
18293
|
+
const db = await initDb();
|
|
18294
|
+
const allSessions = await db.select().from(sessions);
|
|
18295
|
+
const session = allSessions.find((s) => s.id.startsWith(prefix));
|
|
18296
|
+
if (!session) {
|
|
18297
|
+
process.stderr.write(`Session not found: ${prefix}
|
|
18298
|
+
`);
|
|
18299
|
+
process.exit(1);
|
|
18300
|
+
}
|
|
18301
|
+
process.stdout.write(`
|
|
18302
|
+
${BOLD7}Verifying session ${session.id.slice(0, 8)}${RESET7} \u2014 ${session.title}
|
|
18303
|
+
|
|
18304
|
+
`);
|
|
18305
|
+
const rows = await db.select().from(messages).where(eq(messages.session_id, session.id)).orderBy(asc(messages.created_at), asc(sql`rowid`));
|
|
18306
|
+
process.stdout.write(`${BOLD7}Hash chain${RESET7} (${rows.length} messages)
|
|
18307
|
+
`);
|
|
18308
|
+
const chained = rows.filter((r) => r.content_hash !== null);
|
|
18309
|
+
let chainOk = true;
|
|
18310
|
+
if (chained.length === 0) {
|
|
18311
|
+
warn("No chained messages \u2014 session pre-dates hash chaining");
|
|
18312
|
+
} else {
|
|
18313
|
+
for (let i = 0; i < chained.length; i++) {
|
|
18314
|
+
const row = chained[i];
|
|
18315
|
+
const expectedPrev = i === 0 ? null : chained[i - 1].content_hash;
|
|
18316
|
+
if (row.prev_hash !== expectedPrev) {
|
|
18317
|
+
fail(`Row ${i + 1} (${row.id.slice(0, 8)}): prev_hash mismatch \u2014 chain broken`);
|
|
18318
|
+
chainOk = false;
|
|
18319
|
+
break;
|
|
18320
|
+
}
|
|
18321
|
+
const expected = computeContentHash(row.session_id, row.role, row.content, row.created_at, row.prev_hash, row.event_type ?? void 0);
|
|
18322
|
+
if (row.content_hash !== expected) {
|
|
18323
|
+
fail(`Row ${i + 1} (${row.id.slice(0, 8)}): content_hash mismatch \u2014 row tampered`);
|
|
18324
|
+
chainOk = false;
|
|
18325
|
+
break;
|
|
18326
|
+
}
|
|
18327
|
+
}
|
|
18328
|
+
if (chainOk)
|
|
18329
|
+
ok(`All ${chained.length} hashes valid`);
|
|
18330
|
+
}
|
|
18331
|
+
process.stdout.write(`
|
|
18332
|
+
${BOLD7}Signature${RESET7}
|
|
18333
|
+
`);
|
|
18334
|
+
if (!session.public_key) {
|
|
18335
|
+
warn("No public key \u2014 session was created before signing support (v0.1.19)");
|
|
18336
|
+
} else if (!session.signature) {
|
|
18337
|
+
warn("Not yet signed \u2014 run: chron sign " + session.id.slice(0, 8));
|
|
18338
|
+
} else {
|
|
18339
|
+
const finalHash = chained[chained.length - 1]?.content_hash ?? "";
|
|
18340
|
+
const firstCreatedAt = rows[0]?.created_at ?? "";
|
|
18341
|
+
const sigValid = verifySignature(
|
|
18342
|
+
session.public_key,
|
|
18343
|
+
session.signature,
|
|
18344
|
+
session.id,
|
|
18345
|
+
finalHash,
|
|
18346
|
+
rows.length,
|
|
18347
|
+
firstCreatedAt
|
|
18348
|
+
);
|
|
18349
|
+
if (sigValid) {
|
|
18350
|
+
ok("Ed25519 signature valid");
|
|
18351
|
+
process.stdout.write(` ${DIM6}public key: ${session.public_key.split("\n")[1]?.slice(0, 40)}\u2026${RESET7}
|
|
18352
|
+
`);
|
|
18353
|
+
} else {
|
|
18354
|
+
fail("Ed25519 signature INVALID \u2014 session data may have been altered after signing");
|
|
18355
|
+
}
|
|
18356
|
+
}
|
|
18357
|
+
process.stdout.write("\n");
|
|
18358
|
+
const allOk = chainOk && (session.signature ? true : true);
|
|
18359
|
+
process.exit(allOk ? 0 : 1);
|
|
18360
|
+
}
|
|
18361
|
+
var RESET7, BOLD7, GREEN3, RED3, DIM6, YELLOW5;
|
|
18362
|
+
var init_verify = __esm({
|
|
18363
|
+
"src/cli/verify.ts"() {
|
|
18364
|
+
"use strict";
|
|
18365
|
+
init_drizzle_orm();
|
|
18366
|
+
init_db2();
|
|
18367
|
+
init_schema();
|
|
18368
|
+
init_hash();
|
|
18369
|
+
init_signing();
|
|
18370
|
+
RESET7 = "\x1B[0m";
|
|
18371
|
+
BOLD7 = "\x1B[1m";
|
|
18372
|
+
GREEN3 = "\x1B[32m";
|
|
18373
|
+
RED3 = "\x1B[31m";
|
|
18374
|
+
DIM6 = "\x1B[2m";
|
|
18375
|
+
YELLOW5 = "\x1B[33m";
|
|
18376
|
+
}
|
|
18377
|
+
});
|
|
18378
|
+
|
|
18103
18379
|
// src/cli/index.ts
|
|
18104
18380
|
var [, , command, ...args] = process.argv;
|
|
18105
18381
|
async function main() {
|
|
@@ -18139,6 +18415,16 @@ async function main() {
|
|
|
18139
18415
|
await runSummary2(args);
|
|
18140
18416
|
break;
|
|
18141
18417
|
}
|
|
18418
|
+
case "sign": {
|
|
18419
|
+
const { runSign: runSign2 } = await Promise.resolve().then(() => (init_sign(), sign_exports));
|
|
18420
|
+
await runSign2(args);
|
|
18421
|
+
break;
|
|
18422
|
+
}
|
|
18423
|
+
case "verify": {
|
|
18424
|
+
const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
|
|
18425
|
+
await runVerify2(args);
|
|
18426
|
+
break;
|
|
18427
|
+
}
|
|
18142
18428
|
default: {
|
|
18143
18429
|
const name = command ? `Unknown command: ${command}
|
|
18144
18430
|
|
|
@@ -18154,6 +18440,8 @@ Commands:
|
|
|
18154
18440
|
settings View current configuration
|
|
18155
18441
|
connect Connect to a SIEM integration (crowdstrike, sentinel, splunk)
|
|
18156
18442
|
summary Structured summary of a session (timeline, mutations, secrets)
|
|
18443
|
+
sign Sign a session with its Ed25519 key \u2014 produces a .chron.sig file
|
|
18444
|
+
verify Verify a session's hash chain and Ed25519 signature
|
|
18157
18445
|
|
|
18158
18446
|
Options (history):
|
|
18159
18447
|
--limit=<n> Max sessions to show (default: 20)
|
|
@@ -18164,7 +18452,7 @@ Options (report):
|
|
|
18164
18452
|
--format=soc2 Generate SOC 2 HTML evidence package
|
|
18165
18453
|
--output=<file> Output file for --format=soc2 (default: soc2-report.html)
|
|
18166
18454
|
|
|
18167
|
-
Options (export / secrets):
|
|
18455
|
+
Options (export / secrets / sign / verify):
|
|
18168
18456
|
<id-prefix> Scope to a single session
|
|
18169
18457
|
`
|
|
18170
18458
|
);
|
package/dist/index.js
CHANGED
|
@@ -6890,7 +6890,7 @@ var require_filesystem = __commonJS({
|
|
|
6890
6890
|
"use strict";
|
|
6891
6891
|
var fs = require("fs");
|
|
6892
6892
|
var LDD_PATH = "/usr/bin/ldd";
|
|
6893
|
-
var
|
|
6893
|
+
var readFileSync4 = (path) => fs.readFileSync(path, "utf-8");
|
|
6894
6894
|
var readFile = (path) => new Promise((resolve, reject) => {
|
|
6895
6895
|
fs.readFile(path, "utf-8", (err, data) => {
|
|
6896
6896
|
if (err) {
|
|
@@ -6902,7 +6902,7 @@ var require_filesystem = __commonJS({
|
|
|
6902
6902
|
});
|
|
6903
6903
|
module2.exports = {
|
|
6904
6904
|
LDD_PATH,
|
|
6905
|
-
readFileSync:
|
|
6905
|
+
readFileSync: readFileSync4,
|
|
6906
6906
|
readFile
|
|
6907
6907
|
};
|
|
6908
6908
|
}
|
|
@@ -6914,7 +6914,7 @@ var require_detect_libc = __commonJS({
|
|
|
6914
6914
|
"use strict";
|
|
6915
6915
|
var childProcess = require("child_process");
|
|
6916
6916
|
var { isLinux, getReport } = require_process();
|
|
6917
|
-
var { LDD_PATH, readFile, readFileSync:
|
|
6917
|
+
var { LDD_PATH, readFile, readFileSync: readFileSync4 } = require_filesystem();
|
|
6918
6918
|
var cachedFamilyFilesystem;
|
|
6919
6919
|
var cachedVersionFilesystem;
|
|
6920
6920
|
var command = "getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true";
|
|
@@ -6995,7 +6995,7 @@ var require_detect_libc = __commonJS({
|
|
|
6995
6995
|
}
|
|
6996
6996
|
cachedFamilyFilesystem = null;
|
|
6997
6997
|
try {
|
|
6998
|
-
const lddContent =
|
|
6998
|
+
const lddContent = readFileSync4(LDD_PATH);
|
|
6999
6999
|
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
|
|
7000
7000
|
} catch (e) {
|
|
7001
7001
|
}
|
|
@@ -7052,7 +7052,7 @@ var require_detect_libc = __commonJS({
|
|
|
7052
7052
|
}
|
|
7053
7053
|
cachedVersionFilesystem = null;
|
|
7054
7054
|
try {
|
|
7055
|
-
const lddContent =
|
|
7055
|
+
const lddContent = readFileSync4(LDD_PATH);
|
|
7056
7056
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
|
7057
7057
|
if (versionMatch) {
|
|
7058
7058
|
cachedVersionFilesystem = versionMatch[1];
|
|
@@ -10246,7 +10246,7 @@ var require_websocket = __commonJS({
|
|
|
10246
10246
|
var http = require("http");
|
|
10247
10247
|
var net = require("net");
|
|
10248
10248
|
var tls = require("tls");
|
|
10249
|
-
var { randomBytes, createHash:
|
|
10249
|
+
var { randomBytes, createHash: createHash4 } = require("crypto");
|
|
10250
10250
|
var { Duplex, Readable } = require("stream");
|
|
10251
10251
|
var { URL: URL3 } = require("url");
|
|
10252
10252
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -10926,7 +10926,7 @@ var require_websocket = __commonJS({
|
|
|
10926
10926
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
10927
10927
|
return;
|
|
10928
10928
|
}
|
|
10929
|
-
const digest =
|
|
10929
|
+
const digest = createHash4("sha1").update(key + GUID).digest("base64");
|
|
10930
10930
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
10931
10931
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
10932
10932
|
return;
|
|
@@ -11313,7 +11313,7 @@ var require_websocket_server = __commonJS({
|
|
|
11313
11313
|
var EventEmitter = require("events");
|
|
11314
11314
|
var http = require("http");
|
|
11315
11315
|
var { Duplex } = require("stream");
|
|
11316
|
-
var { createHash:
|
|
11316
|
+
var { createHash: createHash4 } = require("crypto");
|
|
11317
11317
|
var extension2 = require_extension();
|
|
11318
11318
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
11319
11319
|
var subprotocol2 = require_subprotocol();
|
|
@@ -11622,7 +11622,7 @@ var require_websocket_server = __commonJS({
|
|
|
11622
11622
|
}
|
|
11623
11623
|
if (this._state > RUNNING)
|
|
11624
11624
|
return abortHandshake(socket, 503);
|
|
11625
|
-
const digest =
|
|
11625
|
+
const digest = createHash4("sha1").update(key + GUID).digest("base64");
|
|
11626
11626
|
const headers = [
|
|
11627
11627
|
"HTTP/1.1 101 Switching Protocols",
|
|
11628
11628
|
"Upgrade: websocket",
|
|
@@ -17521,7 +17521,7 @@ var init_sql2 = __esm({
|
|
|
17521
17521
|
return new SQL([new StringChunk(str)]);
|
|
17522
17522
|
}
|
|
17523
17523
|
sql2.raw = raw;
|
|
17524
|
-
function
|
|
17524
|
+
function join6(chunks, separator) {
|
|
17525
17525
|
const result = [];
|
|
17526
17526
|
for (const [i, chunk] of chunks.entries()) {
|
|
17527
17527
|
if (i > 0 && separator !== void 0) {
|
|
@@ -17531,7 +17531,7 @@ var init_sql2 = __esm({
|
|
|
17531
17531
|
}
|
|
17532
17532
|
return new SQL(result);
|
|
17533
17533
|
}
|
|
17534
|
-
sql2.join =
|
|
17534
|
+
sql2.join = join6;
|
|
17535
17535
|
function identifier(value) {
|
|
17536
17536
|
return new Name(value);
|
|
17537
17537
|
}
|
|
@@ -20462,7 +20462,7 @@ var init_select2 = __esm({
|
|
|
20462
20462
|
const tableName = getTableLikeName(table);
|
|
20463
20463
|
for (const item of extractUsedTable(table))
|
|
20464
20464
|
this.usedTables.add(item);
|
|
20465
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
20465
|
+
if (typeof tableName === "string" && this.config.joins?.some((join6) => join6.alias === tableName)) {
|
|
20466
20466
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
20467
20467
|
}
|
|
20468
20468
|
if (!this.isPartialSelect) {
|
|
@@ -21351,7 +21351,7 @@ var init_update = __esm({
|
|
|
21351
21351
|
createJoin(joinType) {
|
|
21352
21352
|
return (table, on) => {
|
|
21353
21353
|
const tableName = getTableLikeName(table);
|
|
21354
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
21354
|
+
if (typeof tableName === "string" && this.config.joins.some((join6) => join6.alias === tableName)) {
|
|
21355
21355
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
21356
21356
|
}
|
|
21357
21357
|
if (typeof on === "function") {
|
|
@@ -22749,7 +22749,11 @@ var init_schema = __esm({
|
|
|
22749
22749
|
title: text("title").notNull().unique(),
|
|
22750
22750
|
ai_tool: text("ai_tool"),
|
|
22751
22751
|
created_at: text("created_at").notNull(),
|
|
22752
|
-
updated_at: text("updated_at").notNull()
|
|
22752
|
+
updated_at: text("updated_at").notNull(),
|
|
22753
|
+
parent_session_id: text("parent_session_id"),
|
|
22754
|
+
external_ref: text("external_ref"),
|
|
22755
|
+
public_key: text("public_key"),
|
|
22756
|
+
signature: text("signature")
|
|
22753
22757
|
});
|
|
22754
22758
|
messages = sqliteTable("messages", {
|
|
22755
22759
|
id: text("id").primaryKey(),
|
|
@@ -22758,7 +22762,9 @@ var init_schema = __esm({
|
|
|
22758
22762
|
content: text("content").notNull(),
|
|
22759
22763
|
created_at: text("created_at").notNull(),
|
|
22760
22764
|
prev_hash: text("prev_hash"),
|
|
22761
|
-
content_hash: text("content_hash")
|
|
22765
|
+
content_hash: text("content_hash"),
|
|
22766
|
+
// NULL = pre-migration row (treated as 'message'); set for all new rows
|
|
22767
|
+
event_type: text("event_type")
|
|
22762
22768
|
});
|
|
22763
22769
|
secrets_detected = sqliteTable("secrets_detected", {
|
|
22764
22770
|
id: text("id").primaryKey(),
|
|
@@ -22789,14 +22795,31 @@ async function initDb(dbPath) {
|
|
|
22789
22795
|
for (const sql2 of CREATE_SQL) {
|
|
22790
22796
|
await client.execute(sql2);
|
|
22791
22797
|
}
|
|
22792
|
-
const
|
|
22793
|
-
const
|
|
22794
|
-
if (!
|
|
22798
|
+
const msgInfo = await client.execute("PRAGMA table_info(messages)");
|
|
22799
|
+
const msgCols = msgInfo.rows.map((r) => r[1]);
|
|
22800
|
+
if (!msgCols.includes("prev_hash")) {
|
|
22795
22801
|
await client.execute("ALTER TABLE messages ADD COLUMN prev_hash TEXT");
|
|
22796
22802
|
}
|
|
22797
|
-
if (!
|
|
22803
|
+
if (!msgCols.includes("content_hash")) {
|
|
22798
22804
|
await client.execute("ALTER TABLE messages ADD COLUMN content_hash TEXT");
|
|
22799
22805
|
}
|
|
22806
|
+
if (!msgCols.includes("event_type")) {
|
|
22807
|
+
await client.execute("ALTER TABLE messages ADD COLUMN event_type TEXT");
|
|
22808
|
+
}
|
|
22809
|
+
const sessInfo = await client.execute("PRAGMA table_info(sessions)");
|
|
22810
|
+
const sessCols = sessInfo.rows.map((r) => r[1]);
|
|
22811
|
+
if (!sessCols.includes("parent_session_id")) {
|
|
22812
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
|
|
22813
|
+
}
|
|
22814
|
+
if (!sessCols.includes("external_ref")) {
|
|
22815
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN external_ref TEXT");
|
|
22816
|
+
}
|
|
22817
|
+
if (!sessCols.includes("public_key")) {
|
|
22818
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN public_key TEXT");
|
|
22819
|
+
}
|
|
22820
|
+
if (!sessCols.includes("signature")) {
|
|
22821
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN signature TEXT");
|
|
22822
|
+
}
|
|
22800
22823
|
return drizzle(client, { schema: schema_exports });
|
|
22801
22824
|
}
|
|
22802
22825
|
var import_os, import_fs, import_path, CREATE_SQL;
|
|
@@ -22815,7 +22838,11 @@ var init_db2 = __esm({
|
|
|
22815
22838
|
title TEXT NOT NULL UNIQUE,
|
|
22816
22839
|
ai_tool TEXT,
|
|
22817
22840
|
created_at TEXT NOT NULL,
|
|
22818
|
-
updated_at TEXT NOT NULL
|
|
22841
|
+
updated_at TEXT NOT NULL,
|
|
22842
|
+
parent_session_id TEXT,
|
|
22843
|
+
external_ref TEXT,
|
|
22844
|
+
public_key TEXT,
|
|
22845
|
+
signature TEXT
|
|
22819
22846
|
)`,
|
|
22820
22847
|
`CREATE TABLE IF NOT EXISTS messages (
|
|
22821
22848
|
id TEXT PRIMARY KEY,
|
|
@@ -22825,6 +22852,7 @@ var init_db2 = __esm({
|
|
|
22825
22852
|
created_at TEXT NOT NULL,
|
|
22826
22853
|
prev_hash TEXT,
|
|
22827
22854
|
content_hash TEXT,
|
|
22855
|
+
event_type TEXT,
|
|
22828
22856
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
22829
22857
|
)`,
|
|
22830
22858
|
`CREATE TABLE IF NOT EXISTS secrets_detected (
|
|
@@ -38446,7 +38474,7 @@ var init_time = __esm({
|
|
|
38446
38474
|
var version4;
|
|
38447
38475
|
var init_package = __esm({
|
|
38448
38476
|
"package.json"() {
|
|
38449
|
-
version4 = "0.1.
|
|
38477
|
+
version4 = "0.1.20";
|
|
38450
38478
|
}
|
|
38451
38479
|
});
|
|
38452
38480
|
|
|
@@ -38690,18 +38718,71 @@ var init_relay = __esm({
|
|
|
38690
38718
|
}
|
|
38691
38719
|
});
|
|
38692
38720
|
|
|
38721
|
+
// src/utils/signing.ts
|
|
38722
|
+
function keysDir() {
|
|
38723
|
+
return (0, import_path3.join)((0, import_os3.homedir)(), ".chron", "keys");
|
|
38724
|
+
}
|
|
38725
|
+
function privKeyPath(sessionId) {
|
|
38726
|
+
return (0, import_path3.join)(keysDir(), `${sessionId}.key`);
|
|
38727
|
+
}
|
|
38728
|
+
function pubKeyPath(sessionId) {
|
|
38729
|
+
return (0, import_path3.join)(keysDir(), `${sessionId}.pub`);
|
|
38730
|
+
}
|
|
38731
|
+
function generateSessionKeypair(sessionId) {
|
|
38732
|
+
const { privateKey, publicKey } = (0, import_crypto4.generateKeyPairSync)("ed25519", {
|
|
38733
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
38734
|
+
publicKeyEncoding: { type: "spki", format: "pem" }
|
|
38735
|
+
});
|
|
38736
|
+
const dir = keysDir();
|
|
38737
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
38738
|
+
(0, import_fs3.writeFileSync)(privKeyPath(sessionId), privateKey, { mode: 384 });
|
|
38739
|
+
(0, import_fs3.writeFileSync)(pubKeyPath(sessionId), publicKey);
|
|
38740
|
+
return publicKey;
|
|
38741
|
+
}
|
|
38742
|
+
function sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
38743
|
+
const input = `${sessionId}|${finalContentHash}|${messageCount}|${firstCreatedAt}`;
|
|
38744
|
+
return (0, import_crypto4.createHash)("sha256").update(input).digest();
|
|
38745
|
+
}
|
|
38746
|
+
function verifySignature(publicKeyPem, signatureB64, sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
38747
|
+
try {
|
|
38748
|
+
const digest = sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt);
|
|
38749
|
+
const sigBuf = Buffer.from(signatureB64, "base64");
|
|
38750
|
+
return (0, import_crypto4.verify)(null, digest, publicKeyPem, sigBuf);
|
|
38751
|
+
} catch {
|
|
38752
|
+
return false;
|
|
38753
|
+
}
|
|
38754
|
+
}
|
|
38755
|
+
var import_crypto4, import_fs3, import_path3, import_os3;
|
|
38756
|
+
var init_signing = __esm({
|
|
38757
|
+
"src/utils/signing.ts"() {
|
|
38758
|
+
"use strict";
|
|
38759
|
+
import_crypto4 = require("crypto");
|
|
38760
|
+
import_fs3 = require("fs");
|
|
38761
|
+
import_path3 = require("path");
|
|
38762
|
+
import_os3 = require("os");
|
|
38763
|
+
}
|
|
38764
|
+
});
|
|
38765
|
+
|
|
38693
38766
|
// src/tools/sessions.ts
|
|
38694
38767
|
function startSession(db) {
|
|
38695
38768
|
return async (args) => {
|
|
38696
38769
|
const id = v4_default();
|
|
38697
38770
|
const now = localISOString();
|
|
38698
38771
|
try {
|
|
38772
|
+
let publicKey = null;
|
|
38773
|
+
try {
|
|
38774
|
+
publicKey = generateSessionKeypair(id);
|
|
38775
|
+
} catch {
|
|
38776
|
+
}
|
|
38699
38777
|
await db.insert(sessions).values({
|
|
38700
38778
|
id,
|
|
38701
38779
|
title: args.title,
|
|
38702
38780
|
ai_tool: args.ai_tool ?? null,
|
|
38703
38781
|
created_at: now,
|
|
38704
|
-
updated_at: now
|
|
38782
|
+
updated_at: now,
|
|
38783
|
+
parent_session_id: args.parent_session_id ?? null,
|
|
38784
|
+
external_ref: args.external_ref ?? null,
|
|
38785
|
+
public_key: publicKey
|
|
38705
38786
|
});
|
|
38706
38787
|
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: id.slice(0, 8), ai_tool: args.ai_tool ?? null } });
|
|
38707
38788
|
return {
|
|
@@ -38736,12 +38817,20 @@ function initSession(db) {
|
|
|
38736
38817
|
let created;
|
|
38737
38818
|
let ai_tool;
|
|
38738
38819
|
try {
|
|
38820
|
+
let publicKey = null;
|
|
38821
|
+
try {
|
|
38822
|
+
publicKey = generateSessionKeypair(id);
|
|
38823
|
+
} catch {
|
|
38824
|
+
}
|
|
38739
38825
|
await db.insert(sessions).values({
|
|
38740
38826
|
id,
|
|
38741
38827
|
title: args.title,
|
|
38742
38828
|
ai_tool: args.ai_tool ?? null,
|
|
38743
38829
|
created_at: now,
|
|
38744
|
-
updated_at: now
|
|
38830
|
+
updated_at: now,
|
|
38831
|
+
parent_session_id: args.parent_session_id ?? null,
|
|
38832
|
+
external_ref: args.external_ref ?? null,
|
|
38833
|
+
public_key: publicKey
|
|
38745
38834
|
});
|
|
38746
38835
|
session_id = id;
|
|
38747
38836
|
created = true;
|
|
@@ -38842,18 +38931,21 @@ var init_sessions = __esm({
|
|
|
38842
38931
|
init_schema();
|
|
38843
38932
|
init_time();
|
|
38844
38933
|
init_relay();
|
|
38934
|
+
init_signing();
|
|
38845
38935
|
}
|
|
38846
38936
|
});
|
|
38847
38937
|
|
|
38848
38938
|
// src/utils/hash.ts
|
|
38849
|
-
function computeContentHash(sessionId, role, content, createdAt, prevHash) {
|
|
38850
|
-
|
|
38939
|
+
function computeContentHash(sessionId, role, content, createdAt, prevHash, eventType) {
|
|
38940
|
+
const base = `${sessionId}|${role}|${content}|${createdAt}|${prevHash ?? ""}`;
|
|
38941
|
+
const input = eventType != null ? `${base}|${eventType}` : base;
|
|
38942
|
+
return (0, import_crypto5.createHash)("sha256").update(input).digest("hex");
|
|
38851
38943
|
}
|
|
38852
|
-
var
|
|
38944
|
+
var import_crypto5;
|
|
38853
38945
|
var init_hash = __esm({
|
|
38854
38946
|
"src/utils/hash.ts"() {
|
|
38855
38947
|
"use strict";
|
|
38856
|
-
|
|
38948
|
+
import_crypto5 = require("crypto");
|
|
38857
38949
|
}
|
|
38858
38950
|
});
|
|
38859
38951
|
|
|
@@ -39150,32 +39242,37 @@ async function writeDetections(db, session_id, message_id, content) {
|
|
|
39150
39242
|
function isFkError(e) {
|
|
39151
39243
|
return e?.message?.includes("FOREIGN KEY constraint failed") || e?.code === "SQLITE_CONSTRAINT_FOREIGNKEY" || e?.cause?.message?.includes("FOREIGN KEY constraint failed") || e?.cause?.extendedCode === "SQLITE_CONSTRAINT_FOREIGNKEY";
|
|
39152
39244
|
}
|
|
39245
|
+
async function insertMessage(db, session_id, role, content, event_type) {
|
|
39246
|
+
return db.transaction(async (tx) => {
|
|
39247
|
+
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);
|
|
39248
|
+
const ts = localISOString();
|
|
39249
|
+
const msgId = v4_default();
|
|
39250
|
+
const prevHash = last[0]?.content_hash ?? null;
|
|
39251
|
+
const hash = computeContentHash(session_id, role, content, ts, prevHash, event_type);
|
|
39252
|
+
await tx.insert(messages).values({
|
|
39253
|
+
id: msgId,
|
|
39254
|
+
session_id,
|
|
39255
|
+
role,
|
|
39256
|
+
content,
|
|
39257
|
+
created_at: ts,
|
|
39258
|
+
prev_hash: prevHash,
|
|
39259
|
+
content_hash: hash,
|
|
39260
|
+
event_type
|
|
39261
|
+
});
|
|
39262
|
+
await tx.update(sessions).set({ updated_at: ts }).where(eq(sessions.id, session_id));
|
|
39263
|
+
const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, session_id)).limit(1);
|
|
39264
|
+
return { id: msgId, now: ts, contentHash: hash, ai_tool: sessionRow?.ai_tool ?? null };
|
|
39265
|
+
});
|
|
39266
|
+
}
|
|
39153
39267
|
function logMessage(db) {
|
|
39154
39268
|
return async (args) => {
|
|
39269
|
+
const eventType = args.event_type ?? "message";
|
|
39155
39270
|
let id;
|
|
39156
39271
|
let now;
|
|
39157
39272
|
let contentHash;
|
|
39158
39273
|
let sessionAiTool;
|
|
39159
39274
|
try {
|
|
39160
|
-
const result = await db.
|
|
39161
|
-
const last = await tx.select({ content_hash: messages.content_hash }).from(messages).where(eq(messages.session_id, args.session_id)).orderBy(desc(messages.created_at), desc(sql`rowid`)).limit(1);
|
|
39162
|
-
const ts = localISOString();
|
|
39163
|
-
const msgId = v4_default();
|
|
39164
|
-
const prevHash = last[0]?.content_hash ?? null;
|
|
39165
|
-
const hash = computeContentHash(args.session_id, args.role, args.content, ts, prevHash);
|
|
39166
|
-
await tx.insert(messages).values({
|
|
39167
|
-
id: msgId,
|
|
39168
|
-
session_id: args.session_id,
|
|
39169
|
-
role: args.role,
|
|
39170
|
-
content: args.content,
|
|
39171
|
-
created_at: ts,
|
|
39172
|
-
prev_hash: prevHash,
|
|
39173
|
-
content_hash: hash
|
|
39174
|
-
});
|
|
39175
|
-
await tx.update(sessions).set({ updated_at: ts }).where(eq(sessions.id, args.session_id));
|
|
39176
|
-
const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, args.session_id)).limit(1);
|
|
39177
|
-
return { id: msgId, now: ts, contentHash: hash, ai_tool: sessionRow?.ai_tool ?? null };
|
|
39178
|
-
});
|
|
39275
|
+
const result = await insertMessage(db, args.session_id, args.role, args.content, eventType);
|
|
39179
39276
|
id = result.id;
|
|
39180
39277
|
now = result.now;
|
|
39181
39278
|
contentHash = result.contentHash;
|
|
@@ -39187,7 +39284,7 @@ function logMessage(db) {
|
|
|
39187
39284
|
throw e;
|
|
39188
39285
|
}
|
|
39189
39286
|
emitEvent({ event_type: "message_logged", timestamp: now, session: { id_prefix: args.session_id.slice(0, 8), ai_tool: sessionAiTool }, message: { role: args.role } });
|
|
39190
|
-
if (args.role === "user") {
|
|
39287
|
+
if (args.role === "user" && eventType === "message") {
|
|
39191
39288
|
setImmediate(() => writeDetections(db, args.session_id, id, args.content).catch(() => void 0));
|
|
39192
39289
|
}
|
|
39193
39290
|
return {
|
|
@@ -39195,15 +39292,45 @@ function logMessage(db) {
|
|
|
39195
39292
|
};
|
|
39196
39293
|
};
|
|
39197
39294
|
}
|
|
39295
|
+
function logToolCall(db) {
|
|
39296
|
+
return async (args) => {
|
|
39297
|
+
const toolCallId = args.tool_call_id ?? v4_default();
|
|
39298
|
+
const content = JSON.stringify({ tool_name: args.tool_name, input: args.tool_input, tool_call_id: toolCallId });
|
|
39299
|
+
let result;
|
|
39300
|
+
try {
|
|
39301
|
+
result = await insertMessage(db, args.session_id, "assistant", content, "tool_call");
|
|
39302
|
+
} catch (e) {
|
|
39303
|
+
if (isFkError(e)) {
|
|
39304
|
+
return { content: [{ type: "text", text: `Session not found: ${args.session_id}` }], isError: true };
|
|
39305
|
+
}
|
|
39306
|
+
throw e;
|
|
39307
|
+
}
|
|
39308
|
+
return {
|
|
39309
|
+
content: [{ type: "text", text: JSON.stringify({ message_id: result.id, tool_call_id: toolCallId, created_at: result.now, content_hash: result.contentHash }) }]
|
|
39310
|
+
};
|
|
39311
|
+
};
|
|
39312
|
+
}
|
|
39313
|
+
function logToolResult(db) {
|
|
39314
|
+
return async (args) => {
|
|
39315
|
+
const content = JSON.stringify({ tool_call_id: args.tool_call_id, output: args.output, exit_code: args.exit_code ?? null });
|
|
39316
|
+
let result;
|
|
39317
|
+
try {
|
|
39318
|
+
result = await insertMessage(db, args.session_id, "user", content, "tool_result");
|
|
39319
|
+
} catch (e) {
|
|
39320
|
+
if (isFkError(e)) {
|
|
39321
|
+
return { content: [{ type: "text", text: `Session not found: ${args.session_id}` }], isError: true };
|
|
39322
|
+
}
|
|
39323
|
+
throw e;
|
|
39324
|
+
}
|
|
39325
|
+
return {
|
|
39326
|
+
content: [{ type: "text", text: JSON.stringify({ message_id: result.id, tool_call_id: args.tool_call_id, created_at: result.now, content_hash: result.contentHash }) }]
|
|
39327
|
+
};
|
|
39328
|
+
};
|
|
39329
|
+
}
|
|
39198
39330
|
function logExchange(db) {
|
|
39199
39331
|
return async (args) => {
|
|
39200
|
-
let userId;
|
|
39201
|
-
let
|
|
39202
|
-
let userNow;
|
|
39203
|
-
let assistantNow;
|
|
39204
|
-
let userHash;
|
|
39205
|
-
let assistantHash;
|
|
39206
|
-
let sessionAiTool;
|
|
39332
|
+
let userId, assistantId, userNow, assistantNow;
|
|
39333
|
+
let userHash, assistantHash, sessionAiTool;
|
|
39207
39334
|
try {
|
|
39208
39335
|
const result = await db.transaction(async (tx) => {
|
|
39209
39336
|
const last = await tx.select({ content_hash: messages.content_hash }).from(messages).where(eq(messages.session_id, args.session_id)).orderBy(desc(messages.created_at), desc(sql`rowid`)).limit(1);
|
|
@@ -39212,8 +39339,8 @@ function logExchange(db) {
|
|
|
39212
39339
|
const aNow = localISOString();
|
|
39213
39340
|
const aId = v4_default();
|
|
39214
39341
|
const prevHash = last[0]?.content_hash ?? null;
|
|
39215
|
-
const uHash = computeContentHash(args.session_id, "user", args.user_content, uNow, prevHash);
|
|
39216
|
-
const aHash = computeContentHash(args.session_id, "assistant", args.assistant_content, aNow, uHash);
|
|
39342
|
+
const uHash = computeContentHash(args.session_id, "user", args.user_content, uNow, prevHash, "message");
|
|
39343
|
+
const aHash = computeContentHash(args.session_id, "assistant", args.assistant_content, aNow, uHash, "message");
|
|
39217
39344
|
await tx.insert(messages).values({
|
|
39218
39345
|
id: uId,
|
|
39219
39346
|
session_id: args.session_id,
|
|
@@ -39221,7 +39348,8 @@ function logExchange(db) {
|
|
|
39221
39348
|
content: args.user_content,
|
|
39222
39349
|
created_at: uNow,
|
|
39223
39350
|
prev_hash: prevHash,
|
|
39224
|
-
content_hash: uHash
|
|
39351
|
+
content_hash: uHash,
|
|
39352
|
+
event_type: "message"
|
|
39225
39353
|
});
|
|
39226
39354
|
await tx.insert(messages).values({
|
|
39227
39355
|
id: aId,
|
|
@@ -39230,7 +39358,8 @@ function logExchange(db) {
|
|
|
39230
39358
|
content: args.assistant_content,
|
|
39231
39359
|
created_at: aNow,
|
|
39232
39360
|
prev_hash: uHash,
|
|
39233
|
-
content_hash: aHash
|
|
39361
|
+
content_hash: aHash,
|
|
39362
|
+
event_type: "message"
|
|
39234
39363
|
});
|
|
39235
39364
|
await tx.update(sessions).set({ updated_at: aNow }).where(eq(sessions.id, args.session_id));
|
|
39236
39365
|
const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, args.session_id)).limit(1);
|
|
@@ -39284,6 +39413,7 @@ var init_messages = __esm({
|
|
|
39284
39413
|
// src/tools/verify.ts
|
|
39285
39414
|
function verifySession(db) {
|
|
39286
39415
|
return async (args) => {
|
|
39416
|
+
const [sessionRow] = await db.select().from(sessions).where(eq(sessions.id, args.session_id)).limit(1);
|
|
39287
39417
|
const rows = await db.select().from(messages).where(eq(messages.session_id, args.session_id)).orderBy(asc(messages.created_at), asc(sql`rowid`));
|
|
39288
39418
|
const chained = rows.filter((r) => r.content_hash !== null);
|
|
39289
39419
|
if (chained.length === 0) {
|
|
@@ -39305,7 +39435,7 @@ function verifySession(db) {
|
|
|
39305
39435
|
}]
|
|
39306
39436
|
};
|
|
39307
39437
|
}
|
|
39308
|
-
const expected = computeContentHash(row.session_id, row.role, row.content, row.created_at, row.prev_hash);
|
|
39438
|
+
const expected = computeContentHash(row.session_id, row.role, row.content, row.created_at, row.prev_hash, row.event_type ?? void 0);
|
|
39309
39439
|
if (row.content_hash !== expected) {
|
|
39310
39440
|
return {
|
|
39311
39441
|
content: [{
|
|
@@ -39315,10 +39445,23 @@ function verifySession(db) {
|
|
|
39315
39445
|
};
|
|
39316
39446
|
}
|
|
39317
39447
|
}
|
|
39448
|
+
let signature_valid = "unsigned";
|
|
39449
|
+
if (sessionRow?.public_key && sessionRow?.signature) {
|
|
39450
|
+
const finalHash = chained[chained.length - 1]?.content_hash ?? "";
|
|
39451
|
+
const firstCreatedAt = rows[0]?.created_at ?? "";
|
|
39452
|
+
signature_valid = verifySignature(
|
|
39453
|
+
sessionRow.public_key,
|
|
39454
|
+
sessionRow.signature,
|
|
39455
|
+
args.session_id,
|
|
39456
|
+
finalHash,
|
|
39457
|
+
rows.length,
|
|
39458
|
+
firstCreatedAt
|
|
39459
|
+
);
|
|
39460
|
+
}
|
|
39318
39461
|
return {
|
|
39319
39462
|
content: [{
|
|
39320
39463
|
type: "text",
|
|
39321
|
-
text: JSON.stringify({ valid: true, messages: rows.length, chained: chained.length })
|
|
39464
|
+
text: JSON.stringify({ valid: true, messages: rows.length, chained: chained.length, signature_valid })
|
|
39322
39465
|
}]
|
|
39323
39466
|
};
|
|
39324
39467
|
};
|
|
@@ -39329,6 +39472,7 @@ var init_verify = __esm({
|
|
|
39329
39472
|
init_drizzle_orm();
|
|
39330
39473
|
init_schema();
|
|
39331
39474
|
init_hash();
|
|
39475
|
+
init_signing();
|
|
39332
39476
|
}
|
|
39333
39477
|
});
|
|
39334
39478
|
|
|
@@ -39591,7 +39735,9 @@ function createServer(db) {
|
|
|
39591
39735
|
{
|
|
39592
39736
|
title: external_exports.string().describe('Descriptive session title, e.g. "Contract review \u2014 2026-05-08"'),
|
|
39593
39737
|
ai_tool: external_exports.string().optional().describe('AI tool name: "claude", "cursor", "windsurf", etc.'),
|
|
39594
|
-
limit: external_exports.number().int().positive().optional().describe("Number of recent messages to return (default: 10)")
|
|
39738
|
+
limit: external_exports.number().int().positive().optional().describe("Number of recent messages to return (default: 10)"),
|
|
39739
|
+
parent_session_id: external_exports.string().optional().describe("Parent session ID if this is a subagent session spawned by another session"),
|
|
39740
|
+
external_ref: external_exports.string().optional().describe('External ticket/PR reference, e.g. "jira:ENG-123" or "github:org/repo#456"')
|
|
39595
39741
|
},
|
|
39596
39742
|
initSession(db)
|
|
39597
39743
|
);
|
|
@@ -39600,7 +39746,9 @@ function createServer(db) {
|
|
|
39600
39746
|
"Create a new audit session or resume an existing one by title. Call this at the start of every conversation.",
|
|
39601
39747
|
{
|
|
39602
39748
|
title: external_exports.string().describe('Descriptive session title, e.g. "Contract review \u2014 2026-05-08"'),
|
|
39603
|
-
ai_tool: external_exports.string().optional().describe('AI tool name: "claude", "cursor", "windsurf", etc.')
|
|
39749
|
+
ai_tool: external_exports.string().optional().describe('AI tool name: "claude", "cursor", "windsurf", etc.'),
|
|
39750
|
+
parent_session_id: external_exports.string().optional().describe("Parent session ID if this is a subagent session"),
|
|
39751
|
+
external_ref: external_exports.string().optional().describe('External ticket/PR reference, e.g. "jira:ENG-123"')
|
|
39604
39752
|
},
|
|
39605
39753
|
startSession(db)
|
|
39606
39754
|
);
|
|
@@ -39614,6 +39762,28 @@ function createServer(db) {
|
|
|
39614
39762
|
},
|
|
39615
39763
|
logMessage(db)
|
|
39616
39764
|
);
|
|
39765
|
+
server.tool(
|
|
39766
|
+
"log_tool_call",
|
|
39767
|
+
"Record an AI tool invocation as a first-class audit event (event_type=tool_call). Call immediately before executing a tool. Returns tool_call_id to pass to log_tool_result.",
|
|
39768
|
+
{
|
|
39769
|
+
session_id: external_exports.string().describe("Session ID"),
|
|
39770
|
+
tool_name: external_exports.string().describe('Name of the tool being called (e.g. "Edit", "Bash", "WebFetch")'),
|
|
39771
|
+
tool_input: external_exports.record(external_exports.unknown()).describe("Tool input parameters as an object"),
|
|
39772
|
+
tool_call_id: external_exports.string().optional().describe("Optional ID to link this call to its result. Generated automatically if omitted.")
|
|
39773
|
+
},
|
|
39774
|
+
logToolCall(db)
|
|
39775
|
+
);
|
|
39776
|
+
server.tool(
|
|
39777
|
+
"log_tool_result",
|
|
39778
|
+
"Record the result of a tool call as a first-class audit event (event_type=tool_result). Call immediately after receiving tool output. Links back to log_tool_call via tool_call_id.",
|
|
39779
|
+
{
|
|
39780
|
+
session_id: external_exports.string().describe("Session ID"),
|
|
39781
|
+
tool_call_id: external_exports.string().describe("tool_call_id returned by log_tool_call"),
|
|
39782
|
+
output: external_exports.string().describe("Tool output (stdout, file content, API response, etc.)"),
|
|
39783
|
+
exit_code: external_exports.number().int().optional().describe("Exit code for shell tools (0 = success)")
|
|
39784
|
+
},
|
|
39785
|
+
logToolResult(db)
|
|
39786
|
+
);
|
|
39617
39787
|
server.tool(
|
|
39618
39788
|
"log_exchange",
|
|
39619
39789
|
"Record a user+assistant exchange from historical or batch imports only. Do NOT use for live conversations \u2014 both timestamps are captured at the same instant with no real gap. For live sessions always call log_message twice: once for the user message, once for the assistant response.",
|
|
@@ -39706,24 +39876,24 @@ __export(setup_exports, {
|
|
|
39706
39876
|
runSetup: () => runSetup
|
|
39707
39877
|
});
|
|
39708
39878
|
function configPath(...parts) {
|
|
39709
|
-
return (0,
|
|
39879
|
+
return (0, import_path4.join)((0, import_os4.homedir)(), ...parts);
|
|
39710
39880
|
}
|
|
39711
39881
|
function readJson(filePath) {
|
|
39712
|
-
if (!(0,
|
|
39882
|
+
if (!(0, import_fs4.existsSync)(filePath))
|
|
39713
39883
|
return {};
|
|
39714
39884
|
try {
|
|
39715
|
-
return JSON.parse((0,
|
|
39885
|
+
return JSON.parse((0, import_fs4.readFileSync)(filePath, "utf8"));
|
|
39716
39886
|
} catch {
|
|
39717
39887
|
return {};
|
|
39718
39888
|
}
|
|
39719
39889
|
}
|
|
39720
39890
|
function writeJson(filePath, data) {
|
|
39721
|
-
(0,
|
|
39722
|
-
(0,
|
|
39891
|
+
(0, import_fs4.mkdirSync)((0, import_path4.dirname)(filePath), { recursive: true });
|
|
39892
|
+
(0, import_fs4.writeFileSync)(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
39723
39893
|
}
|
|
39724
39894
|
function configureTool(name, filePath) {
|
|
39725
|
-
const dir = (0,
|
|
39726
|
-
if (!(0,
|
|
39895
|
+
const dir = (0, import_path4.dirname)(filePath);
|
|
39896
|
+
if (!(0, import_fs4.existsSync)(dir) && name !== "Claude Code") {
|
|
39727
39897
|
return { tool: name, status: "skipped" };
|
|
39728
39898
|
}
|
|
39729
39899
|
try {
|
|
@@ -39744,7 +39914,7 @@ function configureClaudeCode() {
|
|
|
39744
39914
|
try {
|
|
39745
39915
|
(0, import_child_process.execSync)("claude mcp add chron -- npx -y chron-mcp", { stdio: "pipe" });
|
|
39746
39916
|
} catch {
|
|
39747
|
-
const result = configureTool("Claude Code", (0,
|
|
39917
|
+
const result = configureTool("Claude Code", (0, import_path4.join)((0, import_os4.homedir)(), ".claude", "settings.json"));
|
|
39748
39918
|
if (result.status === "error")
|
|
39749
39919
|
return result;
|
|
39750
39920
|
}
|
|
@@ -39752,13 +39922,13 @@ function configureClaudeCode() {
|
|
|
39752
39922
|
return { tool: "Claude Code", status: "added" };
|
|
39753
39923
|
}
|
|
39754
39924
|
function installClaudeCodeHook() {
|
|
39755
|
-
const skillSrc = (0,
|
|
39756
|
-
const skillDst = (0,
|
|
39757
|
-
if ((0,
|
|
39758
|
-
(0,
|
|
39759
|
-
(0,
|
|
39925
|
+
const skillSrc = (0, import_path4.join)(__dirname, "..", "skills", "chron.skill.md");
|
|
39926
|
+
const skillDst = (0, import_path4.join)((0, import_os4.homedir)(), ".chron", "chron.skill.md");
|
|
39927
|
+
if ((0, import_fs4.existsSync)(skillSrc)) {
|
|
39928
|
+
(0, import_fs4.mkdirSync)((0, import_path4.dirname)(skillDst), { recursive: true });
|
|
39929
|
+
(0, import_fs4.copyFileSync)(skillSrc, skillDst);
|
|
39760
39930
|
}
|
|
39761
|
-
const settingsPath = (0,
|
|
39931
|
+
const settingsPath = (0, import_path4.join)((0, import_os4.homedir)(), ".claude", "settings.json");
|
|
39762
39932
|
const settings = readJson(settingsPath);
|
|
39763
39933
|
if (!settings.hooks)
|
|
39764
39934
|
settings.hooks = {};
|
|
@@ -39782,13 +39952,13 @@ async function runSetup() {
|
|
|
39782
39952
|
}
|
|
39783
39953
|
return results.filter((r) => r.status !== "skipped");
|
|
39784
39954
|
}
|
|
39785
|
-
var
|
|
39955
|
+
var import_fs4, import_os4, import_path4, import_child_process, CHRON_ENTRY, TOOLS;
|
|
39786
39956
|
var init_setup = __esm({
|
|
39787
39957
|
"src/setup.ts"() {
|
|
39788
39958
|
"use strict";
|
|
39789
|
-
|
|
39790
|
-
|
|
39791
|
-
|
|
39959
|
+
import_fs4 = require("fs");
|
|
39960
|
+
import_os4 = require("os");
|
|
39961
|
+
import_path4 = require("path");
|
|
39792
39962
|
import_child_process = require("child_process");
|
|
39793
39963
|
CHRON_ENTRY = {
|
|
39794
39964
|
command: "npx",
|
|
@@ -39797,11 +39967,11 @@ var init_setup = __esm({
|
|
|
39797
39967
|
TOOLS = [
|
|
39798
39968
|
{
|
|
39799
39969
|
name: "Claude Desktop",
|
|
39800
|
-
path: process.platform === "win32" ? (0,
|
|
39970
|
+
path: process.platform === "win32" ? (0, import_path4.join)(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : configPath("Library", "Application Support", "Claude", "claude_desktop_config.json")
|
|
39801
39971
|
},
|
|
39802
39972
|
{
|
|
39803
39973
|
name: "Cursor",
|
|
39804
|
-
path: process.platform === "win32" ? (0,
|
|
39974
|
+
path: process.platform === "win32" ? (0, import_path4.join)(process.env.APPDATA ?? "", "Cursor", "User", "globalStorage", "cursor.mcp", "mcp.json") : configPath(".cursor", "mcp.json")
|
|
39805
39975
|
},
|
|
39806
39976
|
{
|
|
39807
39977
|
name: "Windsurf",
|
|
@@ -59989,7 +60159,7 @@ var require_view = __commonJS({
|
|
|
59989
60159
|
var dirname3 = path.dirname;
|
|
59990
60160
|
var basename = path.basename;
|
|
59991
60161
|
var extname = path.extname;
|
|
59992
|
-
var
|
|
60162
|
+
var join6 = path.join;
|
|
59993
60163
|
var resolve = path.resolve;
|
|
59994
60164
|
module2.exports = View2;
|
|
59995
60165
|
function View2(name, options) {
|
|
@@ -60037,12 +60207,12 @@ var require_view = __commonJS({
|
|
|
60037
60207
|
};
|
|
60038
60208
|
View2.prototype.resolve = function resolve2(dir, file) {
|
|
60039
60209
|
var ext = this.ext;
|
|
60040
|
-
var path2 =
|
|
60210
|
+
var path2 = join6(dir, file);
|
|
60041
60211
|
var stat = tryStat(path2);
|
|
60042
60212
|
if (stat && stat.isFile()) {
|
|
60043
60213
|
return path2;
|
|
60044
60214
|
}
|
|
60045
|
-
path2 =
|
|
60215
|
+
path2 = join6(dir, basename(file, ext), "index" + ext);
|
|
60046
60216
|
stat = tryStat(path2);
|
|
60047
60217
|
if (stat && stat.isFile()) {
|
|
60048
60218
|
return path2;
|
|
@@ -61109,7 +61279,7 @@ var require_send = __commonJS({
|
|
|
61109
61279
|
var Stream2 = require("stream");
|
|
61110
61280
|
var util2 = require("util");
|
|
61111
61281
|
var extname = path.extname;
|
|
61112
|
-
var
|
|
61282
|
+
var join6 = path.join;
|
|
61113
61283
|
var normalize = path.normalize;
|
|
61114
61284
|
var resolve = path.resolve;
|
|
61115
61285
|
var sep = path.sep;
|
|
@@ -61328,7 +61498,7 @@ var require_send = __commonJS({
|
|
|
61328
61498
|
return res;
|
|
61329
61499
|
}
|
|
61330
61500
|
parts = path2.split(sep);
|
|
61331
|
-
path2 = normalize(
|
|
61501
|
+
path2 = normalize(join6(root, path2));
|
|
61332
61502
|
} else {
|
|
61333
61503
|
if (UP_PATH_REGEXP.test(path2)) {
|
|
61334
61504
|
debug('malicious path "%s"', path2);
|
|
@@ -61469,7 +61639,7 @@ var require_send = __commonJS({
|
|
|
61469
61639
|
return self.onStatError(err);
|
|
61470
61640
|
return self.error(404);
|
|
61471
61641
|
}
|
|
61472
|
-
var p =
|
|
61642
|
+
var p = join6(path2, self._index[i]);
|
|
61473
61643
|
debug('stat "%s"', p);
|
|
61474
61644
|
fs.stat(p, function(err2, stat) {
|
|
61475
61645
|
if (err2)
|
|
@@ -68917,8 +69087,8 @@ var StdioServerTransport = class {
|
|
|
68917
69087
|
};
|
|
68918
69088
|
|
|
68919
69089
|
// src/index.ts
|
|
68920
|
-
var
|
|
68921
|
-
var
|
|
69090
|
+
var import_os5 = require("os");
|
|
69091
|
+
var import_path5 = require("path");
|
|
68922
69092
|
init_db2();
|
|
68923
69093
|
init_server3();
|
|
68924
69094
|
init_package();
|
|
@@ -68928,7 +69098,7 @@ if (process.argv[2] === "--version" || process.argv[2] === "-v") {
|
|
|
68928
69098
|
process.exit(0);
|
|
68929
69099
|
}
|
|
68930
69100
|
async function main() {
|
|
68931
|
-
const dbPath = process.env.CHRON_DB_PATH ?? (0,
|
|
69101
|
+
const dbPath = process.env.CHRON_DB_PATH ?? (0, import_path5.join)((0, import_os5.homedir)(), ".chron", "chron.db");
|
|
68932
69102
|
if (process.stdin.isTTY) {
|
|
68933
69103
|
process.stdout.write(`chron-mcp ${version4}
|
|
68934
69104
|
|