chron-mcp 0.1.20 → 0.1.24
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 +1 -1
- package/dist/cli/index.js +666 -226
- package/dist/index.js +133 -14
- package/package.json +1 -1
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 join9(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 = join9;
|
|
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 readFileSync6 = (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: readFileSync6,
|
|
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: readFileSync6 } = 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 = readFileSync6(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 = readFileSync6(LDD_PATH);
|
|
2972
2972
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
|
2973
2973
|
if (versionMatch) {
|
|
2974
2974
|
cachedVersionFilesystem = versionMatch[1];
|
|
@@ -2995,33 +2995,33 @@ var require_detect_libc = __commonJS({
|
|
|
2995
2995
|
}
|
|
2996
2996
|
return null;
|
|
2997
2997
|
};
|
|
2998
|
-
var
|
|
2999
|
-
let
|
|
2998
|
+
var version4 = async () => {
|
|
2999
|
+
let version5 = null;
|
|
3000
3000
|
if (isLinux()) {
|
|
3001
|
-
|
|
3002
|
-
if (!
|
|
3003
|
-
|
|
3001
|
+
version5 = await versionFromFilesystem();
|
|
3002
|
+
if (!version5) {
|
|
3003
|
+
version5 = versionFromReport();
|
|
3004
3004
|
}
|
|
3005
|
-
if (!
|
|
3005
|
+
if (!version5) {
|
|
3006
3006
|
const out = await safeCommand();
|
|
3007
|
-
|
|
3007
|
+
version5 = versionFromCommand(out);
|
|
3008
3008
|
}
|
|
3009
3009
|
}
|
|
3010
|
-
return
|
|
3010
|
+
return version5;
|
|
3011
3011
|
};
|
|
3012
3012
|
var versionSync = () => {
|
|
3013
|
-
let
|
|
3013
|
+
let version5 = null;
|
|
3014
3014
|
if (isLinux()) {
|
|
3015
|
-
|
|
3016
|
-
if (!
|
|
3017
|
-
|
|
3015
|
+
version5 = versionFromFilesystemSync();
|
|
3016
|
+
if (!version5) {
|
|
3017
|
+
version5 = versionFromReport();
|
|
3018
3018
|
}
|
|
3019
|
-
if (!
|
|
3019
|
+
if (!version5) {
|
|
3020
3020
|
const out = safeCommandSync();
|
|
3021
|
-
|
|
3021
|
+
version5 = versionFromCommand(out);
|
|
3022
3022
|
}
|
|
3023
3023
|
}
|
|
3024
|
-
return
|
|
3024
|
+
return version5;
|
|
3025
3025
|
};
|
|
3026
3026
|
module2.exports = {
|
|
3027
3027
|
GLIBC,
|
|
@@ -3030,7 +3030,7 @@ var require_detect_libc = __commonJS({
|
|
|
3030
3030
|
familySync,
|
|
3031
3031
|
isNonGlibcLinux,
|
|
3032
3032
|
isNonGlibcLinuxSync,
|
|
3033
|
-
version:
|
|
3033
|
+
version: version4,
|
|
3034
3034
|
versionSync
|
|
3035
3035
|
};
|
|
3036
3036
|
}
|
|
@@ -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: createHash6 } = 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 = createHash6("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: createHash6 } = require("crypto");
|
|
7233
7233
|
var extension2 = require_extension();
|
|
7234
7234
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
7235
7235
|
var subprotocol2 = require_subprotocol();
|
|
@@ -7429,7 +7429,7 @@ var require_websocket_server = __commonJS({
|
|
|
7429
7429
|
socket.on("error", socketOnError);
|
|
7430
7430
|
const key = req.headers["sec-websocket-key"];
|
|
7431
7431
|
const upgrade = req.headers.upgrade;
|
|
7432
|
-
const
|
|
7432
|
+
const version4 = +req.headers["sec-websocket-version"];
|
|
7433
7433
|
if (req.method !== "GET") {
|
|
7434
7434
|
const message = "Invalid HTTP method";
|
|
7435
7435
|
abortHandshakeOrEmitwsClientError(this, req, socket, 405, message);
|
|
@@ -7445,7 +7445,7 @@ var require_websocket_server = __commonJS({
|
|
|
7445
7445
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
|
7446
7446
|
return;
|
|
7447
7447
|
}
|
|
7448
|
-
if (
|
|
7448
|
+
if (version4 !== 13 && version4 !== 8) {
|
|
7449
7449
|
const message = "Missing or invalid Sec-WebSocket-Version header";
|
|
7450
7450
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message, {
|
|
7451
7451
|
"Sec-WebSocket-Version": "13, 8"
|
|
@@ -7489,7 +7489,7 @@ var require_websocket_server = __commonJS({
|
|
|
7489
7489
|
}
|
|
7490
7490
|
if (this.options.verifyClient) {
|
|
7491
7491
|
const info = {
|
|
7492
|
-
origin: req.headers[`${
|
|
7492
|
+
origin: req.headers[`${version4 === 8 ? "sec-websocket-origin" : "origin"}`],
|
|
7493
7493
|
secure: !!(req.socket.authorized || req.socket.encrypted),
|
|
7494
7494
|
req
|
|
7495
7495
|
};
|
|
@@ -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 = createHash6("sha1").update(key + GUID).digest("base64");
|
|
7542
7542
|
const headers = [
|
|
7543
7543
|
"HTTP/1.1 101 Switching Protocols",
|
|
7544
7544
|
"Upgrade: websocket",
|
|
@@ -11306,7 +11306,7 @@ var init_lib_esm = __esm({
|
|
|
11306
11306
|
});
|
|
11307
11307
|
|
|
11308
11308
|
// node_modules/@libsql/client/lib-esm/hrana.js
|
|
11309
|
-
async function executeHranaBatch(mode,
|
|
11309
|
+
async function executeHranaBatch(mode, version4, batch, hranaStmts, disableForeignKeys = false) {
|
|
11310
11310
|
if (disableForeignKeys) {
|
|
11311
11311
|
batch.step().run("PRAGMA foreign_keys=off");
|
|
11312
11312
|
}
|
|
@@ -11315,7 +11315,7 @@ async function executeHranaBatch(mode, version3, batch, hranaStmts, disableForei
|
|
|
11315
11315
|
let lastStep = beginStep;
|
|
11316
11316
|
const stmtPromises = hranaStmts.map((hranaStmt) => {
|
|
11317
11317
|
const stmtStep = batch.step().condition(BatchCond.ok(lastStep));
|
|
11318
|
-
if (
|
|
11318
|
+
if (version4 >= 3) {
|
|
11319
11319
|
stmtStep.condition(BatchCond.not(BatchCond.isAutocommit(batch)));
|
|
11320
11320
|
}
|
|
11321
11321
|
const stmtPromise = stmtStep.query(hranaStmt);
|
|
@@ -11323,7 +11323,7 @@ async function executeHranaBatch(mode, version3, batch, hranaStmts, disableForei
|
|
|
11323
11323
|
return stmtPromise;
|
|
11324
11324
|
});
|
|
11325
11325
|
const commitStep = batch.step().condition(BatchCond.ok(lastStep));
|
|
11326
|
-
if (
|
|
11326
|
+
if (version4 >= 3) {
|
|
11327
11327
|
commitStep.condition(BatchCond.not(BatchCond.isAutocommit(batch)));
|
|
11328
11328
|
}
|
|
11329
11329
|
const commitPromise = commitStep.run("COMMIT");
|
|
@@ -11426,9 +11426,9 @@ var init_hrana = __esm({
|
|
|
11426
11426
|
// BEGIN statement yet.
|
|
11427
11427
|
#started;
|
|
11428
11428
|
/** @private */
|
|
11429
|
-
constructor(mode,
|
|
11429
|
+
constructor(mode, version4) {
|
|
11430
11430
|
this.#mode = mode;
|
|
11431
|
-
this.#version =
|
|
11431
|
+
this.#version = version4;
|
|
11432
11432
|
this.#started = void 0;
|
|
11433
11433
|
}
|
|
11434
11434
|
execute(stmt) {
|
|
@@ -11859,10 +11859,10 @@ var init_ws = __esm({
|
|
|
11859
11859
|
return stmt;
|
|
11860
11860
|
});
|
|
11861
11861
|
const hranaStmts = normalizedStmts.map(stmtToHrana);
|
|
11862
|
-
const
|
|
11862
|
+
const version4 = await streamState.conn.client.getVersion();
|
|
11863
11863
|
streamState.conn.sqlCache.apply(hranaStmts);
|
|
11864
|
-
const batch = streamState.stream.batch(
|
|
11865
|
-
const resultsPromise = executeHranaBatch(mode,
|
|
11864
|
+
const batch = streamState.stream.batch(version4 >= 3);
|
|
11865
|
+
const resultsPromise = executeHranaBatch(mode, version4, batch, hranaStmts);
|
|
11866
11866
|
const results = await resultsPromise;
|
|
11867
11867
|
return results;
|
|
11868
11868
|
} catch (e) {
|
|
@@ -11877,9 +11877,9 @@ var init_ws = __esm({
|
|
|
11877
11877
|
const streamState = await this.#openStream();
|
|
11878
11878
|
try {
|
|
11879
11879
|
const hranaStmts = stmts.map(stmtToHrana);
|
|
11880
|
-
const
|
|
11881
|
-
const batch = streamState.stream.batch(
|
|
11882
|
-
const resultsPromise = executeHranaBatch("deferred",
|
|
11880
|
+
const version4 = await streamState.conn.client.getVersion();
|
|
11881
|
+
const batch = streamState.stream.batch(version4 >= 3);
|
|
11882
|
+
const resultsPromise = executeHranaBatch("deferred", version4, batch, hranaStmts, true);
|
|
11883
11883
|
const results = await resultsPromise;
|
|
11884
11884
|
return results;
|
|
11885
11885
|
} catch (e) {
|
|
@@ -11893,8 +11893,8 @@ var init_ws = __esm({
|
|
|
11893
11893
|
return this.limit(async () => {
|
|
11894
11894
|
const streamState = await this.#openStream();
|
|
11895
11895
|
try {
|
|
11896
|
-
const
|
|
11897
|
-
return new WsTransaction(this, streamState, mode,
|
|
11896
|
+
const version4 = await streamState.conn.client.getVersion();
|
|
11897
|
+
return new WsTransaction(this, streamState, mode, version4);
|
|
11898
11898
|
} catch (e) {
|
|
11899
11899
|
this._closeStream(streamState);
|
|
11900
11900
|
throw mapHranaError(e);
|
|
@@ -12001,8 +12001,8 @@ var init_ws = __esm({
|
|
|
12001
12001
|
this.#futureConnState = void 0;
|
|
12002
12002
|
}
|
|
12003
12003
|
const next = this.#openConn();
|
|
12004
|
-
const
|
|
12005
|
-
next.useSqlCache =
|
|
12004
|
+
const version4 = await next.client.getVersion();
|
|
12005
|
+
next.useSqlCache = version4 >= 2;
|
|
12006
12006
|
if (next.useSqlCache) {
|
|
12007
12007
|
next.sqlCache.capacity = sqlCacheCapacity;
|
|
12008
12008
|
}
|
|
@@ -12034,8 +12034,8 @@ var init_ws = __esm({
|
|
|
12034
12034
|
#client;
|
|
12035
12035
|
#streamState;
|
|
12036
12036
|
/** @private */
|
|
12037
|
-
constructor(client, state, mode,
|
|
12038
|
-
super(mode,
|
|
12037
|
+
constructor(client, state, mode, version4) {
|
|
12038
|
+
super(mode, version4);
|
|
12039
12039
|
this.#client = client;
|
|
12040
12040
|
this.#streamState = state;
|
|
12041
12041
|
}
|
|
@@ -12152,14 +12152,14 @@ var init_http = __esm({
|
|
|
12152
12152
|
return stmt;
|
|
12153
12153
|
});
|
|
12154
12154
|
const hranaStmts = normalizedStmts.map(stmtToHrana);
|
|
12155
|
-
const
|
|
12155
|
+
const version4 = await this.#client.getVersion();
|
|
12156
12156
|
let resultsPromise;
|
|
12157
12157
|
const stream = this.#client.openStream();
|
|
12158
12158
|
try {
|
|
12159
12159
|
const sqlCache = new SqlCache(stream, sqlCacheCapacity2);
|
|
12160
12160
|
sqlCache.apply(hranaStmts);
|
|
12161
12161
|
const batch = stream.batch(false);
|
|
12162
|
-
resultsPromise = executeHranaBatch(mode,
|
|
12162
|
+
resultsPromise = executeHranaBatch(mode, version4, batch, hranaStmts);
|
|
12163
12163
|
} finally {
|
|
12164
12164
|
stream.closeGracefully();
|
|
12165
12165
|
}
|
|
@@ -12174,12 +12174,12 @@ var init_http = __esm({
|
|
|
12174
12174
|
return this.limit(async () => {
|
|
12175
12175
|
try {
|
|
12176
12176
|
const hranaStmts = stmts.map(stmtToHrana);
|
|
12177
|
-
const
|
|
12177
|
+
const version4 = await this.#client.getVersion();
|
|
12178
12178
|
let resultsPromise;
|
|
12179
12179
|
const stream = this.#client.openStream();
|
|
12180
12180
|
try {
|
|
12181
12181
|
const batch = stream.batch(false);
|
|
12182
|
-
resultsPromise = executeHranaBatch("deferred",
|
|
12182
|
+
resultsPromise = executeHranaBatch("deferred", version4, batch, hranaStmts, true);
|
|
12183
12183
|
} finally {
|
|
12184
12184
|
stream.closeGracefully();
|
|
12185
12185
|
}
|
|
@@ -12193,8 +12193,8 @@ var init_http = __esm({
|
|
|
12193
12193
|
async transaction(mode = "write") {
|
|
12194
12194
|
return this.limit(async () => {
|
|
12195
12195
|
try {
|
|
12196
|
-
const
|
|
12197
|
-
return new HttpTransaction(this.#client.openStream(), mode,
|
|
12196
|
+
const version4 = await this.#client.getVersion();
|
|
12197
|
+
return new HttpTransaction(this.#client.openStream(), mode, version4);
|
|
12198
12198
|
} catch (e) {
|
|
12199
12199
|
throw mapHranaError(e);
|
|
12200
12200
|
}
|
|
@@ -12240,8 +12240,8 @@ var init_http = __esm({
|
|
|
12240
12240
|
#stream;
|
|
12241
12241
|
#sqlCache;
|
|
12242
12242
|
/** @private */
|
|
12243
|
-
constructor(stream, mode,
|
|
12244
|
-
super(mode,
|
|
12243
|
+
constructor(stream, mode, version4) {
|
|
12244
|
+
super(mode, version4);
|
|
12245
12245
|
this.#stream = stream;
|
|
12246
12246
|
this.#sqlCache = new SqlCache(stream, sqlCacheCapacity2);
|
|
12247
12247
|
}
|
|
@@ -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((join9) => join9.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((join9) => join9.alias === tableName)) {
|
|
15145
15145
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
15146
15146
|
}
|
|
15147
15147
|
if (typeof on === "function") {
|
|
@@ -16543,7 +16543,8 @@ var init_schema = __esm({
|
|
|
16543
16543
|
parent_session_id: text("parent_session_id"),
|
|
16544
16544
|
external_ref: text("external_ref"),
|
|
16545
16545
|
public_key: text("public_key"),
|
|
16546
|
-
signature: text("signature")
|
|
16546
|
+
signature: text("signature"),
|
|
16547
|
+
metadata: text("metadata")
|
|
16547
16548
|
});
|
|
16548
16549
|
messages = sqliteTable("messages", {
|
|
16549
16550
|
id: text("id").primaryKey(),
|
|
@@ -16610,6 +16611,9 @@ async function initDb(dbPath2) {
|
|
|
16610
16611
|
if (!sessCols.includes("signature")) {
|
|
16611
16612
|
await client.execute("ALTER TABLE sessions ADD COLUMN signature TEXT");
|
|
16612
16613
|
}
|
|
16614
|
+
if (!sessCols.includes("metadata")) {
|
|
16615
|
+
await client.execute("ALTER TABLE sessions ADD COLUMN metadata TEXT");
|
|
16616
|
+
}
|
|
16613
16617
|
return drizzle(client, { schema: schema_exports });
|
|
16614
16618
|
}
|
|
16615
16619
|
var import_os, import_fs, import_path, CREATE_SQL;
|
|
@@ -16632,7 +16636,8 @@ var init_db2 = __esm({
|
|
|
16632
16636
|
parent_session_id TEXT,
|
|
16633
16637
|
external_ref TEXT,
|
|
16634
16638
|
public_key TEXT,
|
|
16635
|
-
signature TEXT
|
|
16639
|
+
signature TEXT,
|
|
16640
|
+
metadata TEXT
|
|
16636
16641
|
)`,
|
|
16637
16642
|
`CREATE TABLE IF NOT EXISTS messages (
|
|
16638
16643
|
id TEXT PRIMARY KEY,
|
|
@@ -16678,16 +16683,23 @@ function fmtTimestamp(iso) {
|
|
|
16678
16683
|
const tz = !m[3] || m[3] === "Z" ? "+00:00" : m[3];
|
|
16679
16684
|
return `${m[1]} ${m[2]} ${tz}`;
|
|
16680
16685
|
}
|
|
16681
|
-
async function printList(limit) {
|
|
16686
|
+
async function printList(limit, refFilter) {
|
|
16682
16687
|
const db = await initDb();
|
|
16683
|
-
|
|
16688
|
+
let rows = await db.select({
|
|
16684
16689
|
id: sessions.id,
|
|
16685
16690
|
title: sessions.title,
|
|
16686
16691
|
updated_at: sessions.updated_at,
|
|
16692
|
+
external_ref: sessions.external_ref,
|
|
16687
16693
|
message_count: sql`count(${messages.id})`
|
|
16688
|
-
}).from(sessions).leftJoin(messages, eq(messages.session_id, sessions.id)).groupBy(sessions.id).orderBy(desc(sessions.updated_at)).limit(limit);
|
|
16694
|
+
}).from(sessions).leftJoin(messages, eq(messages.session_id, sessions.id)).groupBy(sessions.id).orderBy(desc(sessions.updated_at)).limit(refFilter ? 1e4 : limit);
|
|
16695
|
+
if (refFilter) {
|
|
16696
|
+
rows = rows.filter((r) => r.external_ref?.startsWith(refFilter) ?? false);
|
|
16697
|
+
rows = rows.slice(0, limit);
|
|
16698
|
+
}
|
|
16689
16699
|
if (rows.length === 0) {
|
|
16690
|
-
|
|
16700
|
+
const msg = refFilter ? `No sessions linked to ref: ${refFilter}
|
|
16701
|
+
` : "No sessions found.\n";
|
|
16702
|
+
process.stdout.write(msg);
|
|
16691
16703
|
return;
|
|
16692
16704
|
}
|
|
16693
16705
|
process.stdout.write("\n");
|
|
@@ -16695,8 +16707,9 @@ async function printList(limit) {
|
|
|
16695
16707
|
const date = fmtDate(row.updated_at);
|
|
16696
16708
|
const prefix = row.id.slice(0, 8);
|
|
16697
16709
|
const count = String(row.message_count).padStart(3);
|
|
16710
|
+
const ref = row.external_ref ? ` ${DIM}[${row.external_ref}]${RESET}` : "";
|
|
16698
16711
|
process.stdout.write(
|
|
16699
|
-
` ${DIM}${date}${RESET} ${CYAN}${prefix}${RESET} ${BOLD}${count} msgs${RESET} ${row.title}
|
|
16712
|
+
` ${DIM}${date}${RESET} ${CYAN}${prefix}${RESET} ${BOLD}${count} msgs${RESET} ${row.title}${ref}
|
|
16700
16713
|
`
|
|
16701
16714
|
);
|
|
16702
16715
|
}
|
|
@@ -16726,25 +16739,66 @@ ${BOLD}${session.title}${RESET}
|
|
|
16726
16739
|
`);
|
|
16727
16740
|
for (const msg of msgs) {
|
|
16728
16741
|
const ts = fmtTimestamp(msg.created_at);
|
|
16729
|
-
|
|
16730
|
-
|
|
16742
|
+
if (msg.event_type === "code_change") {
|
|
16743
|
+
try {
|
|
16744
|
+
const payload = JSON.parse(msg.content);
|
|
16745
|
+
const opColor = payload.operation === "delete" ? YELLOW : payload.operation === "create" ? GREEN : CYAN;
|
|
16746
|
+
process.stdout.write(`${DIM}[${ts}]${RESET} ${BOLD}${MAGENTA}code_change${RESET} ${opColor}${payload.operation ?? ""}${RESET} ${payload.file_path ?? ""}
|
|
16747
|
+
`);
|
|
16748
|
+
if (payload.diff) {
|
|
16749
|
+
const lines = payload.diff.split("\n").slice(0, 20);
|
|
16750
|
+
for (const line of lines) {
|
|
16751
|
+
const color = line.startsWith("+") ? GREEN : line.startsWith("-") ? YELLOW : DIM;
|
|
16752
|
+
process.stdout.write(`${color}${line}${RESET}
|
|
16731
16753
|
`);
|
|
16732
|
-
|
|
16754
|
+
}
|
|
16755
|
+
const total = payload.diff.split("\n").length;
|
|
16756
|
+
if (total > 20)
|
|
16757
|
+
process.stdout.write(`${DIM} \u2026 ${total - 20} more lines${RESET}
|
|
16758
|
+
`);
|
|
16759
|
+
}
|
|
16760
|
+
process.stdout.write("\n");
|
|
16761
|
+
} catch {
|
|
16762
|
+
process.stdout.write(`${DIM}[${ts}]${RESET} ${BOLD}${MAGENTA}code_change${RESET}
|
|
16763
|
+
${msg.content}
|
|
16764
|
+
|
|
16765
|
+
`);
|
|
16766
|
+
}
|
|
16767
|
+
} else if (msg.event_type === "tool_call" || msg.event_type === "tool_result") {
|
|
16768
|
+
try {
|
|
16769
|
+
const payload = JSON.parse(msg.content);
|
|
16770
|
+
const label = msg.event_type === "tool_call" ? `${BOLD}${CYAN}tool_call${RESET} ${payload.tool_name ?? ""}` : `${BOLD}${DIM}tool_result${RESET} id:${payload.tool_call_id?.slice(0, 8) ?? ""}`;
|
|
16771
|
+
process.stdout.write(`${DIM}[${ts}]${RESET} ${label}
|
|
16733
16772
|
|
|
16734
16773
|
`);
|
|
16774
|
+
} catch {
|
|
16775
|
+
process.stdout.write(`${DIM}[${ts}]${RESET} ${BOLD}${CYAN}${msg.event_type}${RESET}
|
|
16776
|
+
${msg.content}
|
|
16777
|
+
|
|
16778
|
+
`);
|
|
16779
|
+
}
|
|
16780
|
+
} else {
|
|
16781
|
+
const roleColor = msg.role === "user" ? YELLOW : CYAN;
|
|
16782
|
+
process.stdout.write(`${DIM}[${ts}]${RESET} ${BOLD}${roleColor}${msg.role}${RESET}
|
|
16783
|
+
`);
|
|
16784
|
+
process.stdout.write(`${msg.content}
|
|
16785
|
+
|
|
16786
|
+
`);
|
|
16787
|
+
}
|
|
16735
16788
|
}
|
|
16736
16789
|
}
|
|
16737
16790
|
async function runHistory(args2) {
|
|
16738
16791
|
const limitArg = args2.find((a) => a.startsWith("--limit="));
|
|
16739
16792
|
const limit = limitArg ? parseInt(limitArg.split("=")[1], 10) : 20;
|
|
16793
|
+
const refArg = args2.find((a) => a.startsWith("--ref="))?.split("=").slice(1).join("=");
|
|
16740
16794
|
const positional = args2.filter((a) => !a.startsWith("--"));
|
|
16741
16795
|
if (positional.length === 0) {
|
|
16742
|
-
await printList(limit);
|
|
16796
|
+
await printList(limit, refArg);
|
|
16743
16797
|
} else {
|
|
16744
16798
|
await printDetail(positional[0]);
|
|
16745
16799
|
}
|
|
16746
16800
|
}
|
|
16747
|
-
var RESET, BOLD, DIM, CYAN, YELLOW;
|
|
16801
|
+
var RESET, BOLD, DIM, CYAN, YELLOW, GREEN, MAGENTA;
|
|
16748
16802
|
var init_history = __esm({
|
|
16749
16803
|
"src/cli/history.ts"() {
|
|
16750
16804
|
"use strict";
|
|
@@ -16756,6 +16810,8 @@ var init_history = __esm({
|
|
|
16756
16810
|
DIM = "\x1B[2m";
|
|
16757
16811
|
CYAN = "\x1B[36m";
|
|
16758
16812
|
YELLOW = "\x1B[33m";
|
|
16813
|
+
GREEN = "\x1B[32m";
|
|
16814
|
+
MAGENTA = "\x1B[35m";
|
|
16759
16815
|
}
|
|
16760
16816
|
});
|
|
16761
16817
|
|
|
@@ -17137,15 +17193,15 @@ function parseSince(arg) {
|
|
|
17137
17193
|
}
|
|
17138
17194
|
throw new Error(`Invalid --since value: "${arg}". Use 7d, 30d, or YYYY-MM-DD.`);
|
|
17139
17195
|
}
|
|
17140
|
-
async function buildReport(db,
|
|
17196
|
+
async function buildReport(db, cutoffDate2) {
|
|
17141
17197
|
const allSessions = await db.select({
|
|
17142
17198
|
id: sessions.id,
|
|
17143
17199
|
ai_tool: sessions.ai_tool,
|
|
17144
17200
|
updated_at: sessions.updated_at
|
|
17145
17201
|
}).from(sessions);
|
|
17146
|
-
const filtered =
|
|
17202
|
+
const filtered = cutoffDate2 ? allSessions.filter((s) => s.updated_at >= cutoffDate2) : allSessions;
|
|
17147
17203
|
const dateTo = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
17148
|
-
const dateFrom =
|
|
17204
|
+
const dateFrom = cutoffDate2 ?? (filtered.length > 0 ? filtered.map((s) => s.updated_at).sort()[0].slice(0, 10) : dateTo);
|
|
17149
17205
|
if (filtered.length === 0) {
|
|
17150
17206
|
return { dateFrom, dateTo, sessionCount: 0, providerCounts: {}, userMessages: 0, aiMessages: 0, secretsTotal: 0, byType: [] };
|
|
17151
17207
|
}
|
|
@@ -17213,10 +17269,10 @@ async function runReport(args2) {
|
|
|
17213
17269
|
return;
|
|
17214
17270
|
}
|
|
17215
17271
|
const sinceArg = args2.find((a) => a.startsWith("--since="))?.slice("--since=".length);
|
|
17216
|
-
let
|
|
17272
|
+
let cutoffDate2 = null;
|
|
17217
17273
|
if (sinceArg) {
|
|
17218
17274
|
try {
|
|
17219
|
-
|
|
17275
|
+
cutoffDate2 = parseSince(sinceArg);
|
|
17220
17276
|
} catch (err) {
|
|
17221
17277
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
17222
17278
|
`);
|
|
@@ -17224,7 +17280,7 @@ async function runReport(args2) {
|
|
|
17224
17280
|
}
|
|
17225
17281
|
}
|
|
17226
17282
|
const db = await initDb();
|
|
17227
|
-
const data = await buildReport(db,
|
|
17283
|
+
const data = await buildReport(db, cutoffDate2);
|
|
17228
17284
|
printReport(data);
|
|
17229
17285
|
}
|
|
17230
17286
|
var RESET2, BOLD2;
|
|
@@ -17239,6 +17295,271 @@ var init_report = __esm({
|
|
|
17239
17295
|
}
|
|
17240
17296
|
});
|
|
17241
17297
|
|
|
17298
|
+
// src/utils/signing.ts
|
|
17299
|
+
function keysDir() {
|
|
17300
|
+
return (0, import_path2.join)((0, import_os3.homedir)(), ".chron", "keys");
|
|
17301
|
+
}
|
|
17302
|
+
function privKeyPath(sessionId) {
|
|
17303
|
+
return (0, import_path2.join)(keysDir(), `${sessionId}.key`);
|
|
17304
|
+
}
|
|
17305
|
+
function sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
17306
|
+
const input = `${sessionId}|${finalContentHash}|${messageCount}|${firstCreatedAt}`;
|
|
17307
|
+
return (0, import_crypto2.createHash)("sha256").update(input).digest();
|
|
17308
|
+
}
|
|
17309
|
+
function signSession(sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
17310
|
+
const privPath = privKeyPath(sessionId);
|
|
17311
|
+
if (!(0, import_fs3.existsSync)(privPath)) {
|
|
17312
|
+
throw new Error(`Private key not found: ${privPath}`);
|
|
17313
|
+
}
|
|
17314
|
+
const privateKey = (0, import_fs3.readFileSync)(privPath, "utf8");
|
|
17315
|
+
const digest = sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt);
|
|
17316
|
+
const sig = (0, import_crypto2.sign)(null, digest, privateKey);
|
|
17317
|
+
return sig.toString("base64");
|
|
17318
|
+
}
|
|
17319
|
+
function verifySignature(publicKeyPem, signatureB64, sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
17320
|
+
try {
|
|
17321
|
+
const digest = sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt);
|
|
17322
|
+
const sigBuf = Buffer.from(signatureB64, "base64");
|
|
17323
|
+
return (0, import_crypto2.verify)(null, digest, publicKeyPem, sigBuf);
|
|
17324
|
+
} catch {
|
|
17325
|
+
return false;
|
|
17326
|
+
}
|
|
17327
|
+
}
|
|
17328
|
+
function signBuffer(sessionId, data) {
|
|
17329
|
+
const privateKey = (0, import_fs3.readFileSync)(privKeyPath(sessionId), "utf8");
|
|
17330
|
+
const digest = (0, import_crypto2.createHash)("sha256").update(data).digest();
|
|
17331
|
+
const sig = (0, import_crypto2.sign)(null, digest, privateKey);
|
|
17332
|
+
return sig.toString("base64");
|
|
17333
|
+
}
|
|
17334
|
+
function verifyBufferSignature(publicKeyPem, signatureB64, data) {
|
|
17335
|
+
try {
|
|
17336
|
+
const digest = (0, import_crypto2.createHash)("sha256").update(data).digest();
|
|
17337
|
+
const sigBuf = Buffer.from(signatureB64, "base64");
|
|
17338
|
+
return (0, import_crypto2.verify)(null, digest, publicKeyPem, sigBuf);
|
|
17339
|
+
} catch {
|
|
17340
|
+
return false;
|
|
17341
|
+
}
|
|
17342
|
+
}
|
|
17343
|
+
var import_crypto2, import_fs3, import_path2, import_os3;
|
|
17344
|
+
var init_signing = __esm({
|
|
17345
|
+
"src/utils/signing.ts"() {
|
|
17346
|
+
"use strict";
|
|
17347
|
+
import_crypto2 = require("crypto");
|
|
17348
|
+
import_fs3 = require("fs");
|
|
17349
|
+
import_path2 = require("path");
|
|
17350
|
+
import_os3 = require("os");
|
|
17351
|
+
}
|
|
17352
|
+
});
|
|
17353
|
+
|
|
17354
|
+
// package.json
|
|
17355
|
+
var require_package = __commonJS({
|
|
17356
|
+
"package.json"(exports2, module2) {
|
|
17357
|
+
module2.exports = {
|
|
17358
|
+
name: "chron-mcp",
|
|
17359
|
+
version: "0.1.24",
|
|
17360
|
+
mcpName: "io.github.sirinivask/chron",
|
|
17361
|
+
description: "Audit-grade timestamped logs for every AI conversation",
|
|
17362
|
+
repository: {
|
|
17363
|
+
type: "git",
|
|
17364
|
+
url: "https://github.com/sirinivask/chron.git"
|
|
17365
|
+
},
|
|
17366
|
+
main: "dist/index.js",
|
|
17367
|
+
vitest: {
|
|
17368
|
+
include: [
|
|
17369
|
+
"tests/**/*.test.ts"
|
|
17370
|
+
],
|
|
17371
|
+
globals: true
|
|
17372
|
+
},
|
|
17373
|
+
bin: {
|
|
17374
|
+
"chron-mcp": "dist/index.js",
|
|
17375
|
+
chron: "dist/cli/index.js"
|
|
17376
|
+
},
|
|
17377
|
+
files: [
|
|
17378
|
+
"dist",
|
|
17379
|
+
"skills",
|
|
17380
|
+
".claude-plugin",
|
|
17381
|
+
"assets",
|
|
17382
|
+
"dashboards",
|
|
17383
|
+
"README.md"
|
|
17384
|
+
],
|
|
17385
|
+
engines: {
|
|
17386
|
+
node: ">=18"
|
|
17387
|
+
},
|
|
17388
|
+
scripts: {
|
|
17389
|
+
build: "npx esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.js --platform=node && chmod +x dist/index.js && npx esbuild src/cli/index.ts --bundle --format=cjs --outfile=dist/cli/index.js --platform=node && chmod +x dist/cli/index.js",
|
|
17390
|
+
"build:cli": "npx esbuild src/cli/index.ts --bundle --format=cjs --outfile=dist/cli/index.js --platform=node && chmod +x dist/cli/index.js",
|
|
17391
|
+
typecheck: "tsc --noEmit",
|
|
17392
|
+
dev: "tsc --watch",
|
|
17393
|
+
start: "node dist/index.js",
|
|
17394
|
+
test: "vitest run",
|
|
17395
|
+
"test:watch": "vitest",
|
|
17396
|
+
prepublishOnly: "npm test && npm run build"
|
|
17397
|
+
},
|
|
17398
|
+
keywords: [
|
|
17399
|
+
"mcp",
|
|
17400
|
+
"ai",
|
|
17401
|
+
"audit",
|
|
17402
|
+
"logging",
|
|
17403
|
+
"claude",
|
|
17404
|
+
"cursor"
|
|
17405
|
+
],
|
|
17406
|
+
license: "SEE LICENSE IN LICENSE",
|
|
17407
|
+
dependencies: {
|
|
17408
|
+
"@libsql/client": "^0.17.3",
|
|
17409
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
17410
|
+
"drizzle-orm": "^0.45.2",
|
|
17411
|
+
express: "^4.18.2",
|
|
17412
|
+
uuid: "^11.1.1",
|
|
17413
|
+
zod: "^3.22.4"
|
|
17414
|
+
},
|
|
17415
|
+
devDependencies: {
|
|
17416
|
+
"@types/express": "^4.17.21",
|
|
17417
|
+
"@types/node": "^20.0.0",
|
|
17418
|
+
"@types/uuid": "^9.0.0",
|
|
17419
|
+
"drizzle-kit": "^0.20.14",
|
|
17420
|
+
typescript: "^5.4.5",
|
|
17421
|
+
vitest: "^1.4.0"
|
|
17422
|
+
}
|
|
17423
|
+
};
|
|
17424
|
+
}
|
|
17425
|
+
});
|
|
17426
|
+
|
|
17427
|
+
// src/cli/export-bundle.ts
|
|
17428
|
+
var export_bundle_exports = {};
|
|
17429
|
+
__export(export_bundle_exports, {
|
|
17430
|
+
runExportBundle: () => runExportBundle
|
|
17431
|
+
});
|
|
17432
|
+
function sha256file(filePath) {
|
|
17433
|
+
const content = (0, import_fs4.readFileSync)(filePath);
|
|
17434
|
+
return (0, import_crypto3.createHash)("sha256").update(content).digest("hex");
|
|
17435
|
+
}
|
|
17436
|
+
function parseSince2(s) {
|
|
17437
|
+
if (s.endsWith("d")) {
|
|
17438
|
+
const days = parseInt(s, 10);
|
|
17439
|
+
const d = /* @__PURE__ */ new Date();
|
|
17440
|
+
d.setDate(d.getDate() - days);
|
|
17441
|
+
return d.toISOString().slice(0, 10);
|
|
17442
|
+
}
|
|
17443
|
+
return s;
|
|
17444
|
+
}
|
|
17445
|
+
async function runExportBundle(args2) {
|
|
17446
|
+
const sessionArg = args2.find((a) => a.startsWith("--session="))?.split("=").slice(1).join("=");
|
|
17447
|
+
const sinceArg = args2.find((a) => a.startsWith("--since="))?.split("=").slice(1).join("=");
|
|
17448
|
+
const outputPath = args2.find((a) => a.startsWith("--output="))?.split("=").slice(1).join("=") ?? "bundle.chron.tar.gz";
|
|
17449
|
+
const db = await initDb();
|
|
17450
|
+
const allSessions = await db.select().from(sessions);
|
|
17451
|
+
let filtered = allSessions;
|
|
17452
|
+
if (sessionArg) {
|
|
17453
|
+
filtered = allSessions.filter((s) => s.id.startsWith(sessionArg));
|
|
17454
|
+
if (filtered.length === 0) {
|
|
17455
|
+
process.stderr.write(`Session not found: ${sessionArg}
|
|
17456
|
+
`);
|
|
17457
|
+
process.exit(1);
|
|
17458
|
+
}
|
|
17459
|
+
}
|
|
17460
|
+
if (sinceArg) {
|
|
17461
|
+
const cutoff = parseSince2(sinceArg);
|
|
17462
|
+
filtered = filtered.filter((s) => s.created_at >= cutoff);
|
|
17463
|
+
}
|
|
17464
|
+
if (filtered.length === 0) {
|
|
17465
|
+
process.stderr.write("No sessions matched the filter.\n");
|
|
17466
|
+
process.exit(1);
|
|
17467
|
+
}
|
|
17468
|
+
const sessionIds = filtered.map((s) => s.id);
|
|
17469
|
+
const tempDir = (0, import_path3.join)((0, import_os4.tmpdir)(), `chron-bundle-${Date.now()}`);
|
|
17470
|
+
(0, import_fs4.mkdirSync)(tempDir, { recursive: true });
|
|
17471
|
+
const pubkeysDir = (0, import_path3.join)(tempDir, "pubkeys");
|
|
17472
|
+
(0, import_fs4.mkdirSync)(pubkeysDir);
|
|
17473
|
+
try {
|
|
17474
|
+
(0, import_fs4.writeFileSync)((0, import_path3.join)(tempDir, "sessions.json"), JSON.stringify(filtered, null, 2));
|
|
17475
|
+
const msgsPath = (0, import_path3.join)(tempDir, "messages.jsonl");
|
|
17476
|
+
const msgsStream = (0, import_fs4.createWriteStream)(msgsPath);
|
|
17477
|
+
for (const sid of sessionIds) {
|
|
17478
|
+
const msgs = await db.select().from(messages).where(eq(messages.session_id, sid)).orderBy(asc(messages.created_at), asc(sql`rowid`));
|
|
17479
|
+
for (const m of msgs)
|
|
17480
|
+
msgsStream.write(JSON.stringify(m) + "\n");
|
|
17481
|
+
}
|
|
17482
|
+
await new Promise((resolve) => msgsStream.end(resolve));
|
|
17483
|
+
const secretsPath = (0, import_path3.join)(tempDir, "secrets.jsonl");
|
|
17484
|
+
const secretsStream = (0, import_fs4.createWriteStream)(secretsPath);
|
|
17485
|
+
for (const sid of sessionIds) {
|
|
17486
|
+
const secs = await db.select().from(secrets_detected).where(eq(secrets_detected.session_id, sid));
|
|
17487
|
+
for (const s of secs)
|
|
17488
|
+
secretsStream.write(JSON.stringify(s) + "\n");
|
|
17489
|
+
}
|
|
17490
|
+
await new Promise((resolve) => secretsStream.end(resolve));
|
|
17491
|
+
for (const s of filtered) {
|
|
17492
|
+
if (s.public_key) {
|
|
17493
|
+
(0, import_fs4.writeFileSync)((0, import_path3.join)(pubkeysDir, `${s.id}.pub`), s.public_key);
|
|
17494
|
+
}
|
|
17495
|
+
}
|
|
17496
|
+
const fileHashes = {
|
|
17497
|
+
"sessions.json": sha256file((0, import_path3.join)(tempDir, "sessions.json")),
|
|
17498
|
+
"messages.jsonl": sha256file(msgsPath),
|
|
17499
|
+
"secrets.jsonl": sha256file(secretsPath)
|
|
17500
|
+
};
|
|
17501
|
+
for (const s of filtered) {
|
|
17502
|
+
if (s.public_key) {
|
|
17503
|
+
fileHashes[`pubkeys/${s.id}.pub`] = sha256file((0, import_path3.join)(pubkeysDir, `${s.id}.pub`));
|
|
17504
|
+
}
|
|
17505
|
+
}
|
|
17506
|
+
const manifest = {
|
|
17507
|
+
chron_bundle: "v1",
|
|
17508
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17509
|
+
chron_version: import_package.version,
|
|
17510
|
+
machine_id: (0, import_os4.hostname)(),
|
|
17511
|
+
sessions: sessionIds,
|
|
17512
|
+
files: fileHashes
|
|
17513
|
+
};
|
|
17514
|
+
const manifestBytes = Buffer.from(JSON.stringify(manifest, null, 2));
|
|
17515
|
+
(0, import_fs4.writeFileSync)((0, import_path3.join)(tempDir, "manifest.json"), manifestBytes);
|
|
17516
|
+
let signed = false;
|
|
17517
|
+
if (filtered.length === 1) {
|
|
17518
|
+
const s = filtered[0];
|
|
17519
|
+
if (s.public_key && (0, import_fs4.existsSync)(privKeyPath(s.id))) {
|
|
17520
|
+
try {
|
|
17521
|
+
const sig = signBuffer(s.id, manifestBytes);
|
|
17522
|
+
(0, import_fs4.writeFileSync)((0, import_path3.join)(tempDir, "manifest.sig"), JSON.stringify({
|
|
17523
|
+
chron_sig: "v1",
|
|
17524
|
+
session_id: s.id,
|
|
17525
|
+
algorithm: "Ed25519-SHA256",
|
|
17526
|
+
signature: sig
|
|
17527
|
+
}, null, 2));
|
|
17528
|
+
signed = true;
|
|
17529
|
+
} catch {
|
|
17530
|
+
}
|
|
17531
|
+
}
|
|
17532
|
+
}
|
|
17533
|
+
const absOutput = outputPath.startsWith("/") ? outputPath : (0, import_path3.join)(process.cwd(), outputPath);
|
|
17534
|
+
(0, import_child_process.execSync)(`tar -czf "${absOutput}" -C "${tempDir}" .`);
|
|
17535
|
+
const sessionWord = filtered.length === 1 ? "session" : "sessions";
|
|
17536
|
+
process.stdout.write(`Bundle: ${absOutput}
|
|
17537
|
+
`);
|
|
17538
|
+
process.stdout.write(` ${filtered.length} ${sessionWord} | signed: ${signed ? "yes (Ed25519)" : "no"}
|
|
17539
|
+
`);
|
|
17540
|
+
process.stdout.write(` Verify: chron verify --bundle="${absOutput}"
|
|
17541
|
+
`);
|
|
17542
|
+
} finally {
|
|
17543
|
+
(0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
17544
|
+
}
|
|
17545
|
+
}
|
|
17546
|
+
var import_fs4, import_path3, import_os4, import_crypto3, import_child_process, import_package;
|
|
17547
|
+
var init_export_bundle = __esm({
|
|
17548
|
+
"src/cli/export-bundle.ts"() {
|
|
17549
|
+
"use strict";
|
|
17550
|
+
import_fs4 = require("fs");
|
|
17551
|
+
import_path3 = require("path");
|
|
17552
|
+
import_os4 = require("os");
|
|
17553
|
+
import_crypto3 = require("crypto");
|
|
17554
|
+
import_child_process = require("child_process");
|
|
17555
|
+
init_drizzle_orm();
|
|
17556
|
+
init_db2();
|
|
17557
|
+
init_schema();
|
|
17558
|
+
init_signing();
|
|
17559
|
+
import_package = __toESM(require_package());
|
|
17560
|
+
}
|
|
17561
|
+
});
|
|
17562
|
+
|
|
17242
17563
|
// src/cli/export.ts
|
|
17243
17564
|
var export_exports = {};
|
|
17244
17565
|
__export(export_exports, {
|
|
@@ -17252,9 +17573,16 @@ function fmtTimestamp2(iso) {
|
|
|
17252
17573
|
return `${m[1]} ${m[2]} ${tz}`;
|
|
17253
17574
|
}
|
|
17254
17575
|
async function runExport(args2) {
|
|
17576
|
+
if (args2.includes("--signed")) {
|
|
17577
|
+
const { runExportBundle: runExportBundle2 } = await Promise.resolve().then(() => (init_export_bundle(), export_bundle_exports));
|
|
17578
|
+
await runExportBundle2(args2.filter((a) => a !== "--signed"));
|
|
17579
|
+
return;
|
|
17580
|
+
}
|
|
17255
17581
|
const prefix = args2.find((a) => !a.startsWith("--"));
|
|
17256
17582
|
if (!prefix) {
|
|
17257
|
-
process.stderr.write(
|
|
17583
|
+
process.stderr.write(
|
|
17584
|
+
"Usage: chron export <session-id-prefix>\n chron export --signed [--session=<id>] [--since=<range>] [--output=<file>]\n"
|
|
17585
|
+
);
|
|
17258
17586
|
process.exit(1);
|
|
17259
17587
|
}
|
|
17260
17588
|
const db = await initDb();
|
|
@@ -17312,7 +17640,7 @@ __export(settings_exports, {
|
|
|
17312
17640
|
runSettings: () => runSettings
|
|
17313
17641
|
});
|
|
17314
17642
|
function dbPath() {
|
|
17315
|
-
return process.env.CHRON_DB_PATH ?? (0,
|
|
17643
|
+
return process.env.CHRON_DB_PATH ?? (0, import_path4.join)((0, import_os5.homedir)(), ".chron", "chron.db");
|
|
17316
17644
|
}
|
|
17317
17645
|
async function runSettings(_args) {
|
|
17318
17646
|
process.stdout.write(`
|
|
@@ -17326,12 +17654,12 @@ ${BOLD3}Chron Settings${RESET3}
|
|
|
17326
17654
|
|
|
17327
17655
|
`);
|
|
17328
17656
|
}
|
|
17329
|
-
var
|
|
17657
|
+
var import_os5, import_path4, RESET3, BOLD3, DIM2, CYAN2;
|
|
17330
17658
|
var init_settings = __esm({
|
|
17331
17659
|
"src/cli/settings.ts"() {
|
|
17332
17660
|
"use strict";
|
|
17333
|
-
|
|
17334
|
-
|
|
17661
|
+
import_os5 = require("os");
|
|
17662
|
+
import_path4 = require("path");
|
|
17335
17663
|
RESET3 = "\x1B[0m";
|
|
17336
17664
|
BOLD3 = "\x1B[1m";
|
|
17337
17665
|
DIM2 = "\x1B[2m";
|
|
@@ -17424,79 +17752,6 @@ var init_secrets = __esm({
|
|
|
17424
17752
|
}
|
|
17425
17753
|
});
|
|
17426
17754
|
|
|
17427
|
-
// package.json
|
|
17428
|
-
var require_package = __commonJS({
|
|
17429
|
-
"package.json"(exports2, module2) {
|
|
17430
|
-
module2.exports = {
|
|
17431
|
-
name: "chron-mcp",
|
|
17432
|
-
version: "0.1.20",
|
|
17433
|
-
mcpName: "io.github.sirinivask/chron",
|
|
17434
|
-
description: "Audit-grade timestamped logs for every AI conversation",
|
|
17435
|
-
repository: {
|
|
17436
|
-
type: "git",
|
|
17437
|
-
url: "https://github.com/sirinivask/chron.git"
|
|
17438
|
-
},
|
|
17439
|
-
main: "dist/index.js",
|
|
17440
|
-
vitest: {
|
|
17441
|
-
include: [
|
|
17442
|
-
"tests/**/*.test.ts"
|
|
17443
|
-
],
|
|
17444
|
-
globals: true
|
|
17445
|
-
},
|
|
17446
|
-
bin: {
|
|
17447
|
-
"chron-mcp": "dist/index.js",
|
|
17448
|
-
chron: "dist/cli/index.js"
|
|
17449
|
-
},
|
|
17450
|
-
files: [
|
|
17451
|
-
"dist",
|
|
17452
|
-
"skills",
|
|
17453
|
-
".claude-plugin",
|
|
17454
|
-
"assets",
|
|
17455
|
-
"dashboards",
|
|
17456
|
-
"README.md"
|
|
17457
|
-
],
|
|
17458
|
-
engines: {
|
|
17459
|
-
node: ">=18"
|
|
17460
|
-
},
|
|
17461
|
-
scripts: {
|
|
17462
|
-
build: "npx esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.js --platform=node && chmod +x dist/index.js && npx esbuild src/cli/index.ts --bundle --format=cjs --outfile=dist/cli/index.js --platform=node && chmod +x dist/cli/index.js",
|
|
17463
|
-
"build:cli": "npx esbuild src/cli/index.ts --bundle --format=cjs --outfile=dist/cli/index.js --platform=node && chmod +x dist/cli/index.js",
|
|
17464
|
-
typecheck: "tsc --noEmit",
|
|
17465
|
-
dev: "tsc --watch",
|
|
17466
|
-
start: "node dist/index.js",
|
|
17467
|
-
test: "vitest run",
|
|
17468
|
-
"test:watch": "vitest",
|
|
17469
|
-
prepublishOnly: "npm test && npm run build"
|
|
17470
|
-
},
|
|
17471
|
-
keywords: [
|
|
17472
|
-
"mcp",
|
|
17473
|
-
"ai",
|
|
17474
|
-
"audit",
|
|
17475
|
-
"logging",
|
|
17476
|
-
"claude",
|
|
17477
|
-
"cursor"
|
|
17478
|
-
],
|
|
17479
|
-
license: "SEE LICENSE IN LICENSE",
|
|
17480
|
-
dependencies: {
|
|
17481
|
-
"@libsql/client": "^0.17.3",
|
|
17482
|
-
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
17483
|
-
"drizzle-orm": "^0.45.2",
|
|
17484
|
-
express: "^4.18.2",
|
|
17485
|
-
uuid: "^11.1.1",
|
|
17486
|
-
zod: "^3.22.4"
|
|
17487
|
-
},
|
|
17488
|
-
devDependencies: {
|
|
17489
|
-
"@types/express": "^4.17.21",
|
|
17490
|
-
"@types/node": "^20.0.0",
|
|
17491
|
-
"@types/uuid": "^9.0.0",
|
|
17492
|
-
"drizzle-kit": "^0.20.14",
|
|
17493
|
-
typescript: "^5.4.5",
|
|
17494
|
-
vitest: "^1.4.0"
|
|
17495
|
-
}
|
|
17496
|
-
};
|
|
17497
|
-
}
|
|
17498
|
-
});
|
|
17499
|
-
|
|
17500
17755
|
// src/cli/connect.ts
|
|
17501
17756
|
var connect_exports = {};
|
|
17502
17757
|
__export(connect_exports, {
|
|
@@ -17506,26 +17761,26 @@ function prompt(rl, question) {
|
|
|
17506
17761
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
17507
17762
|
}
|
|
17508
17763
|
function configPath() {
|
|
17509
|
-
return (0,
|
|
17764
|
+
return (0, import_path5.join)((0, import_os6.homedir)(), ".chron", "config.json");
|
|
17510
17765
|
}
|
|
17511
17766
|
function loadConfig() {
|
|
17512
17767
|
try {
|
|
17513
|
-
return JSON.parse((0,
|
|
17768
|
+
return JSON.parse((0, import_fs5.readFileSync)(configPath(), "utf8"));
|
|
17514
17769
|
} catch {
|
|
17515
17770
|
return {};
|
|
17516
17771
|
}
|
|
17517
17772
|
}
|
|
17518
17773
|
function saveConfig(data) {
|
|
17519
|
-
const dir = (0,
|
|
17520
|
-
(0,
|
|
17521
|
-
(0,
|
|
17774
|
+
const dir = (0, import_path5.join)((0, import_os6.homedir)(), ".chron");
|
|
17775
|
+
(0, import_fs5.mkdirSync)(dir, { recursive: true });
|
|
17776
|
+
(0, import_fs5.writeFileSync)(configPath(), JSON.stringify(data, null, 2), "utf8");
|
|
17522
17777
|
}
|
|
17523
17778
|
function patchClaudeJson(vars) {
|
|
17524
|
-
const path = (0,
|
|
17525
|
-
if (!(0,
|
|
17779
|
+
const path = (0, import_path5.join)((0, import_os6.homedir)(), ".claude.json");
|
|
17780
|
+
if (!(0, import_fs5.existsSync)(path))
|
|
17526
17781
|
return false;
|
|
17527
17782
|
try {
|
|
17528
|
-
const raw = (0,
|
|
17783
|
+
const raw = (0, import_fs5.readFileSync)(path, "utf8");
|
|
17529
17784
|
const doc = JSON.parse(raw);
|
|
17530
17785
|
const servers = doc.mcpServers ?? {};
|
|
17531
17786
|
const chron = servers.chron ?? {};
|
|
@@ -17534,7 +17789,7 @@ function patchClaudeJson(vars) {
|
|
|
17534
17789
|
chron.env = { ...chron.env ?? {}, ...vars };
|
|
17535
17790
|
servers.chron = chron;
|
|
17536
17791
|
doc.mcpServers = servers;
|
|
17537
|
-
(0,
|
|
17792
|
+
(0, import_fs5.writeFileSync)(path, JSON.stringify(doc, null, 2), "utf8");
|
|
17538
17793
|
return true;
|
|
17539
17794
|
} catch {
|
|
17540
17795
|
return false;
|
|
@@ -17617,13 +17872,13 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17617
17872
|
`);
|
|
17618
17873
|
process.exit(1);
|
|
17619
17874
|
}
|
|
17620
|
-
process.stdout.write(`${
|
|
17875
|
+
process.stdout.write(`${GREEN2}OK${RESET5}
|
|
17621
17876
|
|
|
17622
17877
|
`);
|
|
17623
17878
|
const config = loadConfig();
|
|
17624
17879
|
config.logscale = { url, connected_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
17625
17880
|
saveConfig(config);
|
|
17626
|
-
process.stdout.write(`${
|
|
17881
|
+
process.stdout.write(`${GREEN2}${BOLD5}Connected!${RESET5} Test event appeared in LogScale.
|
|
17627
17882
|
|
|
17628
17883
|
`);
|
|
17629
17884
|
process.stdout.write(`${BOLD5}Add these env vars to your MCP client config:${RESET5}
|
|
@@ -17732,7 +17987,7 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17732
17987
|
`);
|
|
17733
17988
|
process.exit(1);
|
|
17734
17989
|
}
|
|
17735
|
-
process.stdout.write(`${
|
|
17990
|
+
process.stdout.write(`${GREEN2}OK${RESET5}
|
|
17736
17991
|
|
|
17737
17992
|
`);
|
|
17738
17993
|
} catch (e) {
|
|
@@ -17750,7 +18005,7 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17750
18005
|
if (isLocalhost(url))
|
|
17751
18006
|
splunkVars.CHRON_SPLUNK_INSECURE = "1";
|
|
17752
18007
|
patchClaudeJson(splunkVars);
|
|
17753
|
-
process.stdout.write(`${
|
|
18008
|
+
process.stdout.write(`${GREEN2}${BOLD5}Connected!${RESET5} Splunk is receiving chron events.
|
|
17754
18009
|
|
|
17755
18010
|
`);
|
|
17756
18011
|
process.stdout.write(`${DIM4}Config saved to ~/.chron/config.json \u2014 events will flow immediately in all running sessions.${RESET5}
|
|
@@ -17839,7 +18094,7 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
|
|
|
17839
18094
|
}
|
|
17840
18095
|
const tokenData = await tokenRes.json();
|
|
17841
18096
|
accessToken = tokenData.access_token;
|
|
17842
|
-
process.stdout.write(`${
|
|
18097
|
+
process.stdout.write(`${GREEN2}OK${RESET5}
|
|
17843
18098
|
`);
|
|
17844
18099
|
} catch (e) {
|
|
17845
18100
|
process.stdout.write(`${RED}failed${RESET5}
|
|
@@ -17881,7 +18136,7 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
|
|
|
17881
18136
|
`);
|
|
17882
18137
|
process.exit(1);
|
|
17883
18138
|
}
|
|
17884
|
-
process.stdout.write(`${
|
|
18139
|
+
process.stdout.write(`${GREEN2}OK${RESET5}
|
|
17885
18140
|
|
|
17886
18141
|
`);
|
|
17887
18142
|
} catch (e) {
|
|
@@ -17903,7 +18158,7 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
|
|
|
17903
18158
|
CHRON_SENTINEL_DCR_ID: dcrId,
|
|
17904
18159
|
CHRON_SENTINEL_STREAM: stream
|
|
17905
18160
|
});
|
|
17906
|
-
process.stdout.write(`${
|
|
18161
|
+
process.stdout.write(`${GREEN2}${BOLD5}Connected!${RESET5} Test event sent to Sentinel workspace.
|
|
17907
18162
|
|
|
17908
18163
|
`);
|
|
17909
18164
|
process.stdout.write(`${DIM4}Config saved to ~/.chron/config.json \u2014 events will flow immediately in all running sessions.${RESET5}
|
|
@@ -17939,19 +18194,19 @@ Integrations:
|
|
|
17939
18194
|
}
|
|
17940
18195
|
}
|
|
17941
18196
|
}
|
|
17942
|
-
var import_readline,
|
|
18197
|
+
var import_readline, import_os6, import_path5, import_fs5, RESET5, BOLD5, DIM4, CYAN4, GREEN2, RED, YELLOW3;
|
|
17943
18198
|
var init_connect = __esm({
|
|
17944
18199
|
"src/cli/connect.ts"() {
|
|
17945
18200
|
"use strict";
|
|
17946
18201
|
import_readline = require("readline");
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
18202
|
+
import_os6 = require("os");
|
|
18203
|
+
import_path5 = require("path");
|
|
18204
|
+
import_fs5 = require("fs");
|
|
17950
18205
|
RESET5 = "\x1B[0m";
|
|
17951
18206
|
BOLD5 = "\x1B[1m";
|
|
17952
18207
|
DIM4 = "\x1B[2m";
|
|
17953
18208
|
CYAN4 = "\x1B[36m";
|
|
17954
|
-
|
|
18209
|
+
GREEN2 = "\x1B[32m";
|
|
17955
18210
|
RED = "\x1B[31m";
|
|
17956
18211
|
YELLOW3 = "\x1B[33m";
|
|
17957
18212
|
}
|
|
@@ -18047,7 +18302,7 @@ ${BOLD6}Session Summary${RESET6}
|
|
|
18047
18302
|
process.stdout.write(` Duration ${stats.duration_minutes}m
|
|
18048
18303
|
|
|
18049
18304
|
`);
|
|
18050
|
-
process.stdout.write(`${BOLD6}Secrets touched${RESET6} ${secrets.length === 0 ? `${
|
|
18305
|
+
process.stdout.write(`${BOLD6}Secrets touched${RESET6} ${secrets.length === 0 ? `${GREEN3}none${RESET6}` : `${RED2}${secrets.length} detection(s)${RESET6}`}
|
|
18051
18306
|
`);
|
|
18052
18307
|
for (const s of secrets) {
|
|
18053
18308
|
process.stdout.write(` ${RED2}[${s.type}]${RESET6} ${DIM5}${s.masked_value}${RESET6}
|
|
@@ -18101,7 +18356,7 @@ async function runSummary(args2) {
|
|
|
18101
18356
|
printSummary(data);
|
|
18102
18357
|
}
|
|
18103
18358
|
}
|
|
18104
|
-
var RESET6, BOLD6, DIM5, CYAN5,
|
|
18359
|
+
var RESET6, BOLD6, DIM5, CYAN5, GREEN3, YELLOW4, RED2, MUTATION_PATTERNS, PROD_PATTERNS;
|
|
18105
18360
|
var init_summary = __esm({
|
|
18106
18361
|
"src/cli/summary.ts"() {
|
|
18107
18362
|
"use strict";
|
|
@@ -18112,7 +18367,7 @@ var init_summary = __esm({
|
|
|
18112
18367
|
BOLD6 = "\x1B[1m";
|
|
18113
18368
|
DIM5 = "\x1B[2m";
|
|
18114
18369
|
CYAN5 = "\x1B[36m";
|
|
18115
|
-
|
|
18370
|
+
GREEN3 = "\x1B[32m";
|
|
18116
18371
|
YELLOW4 = "\x1B[33m";
|
|
18117
18372
|
RED2 = "\x1B[31m";
|
|
18118
18373
|
MUTATION_PATTERNS = [
|
|
@@ -18128,44 +18383,104 @@ var init_summary = __esm({
|
|
|
18128
18383
|
}
|
|
18129
18384
|
});
|
|
18130
18385
|
|
|
18131
|
-
// src/
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
|
|
18136
|
-
|
|
18386
|
+
// src/cli/prune.ts
|
|
18387
|
+
var prune_exports = {};
|
|
18388
|
+
__export(prune_exports, {
|
|
18389
|
+
runPrune: () => runPrune
|
|
18390
|
+
});
|
|
18391
|
+
function configRetentionDays() {
|
|
18392
|
+
const configPath2 = (0, import_path6.join)((0, import_os7.homedir)(), ".chron", "config.json");
|
|
18393
|
+
if (!(0, import_fs6.existsSync)(configPath2))
|
|
18394
|
+
return null;
|
|
18395
|
+
try {
|
|
18396
|
+
const cfg = JSON.parse((0, import_fs6.readFileSync)(configPath2, "utf8"));
|
|
18397
|
+
const v = Number(cfg.retention_days);
|
|
18398
|
+
return isNaN(v) ? null : v;
|
|
18399
|
+
} catch {
|
|
18400
|
+
return null;
|
|
18401
|
+
}
|
|
18137
18402
|
}
|
|
18138
|
-
function
|
|
18139
|
-
const
|
|
18140
|
-
|
|
18403
|
+
function cutoffDate(days) {
|
|
18404
|
+
const d = /* @__PURE__ */ new Date();
|
|
18405
|
+
d.setDate(d.getDate() - days);
|
|
18406
|
+
return d.toISOString().slice(0, 10);
|
|
18141
18407
|
}
|
|
18142
|
-
function
|
|
18143
|
-
const
|
|
18144
|
-
|
|
18145
|
-
|
|
18408
|
+
async function runPrune(args2) {
|
|
18409
|
+
const dryRun = args2.includes("--dry-run");
|
|
18410
|
+
const confirm = args2.includes("--confirm");
|
|
18411
|
+
const olderArg = args2.find((a) => a.startsWith("--older-than="))?.split("=")[1];
|
|
18412
|
+
if (!dryRun && !confirm) {
|
|
18413
|
+
process.stderr.write(
|
|
18414
|
+
"Usage: chron prune --older-than=<n>d [--dry-run | --confirm]\n\n --dry-run Show what would be deleted without deleting\n --confirm Actually delete (required for real deletion)\n\nExample: chron prune --older-than=90d --dry-run\n"
|
|
18415
|
+
);
|
|
18416
|
+
process.exit(1);
|
|
18146
18417
|
}
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18151
|
-
}
|
|
18152
|
-
|
|
18153
|
-
|
|
18154
|
-
|
|
18155
|
-
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18418
|
+
let days;
|
|
18419
|
+
if (olderArg) {
|
|
18420
|
+
days = parseInt(olderArg, 10);
|
|
18421
|
+
if (isNaN(days) || days <= 0) {
|
|
18422
|
+
process.stderr.write(`Invalid --older-than value: ${olderArg} (expected e.g. 90d)
|
|
18423
|
+
`);
|
|
18424
|
+
process.exit(1);
|
|
18425
|
+
}
|
|
18426
|
+
} else {
|
|
18427
|
+
const cfg = configRetentionDays();
|
|
18428
|
+
if (!cfg) {
|
|
18429
|
+
process.stderr.write(
|
|
18430
|
+
"No --older-than specified and no retention_days in ~/.chron/config.json\n"
|
|
18431
|
+
);
|
|
18432
|
+
process.exit(1);
|
|
18433
|
+
}
|
|
18434
|
+
days = cfg;
|
|
18435
|
+
process.stdout.write(`Using retention_days=${days} from config
|
|
18436
|
+
`);
|
|
18437
|
+
}
|
|
18438
|
+
const cutoff = cutoffDate(days);
|
|
18439
|
+
const db = await initDb();
|
|
18440
|
+
const stale = await db.select({
|
|
18441
|
+
id: sessions.id,
|
|
18442
|
+
title: sessions.title,
|
|
18443
|
+
updated_at: sessions.updated_at,
|
|
18444
|
+
msg_count: sql`count(${messages.id})`
|
|
18445
|
+
}).from(sessions).leftJoin(messages, sql`${messages.session_id} = ${sessions.id}`).where(lt(sessions.updated_at, cutoff)).groupBy(sessions.id);
|
|
18446
|
+
if (stale.length === 0) {
|
|
18447
|
+
process.stdout.write(`No sessions older than ${days}d (cutoff: ${cutoff}).
|
|
18448
|
+
`);
|
|
18449
|
+
return;
|
|
18450
|
+
}
|
|
18451
|
+
const totalMsgs = stale.reduce((s, r) => s + Number(r.msg_count), 0);
|
|
18452
|
+
process.stdout.write(`
|
|
18453
|
+
Sessions to prune (last active before ${cutoff}):
|
|
18454
|
+
|
|
18455
|
+
`);
|
|
18456
|
+
for (const row of stale) {
|
|
18457
|
+
process.stdout.write(` ${row.updated_at.slice(0, 10)} ${row.id.slice(0, 8)} ${row.msg_count} msgs ${row.title}
|
|
18458
|
+
`);
|
|
18159
18459
|
}
|
|
18460
|
+
process.stdout.write(`
|
|
18461
|
+
Total: ${stale.length} session(s), ${totalMsgs} message(s)
|
|
18462
|
+
|
|
18463
|
+
`);
|
|
18464
|
+
if (dryRun) {
|
|
18465
|
+
process.stdout.write("Dry run \u2014 nothing deleted. Re-run with --confirm to delete.\n");
|
|
18466
|
+
return;
|
|
18467
|
+
}
|
|
18468
|
+
for (const row of stale) {
|
|
18469
|
+
await db.delete(sessions).where(sql`${sessions.id} = ${row.id}`);
|
|
18470
|
+
}
|
|
18471
|
+
process.stdout.write(`Pruned ${stale.length} session(s) and ${totalMsgs} message(s).
|
|
18472
|
+
`);
|
|
18160
18473
|
}
|
|
18161
|
-
var
|
|
18162
|
-
var
|
|
18163
|
-
"src/
|
|
18474
|
+
var import_fs6, import_path6, import_os7;
|
|
18475
|
+
var init_prune = __esm({
|
|
18476
|
+
"src/cli/prune.ts"() {
|
|
18164
18477
|
"use strict";
|
|
18165
|
-
|
|
18166
|
-
|
|
18167
|
-
|
|
18168
|
-
|
|
18478
|
+
init_drizzle_orm();
|
|
18479
|
+
import_fs6 = require("fs");
|
|
18480
|
+
import_path6 = require("path");
|
|
18481
|
+
import_os7 = require("os");
|
|
18482
|
+
init_db2();
|
|
18483
|
+
init_schema();
|
|
18169
18484
|
}
|
|
18170
18485
|
});
|
|
18171
18486
|
|
|
@@ -18193,7 +18508,7 @@ async function runSign(args2) {
|
|
|
18193
18508
|
`);
|
|
18194
18509
|
process.exit(1);
|
|
18195
18510
|
}
|
|
18196
|
-
if (!(0,
|
|
18511
|
+
if (!(0, import_fs8.existsSync)(privKeyPath(session.id))) {
|
|
18197
18512
|
process.stderr.write(`Private key not found: ${privKeyPath(session.id)}
|
|
18198
18513
|
`);
|
|
18199
18514
|
process.stderr.write("The key is stored on the machine where the session was created.\n");
|
|
@@ -18228,8 +18543,8 @@ async function runSign(args2) {
|
|
|
18228
18543
|
signature,
|
|
18229
18544
|
signed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
18230
18545
|
};
|
|
18231
|
-
const sigFile = (0,
|
|
18232
|
-
(0,
|
|
18546
|
+
const sigFile = (0, import_path7.join)(process.cwd(), `${session.id.slice(0, 8)}.chron.sig`);
|
|
18547
|
+
(0, import_fs7.writeFileSync)(sigFile, JSON.stringify(sigData, null, 2));
|
|
18233
18548
|
process.stdout.write(`Signed session ${session.id.slice(0, 8)}
|
|
18234
18549
|
`);
|
|
18235
18550
|
process.stdout.write(` messages : ${messageCount}
|
|
@@ -18239,17 +18554,17 @@ async function runSign(args2) {
|
|
|
18239
18554
|
process.stdout.write(` sig file : ${sigFile}
|
|
18240
18555
|
`);
|
|
18241
18556
|
}
|
|
18242
|
-
var
|
|
18557
|
+
var import_fs7, import_path7, import_fs8;
|
|
18243
18558
|
var init_sign = __esm({
|
|
18244
18559
|
"src/cli/sign.ts"() {
|
|
18245
18560
|
"use strict";
|
|
18246
18561
|
init_drizzle_orm();
|
|
18247
|
-
|
|
18248
|
-
|
|
18562
|
+
import_fs7 = require("fs");
|
|
18563
|
+
import_path7 = require("path");
|
|
18249
18564
|
init_db2();
|
|
18250
18565
|
init_schema();
|
|
18251
18566
|
init_signing();
|
|
18252
|
-
|
|
18567
|
+
import_fs8 = require("fs");
|
|
18253
18568
|
}
|
|
18254
18569
|
});
|
|
18255
18570
|
|
|
@@ -18257,13 +18572,13 @@ var init_sign = __esm({
|
|
|
18257
18572
|
function computeContentHash(sessionId, role, content, createdAt, prevHash, eventType) {
|
|
18258
18573
|
const base = `${sessionId}|${role}|${content}|${createdAt}|${prevHash ?? ""}`;
|
|
18259
18574
|
const input = eventType != null ? `${base}|${eventType}` : base;
|
|
18260
|
-
return (0,
|
|
18575
|
+
return (0, import_crypto4.createHash)("sha256").update(input).digest("hex");
|
|
18261
18576
|
}
|
|
18262
|
-
var
|
|
18577
|
+
var import_crypto4;
|
|
18263
18578
|
var init_hash = __esm({
|
|
18264
18579
|
"src/utils/hash.ts"() {
|
|
18265
18580
|
"use strict";
|
|
18266
|
-
|
|
18581
|
+
import_crypto4 = require("crypto");
|
|
18267
18582
|
}
|
|
18268
18583
|
});
|
|
18269
18584
|
|
|
@@ -18273,7 +18588,7 @@ __export(verify_exports, {
|
|
|
18273
18588
|
runVerify: () => runVerify
|
|
18274
18589
|
});
|
|
18275
18590
|
function ok(msg) {
|
|
18276
|
-
process.stdout.write(` ${
|
|
18591
|
+
process.stdout.write(` ${GREEN4}\u2713${RESET7} ${msg}
|
|
18277
18592
|
`);
|
|
18278
18593
|
}
|
|
18279
18594
|
function fail(msg) {
|
|
@@ -18284,10 +18599,90 @@ function warn(msg) {
|
|
|
18284
18599
|
process.stdout.write(` ${YELLOW5}!${RESET7} ${msg}
|
|
18285
18600
|
`);
|
|
18286
18601
|
}
|
|
18602
|
+
async function runVerifyBundle(bundlePath) {
|
|
18603
|
+
process.stdout.write(`
|
|
18604
|
+
${BOLD7}Verifying bundle${RESET7} ${bundlePath}
|
|
18605
|
+
|
|
18606
|
+
`);
|
|
18607
|
+
const tempDir = (0, import_path8.join)((0, import_os8.tmpdir)(), `chron-verify-${Date.now()}`);
|
|
18608
|
+
(0, import_fs9.mkdirSync)(tempDir, { recursive: true });
|
|
18609
|
+
try {
|
|
18610
|
+
(0, import_child_process2.execSync)(`tar -xzf "${bundlePath}" -C "${tempDir}"`, { stdio: "pipe" });
|
|
18611
|
+
} catch {
|
|
18612
|
+
process.stderr.write(`Failed to extract bundle: ${bundlePath}
|
|
18613
|
+
`);
|
|
18614
|
+
(0, import_fs9.rmSync)(tempDir, { recursive: true, force: true });
|
|
18615
|
+
process.exit(1);
|
|
18616
|
+
}
|
|
18617
|
+
let allOk = true;
|
|
18618
|
+
try {
|
|
18619
|
+
const manifestPath = (0, import_path8.join)(tempDir, "manifest.json");
|
|
18620
|
+
const manifestBytes = (0, import_fs9.readFileSync)(manifestPath);
|
|
18621
|
+
const manifest = JSON.parse(manifestBytes.toString());
|
|
18622
|
+
process.stdout.write(`${BOLD7}Manifest${RESET7} (${manifest.sessions.length} session(s), generated ${manifest.generated_at})
|
|
18623
|
+
`);
|
|
18624
|
+
process.stdout.write(`
|
|
18625
|
+
${BOLD7}File integrity${RESET7}
|
|
18626
|
+
`);
|
|
18627
|
+
for (const [rel, expectedHash] of Object.entries(manifest.files)) {
|
|
18628
|
+
const filePath = (0, import_path8.join)(tempDir, rel);
|
|
18629
|
+
try {
|
|
18630
|
+
const content = (0, import_fs9.readFileSync)(filePath);
|
|
18631
|
+
const actual = (0, import_fs9.createHash)("sha256").update(content).digest("hex");
|
|
18632
|
+
if (actual === expectedHash) {
|
|
18633
|
+
ok(rel);
|
|
18634
|
+
} else {
|
|
18635
|
+
fail(`${rel} \u2014 SHA256 mismatch (tampered)`);
|
|
18636
|
+
allOk = false;
|
|
18637
|
+
}
|
|
18638
|
+
} catch {
|
|
18639
|
+
fail(`${rel} \u2014 file missing from bundle`);
|
|
18640
|
+
allOk = false;
|
|
18641
|
+
}
|
|
18642
|
+
}
|
|
18643
|
+
process.stdout.write(`
|
|
18644
|
+
${BOLD7}Signature${RESET7}
|
|
18645
|
+
`);
|
|
18646
|
+
const sigPath = (0, import_path8.join)(tempDir, "manifest.sig");
|
|
18647
|
+
let sigFile = null;
|
|
18648
|
+
try {
|
|
18649
|
+
sigFile = JSON.parse((0, import_fs9.readFileSync)(sigPath).toString());
|
|
18650
|
+
} catch {
|
|
18651
|
+
warn("No manifest.sig \u2014 bundle was not signed");
|
|
18652
|
+
}
|
|
18653
|
+
if (sigFile?.session_id && sigFile?.signature) {
|
|
18654
|
+
const pubKeyPath = (0, import_path8.join)(tempDir, "pubkeys", `${sigFile.session_id}.pub`);
|
|
18655
|
+
try {
|
|
18656
|
+
const pubKey = (0, import_fs9.readFileSync)(pubKeyPath, "utf8");
|
|
18657
|
+
const valid = verifyBufferSignature(pubKey, sigFile.signature, manifestBytes);
|
|
18658
|
+
if (valid) {
|
|
18659
|
+
ok(`Ed25519 signature valid (session ${sigFile.session_id.slice(0, 8)})`);
|
|
18660
|
+
} else {
|
|
18661
|
+
fail("Ed25519 signature INVALID \u2014 manifest was altered after signing");
|
|
18662
|
+
allOk = false;
|
|
18663
|
+
}
|
|
18664
|
+
} catch {
|
|
18665
|
+
fail(`Public key not found for session ${sigFile.session_id.slice(0, 8)}`);
|
|
18666
|
+
allOk = false;
|
|
18667
|
+
}
|
|
18668
|
+
}
|
|
18669
|
+
process.stdout.write("\n");
|
|
18670
|
+
} finally {
|
|
18671
|
+
(0, import_fs9.rmSync)(tempDir, { recursive: true, force: true });
|
|
18672
|
+
}
|
|
18673
|
+
process.exit(allOk ? 0 : 1);
|
|
18674
|
+
}
|
|
18287
18675
|
async function runVerify(args2) {
|
|
18288
|
-
const
|
|
18676
|
+
const bundleArg = args2.find((a) => a.startsWith("--bundle="))?.split("=").slice(1).join("=");
|
|
18677
|
+
if (bundleArg) {
|
|
18678
|
+
await runVerifyBundle(bundleArg);
|
|
18679
|
+
return;
|
|
18680
|
+
}
|
|
18681
|
+
const prefix = args2.find((a) => !a.startsWith("--"));
|
|
18289
18682
|
if (!prefix) {
|
|
18290
|
-
process.stderr.write(
|
|
18683
|
+
process.stderr.write(
|
|
18684
|
+
"Usage: chron verify <session-id-prefix>\n chron verify --bundle=<bundle.chron.tar.gz>\n"
|
|
18685
|
+
);
|
|
18291
18686
|
process.exit(1);
|
|
18292
18687
|
}
|
|
18293
18688
|
const db = await initDb();
|
|
@@ -18329,6 +18724,27 @@ ${BOLD7}Verifying session ${session.id.slice(0, 8)}${RESET7} \u2014 ${session.ti
|
|
|
18329
18724
|
ok(`All ${chained.length} hashes valid`);
|
|
18330
18725
|
}
|
|
18331
18726
|
process.stdout.write(`
|
|
18727
|
+
${BOLD7}Clock attestation${RESET7}
|
|
18728
|
+
`);
|
|
18729
|
+
if (session.metadata) {
|
|
18730
|
+
try {
|
|
18731
|
+
const meta = JSON.parse(session.metadata);
|
|
18732
|
+
const status = meta.clock_sync_status ?? "unknown";
|
|
18733
|
+
if (status === "synchronized") {
|
|
18734
|
+
const offset = meta.clock_offset_ms !== void 0 ? ` (offset: ${meta.clock_offset_ms > 0 ? "+" : ""}${meta.clock_offset_ms}ms)` : "";
|
|
18735
|
+
ok(`Clock synchronized via NTP${offset}`);
|
|
18736
|
+
} else if (status === "not_synchronized") {
|
|
18737
|
+
fail(`Clock NOT synchronized with NTP \u2014 timing analysis unreliable`);
|
|
18738
|
+
} else {
|
|
18739
|
+
warn("Clock sync status unknown (NTP check failed or offline)");
|
|
18740
|
+
}
|
|
18741
|
+
} catch {
|
|
18742
|
+
warn("Could not parse session metadata");
|
|
18743
|
+
}
|
|
18744
|
+
} else {
|
|
18745
|
+
warn("No clock attestation \u2014 session created before NTP support (v0.1.24)");
|
|
18746
|
+
}
|
|
18747
|
+
process.stdout.write(`
|
|
18332
18748
|
${BOLD7}Signature${RESET7}
|
|
18333
18749
|
`);
|
|
18334
18750
|
if (!session.public_key) {
|
|
@@ -18358,18 +18774,22 @@ ${BOLD7}Signature${RESET7}
|
|
|
18358
18774
|
const allOk = chainOk && (session.signature ? true : true);
|
|
18359
18775
|
process.exit(allOk ? 0 : 1);
|
|
18360
18776
|
}
|
|
18361
|
-
var RESET7, BOLD7,
|
|
18777
|
+
var import_fs9, import_path8, import_os8, import_child_process2, RESET7, BOLD7, GREEN4, RED3, DIM6, YELLOW5;
|
|
18362
18778
|
var init_verify = __esm({
|
|
18363
18779
|
"src/cli/verify.ts"() {
|
|
18364
18780
|
"use strict";
|
|
18365
18781
|
init_drizzle_orm();
|
|
18782
|
+
import_fs9 = require("fs");
|
|
18783
|
+
import_path8 = require("path");
|
|
18784
|
+
import_os8 = require("os");
|
|
18785
|
+
import_child_process2 = require("child_process");
|
|
18366
18786
|
init_db2();
|
|
18367
18787
|
init_schema();
|
|
18368
18788
|
init_hash();
|
|
18369
18789
|
init_signing();
|
|
18370
18790
|
RESET7 = "\x1B[0m";
|
|
18371
18791
|
BOLD7 = "\x1B[1m";
|
|
18372
|
-
|
|
18792
|
+
GREEN4 = "\x1B[32m";
|
|
18373
18793
|
RED3 = "\x1B[31m";
|
|
18374
18794
|
DIM6 = "\x1B[2m";
|
|
18375
18795
|
YELLOW5 = "\x1B[33m";
|
|
@@ -18415,6 +18835,11 @@ async function main() {
|
|
|
18415
18835
|
await runSummary2(args);
|
|
18416
18836
|
break;
|
|
18417
18837
|
}
|
|
18838
|
+
case "prune": {
|
|
18839
|
+
const { runPrune: runPrune2 } = await Promise.resolve().then(() => (init_prune(), prune_exports));
|
|
18840
|
+
await runPrune2(args);
|
|
18841
|
+
break;
|
|
18842
|
+
}
|
|
18418
18843
|
case "sign": {
|
|
18419
18844
|
const { runSign: runSign2 } = await Promise.resolve().then(() => (init_sign(), sign_exports));
|
|
18420
18845
|
await runSign2(args);
|
|
@@ -18442,9 +18867,11 @@ Commands:
|
|
|
18442
18867
|
summary Structured summary of a session (timeline, mutations, secrets)
|
|
18443
18868
|
sign Sign a session with its Ed25519 key \u2014 produces a .chron.sig file
|
|
18444
18869
|
verify Verify a session's hash chain and Ed25519 signature
|
|
18870
|
+
prune Delete sessions older than a retention cutoff
|
|
18445
18871
|
|
|
18446
18872
|
Options (history):
|
|
18447
18873
|
--limit=<n> Max sessions to show (default: 20)
|
|
18874
|
+
--ref=<value> Filter by external_ref prefix (e.g. --ref=jira: or --ref=jira:ENG-123)
|
|
18448
18875
|
<id-prefix> Show full log for the session with this ID prefix
|
|
18449
18876
|
|
|
18450
18877
|
Options (report):
|
|
@@ -18452,8 +18879,21 @@ Options (report):
|
|
|
18452
18879
|
--format=soc2 Generate SOC 2 HTML evidence package
|
|
18453
18880
|
--output=<file> Output file for --format=soc2 (default: soc2-report.html)
|
|
18454
18881
|
|
|
18455
|
-
Options (export
|
|
18456
|
-
<id-prefix>
|
|
18882
|
+
Options (export):
|
|
18883
|
+
<id-prefix> Markdown export for a single session
|
|
18884
|
+
--signed Tamper-evident bundle (JSONL + manifest + Ed25519 sig)
|
|
18885
|
+
--session=<id> Filter bundle to a single session
|
|
18886
|
+
--since=<range> Filter bundle by date: 7d, 30d, or YYYY-MM-DD
|
|
18887
|
+
--output=<file> Output path (default: bundle.chron.tar.gz)
|
|
18888
|
+
|
|
18889
|
+
Options (verify):
|
|
18890
|
+
<id-prefix> Verify a session's hash chain + Ed25519 signature
|
|
18891
|
+
--bundle=<file> Verify a signed bundle offline (no DB needed)
|
|
18892
|
+
|
|
18893
|
+
Options (prune):
|
|
18894
|
+
--older-than=<n>d Cutoff in days (falls back to retention_days in config)
|
|
18895
|
+
--dry-run Show what would be deleted without deleting
|
|
18896
|
+
--confirm Required flag to actually delete
|
|
18457
18897
|
`
|
|
18458
18898
|
);
|
|
18459
18899
|
process.exit(command ? 1 : 0);
|