chron-mcp 0.1.20 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js 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 join6(chunks, separator) {
1187
+ function join10(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 = join6;
1197
+ sql2.join = join10;
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 readFileSync3 = (path) => fs.readFileSync(path, "utf-8");
2809
+ var readFileSync7 = (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: readFileSync3,
2821
+ readFileSync: readFileSync7,
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: readFileSync3 } = require_filesystem();
2833
+ var { LDD_PATH, readFile, readFileSync: readFileSync7 } = 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 = readFileSync3(LDD_PATH);
2914
+ const lddContent = readFileSync7(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 = readFileSync3(LDD_PATH);
2971
+ const lddContent = readFileSync7(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 version3 = async () => {
2999
- let version4 = null;
2998
+ var version4 = async () => {
2999
+ let version5 = null;
3000
3000
  if (isLinux()) {
3001
- version4 = await versionFromFilesystem();
3002
- if (!version4) {
3003
- version4 = versionFromReport();
3001
+ version5 = await versionFromFilesystem();
3002
+ if (!version5) {
3003
+ version5 = versionFromReport();
3004
3004
  }
3005
- if (!version4) {
3005
+ if (!version5) {
3006
3006
  const out = await safeCommand();
3007
- version4 = versionFromCommand(out);
3007
+ version5 = versionFromCommand(out);
3008
3008
  }
3009
3009
  }
3010
- return version4;
3010
+ return version5;
3011
3011
  };
3012
3012
  var versionSync = () => {
3013
- let version4 = null;
3013
+ let version5 = null;
3014
3014
  if (isLinux()) {
3015
- version4 = versionFromFilesystemSync();
3016
- if (!version4) {
3017
- version4 = versionFromReport();
3015
+ version5 = versionFromFilesystemSync();
3016
+ if (!version5) {
3017
+ version5 = versionFromReport();
3018
3018
  }
3019
- if (!version4) {
3019
+ if (!version5) {
3020
3020
  const out = safeCommandSync();
3021
- version4 = versionFromCommand(out);
3021
+ version5 = versionFromCommand(out);
3022
3022
  }
3023
3023
  }
3024
- return version4;
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: version3,
3033
+ version: version4,
3034
3034
  versionSync
3035
3035
  };
3036
3036
  }
@@ -3553,9 +3553,9 @@ function executeStmt(db, stmt, intMode) {
3553
3553
  const lastInsertRowid = void 0;
3554
3554
  return new ResultSetImpl(columns, columnTypes, rows, rowsAffected, lastInsertRowid);
3555
3555
  } else {
3556
- const info = sqlStmt.run(args2);
3557
- const rowsAffected = info.changes;
3558
- const lastInsertRowid = BigInt(info.lastInsertRowid);
3556
+ const info2 = sqlStmt.run(args2);
3557
+ const rowsAffected = info2.changes;
3558
+ const lastInsertRowid = BigInt(info2.lastInsertRowid);
3559
3559
  return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid);
3560
3560
  }
3561
3561
  } catch (e) {
@@ -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: createHash4 } = require("crypto");
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 = createHash4("sha1").update(key + GUID).digest("base64");
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: createHash4 } = require("crypto");
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 version3 = +req.headers["sec-websocket-version"];
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 (version3 !== 13 && version3 !== 8) {
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"
@@ -7488,13 +7488,13 @@ var require_websocket_server = __commonJS({
7488
7488
  }
7489
7489
  }
7490
7490
  if (this.options.verifyClient) {
7491
- const info = {
7492
- origin: req.headers[`${version3 === 8 ? "sec-websocket-origin" : "origin"}`],
7491
+ const info2 = {
7492
+ origin: req.headers[`${version4 === 8 ? "sec-websocket-origin" : "origin"}`],
7493
7493
  secure: !!(req.socket.authorized || req.socket.encrypted),
7494
7494
  req
7495
7495
  };
7496
7496
  if (this.options.verifyClient.length === 2) {
7497
- this.options.verifyClient(info, (verified, code, message, headers) => {
7497
+ this.options.verifyClient(info2, (verified, code, message, headers) => {
7498
7498
  if (!verified) {
7499
7499
  return abortHandshake(socket, code || 401, message, headers);
7500
7500
  }
@@ -7510,7 +7510,7 @@ var require_websocket_server = __commonJS({
7510
7510
  });
7511
7511
  return;
7512
7512
  }
7513
- if (!this.options.verifyClient(info))
7513
+ if (!this.options.verifyClient(info2))
7514
7514
  return abortHandshake(socket, 401);
7515
7515
  }
7516
7516
  this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
@@ -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 = createHash4("sha1").update(key + GUID).digest("base64");
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, version3, batch, hranaStmts, disableForeignKeys = false) {
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 (version3 >= 3) {
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 (version3 >= 3) {
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, version3) {
11429
+ constructor(mode, version4) {
11430
11430
  this.#mode = mode;
11431
- this.#version = version3;
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 version3 = await streamState.conn.client.getVersion();
11862
+ const version4 = await streamState.conn.client.getVersion();
11863
11863
  streamState.conn.sqlCache.apply(hranaStmts);
11864
- const batch = streamState.stream.batch(version3 >= 3);
11865
- const resultsPromise = executeHranaBatch(mode, version3, batch, hranaStmts);
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 version3 = await streamState.conn.client.getVersion();
11881
- const batch = streamState.stream.batch(version3 >= 3);
11882
- const resultsPromise = executeHranaBatch("deferred", version3, batch, hranaStmts, true);
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 version3 = await streamState.conn.client.getVersion();
11897
- return new WsTransaction(this, streamState, mode, version3);
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 version3 = await next.client.getVersion();
12005
- next.useSqlCache = version3 >= 2;
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, version3) {
12038
- super(mode, version3);
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 version3 = await this.#client.getVersion();
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, version3, batch, hranaStmts);
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 version3 = await this.#client.getVersion();
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", version3, batch, hranaStmts, true);
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 version3 = await this.#client.getVersion();
12197
- return new HttpTransaction(this.#client.openStream(), mode, version3);
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, version3) {
12244
- super(mode, version3);
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((join6) => join6.alias === tableName)) {
14255
+ if (typeof tableName === "string" && this.config.joins?.some((join10) => join10.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((join6) => join6.alias === tableName)) {
15144
+ if (typeof tableName === "string" && this.config.joins.some((join10) => join10.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(),
@@ -16574,8 +16575,8 @@ function getDbPath() {
16574
16575
  return envPath;
16575
16576
  return (0, import_path.join)((0, import_os.homedir)(), ".chron", "chron.db");
16576
16577
  }
16577
- async function initDb(dbPath2) {
16578
- const path = dbPath2 ?? getDbPath();
16578
+ async function initDb(dbPath3) {
16579
+ const path = dbPath3 ?? getDbPath();
16579
16580
  if (!path.startsWith(":")) {
16580
16581
  (0, import_fs.mkdirSync)((0, import_path.dirname)(path), { recursive: true });
16581
16582
  }
@@ -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
- const rows = await db.select({
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
- process.stdout.write("No sessions found.\n");
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
- const roleColor = msg.role === "user" ? YELLOW : CYAN;
16730
- process.stdout.write(`${DIM}[${ts}]${RESET} ${BOLD}${roleColor}${msg.role}${RESET}
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}
16753
+ `);
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}
16772
+
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}
16731
16783
  `);
16732
- process.stdout.write(`${msg.content}
16784
+ process.stdout.write(`${msg.content}
16733
16785
 
16734
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
 
@@ -16834,14 +16890,14 @@ async function queryIntegrity(db) {
16834
16890
  unchained++;
16835
16891
  continue;
16836
16892
  }
16837
- let ok2 = true;
16893
+ let ok3 = true;
16838
16894
  for (let i = 1; i < chained.length; i++) {
16839
16895
  if (chained[i].prev_hash !== chained[i - 1].content_hash) {
16840
- ok2 = false;
16896
+ ok3 = false;
16841
16897
  break;
16842
16898
  }
16843
16899
  }
16844
- if (ok2)
16900
+ if (ok3)
16845
16901
  intact++;
16846
16902
  else {
16847
16903
  broken++;
@@ -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, cutoffDate) {
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 = cutoffDate ? allSessions.filter((s) => s.updated_at >= cutoffDate) : allSessions;
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 = cutoffDate ?? (filtered.length > 0 ? filtered.map((s) => s.updated_at).sort()[0].slice(0, 10) : dateTo);
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 cutoffDate = null;
17272
+ let cutoffDate2 = null;
17217
17273
  if (sinceArg) {
17218
17274
  try {
17219
- cutoffDate = parseSince(sinceArg);
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, cutoffDate);
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.25",
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("Usage: chron export <session-id-prefix>\n");
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, import_path2.join)((0, import_os3.homedir)(), ".chron", "chron.db");
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 import_os3, import_path2, RESET3, BOLD3, DIM2, CYAN2;
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
- import_os3 = require("os");
17334
- import_path2 = require("path");
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, import_path3.join)((0, import_os4.homedir)(), ".chron", "config.json");
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, import_fs3.readFileSync)(configPath(), "utf8"));
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, import_path3.join)((0, import_os4.homedir)(), ".chron");
17520
- (0, import_fs3.mkdirSync)(dir, { recursive: true });
17521
- (0, import_fs3.writeFileSync)(configPath(), JSON.stringify(data, null, 2), "utf8");
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, import_path3.join)((0, import_os4.homedir)(), ".claude.json");
17525
- if (!(0, import_fs3.existsSync)(path))
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, import_fs3.readFileSync)(path, "utf8");
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, import_fs3.writeFileSync)(path, JSON.stringify(doc, null, 2), "utf8");
17792
+ (0, import_fs5.writeFileSync)(path, JSON.stringify(doc, null, 2), "utf8");
17538
17793
  return true;
17539
17794
  } catch {
17540
17795
  return false;
@@ -17588,7 +17843,7 @@ ${DIM4}Sending test event...${RESET5} `);
17588
17843
  }
17589
17844
  }]
17590
17845
  }]);
17591
- let ok2 = false;
17846
+ let ok3 = false;
17592
17847
  let statusCode = 0;
17593
17848
  try {
17594
17849
  const res = await fetch(url, {
@@ -17600,7 +17855,7 @@ ${DIM4}Sending test event...${RESET5} `);
17600
17855
  body: testPayload
17601
17856
  });
17602
17857
  statusCode = res.status;
17603
- ok2 = res.ok;
17858
+ ok3 = res.ok;
17604
17859
  } catch (e) {
17605
17860
  process.stdout.write(`${RED}failed${RESET5}
17606
17861
  `);
@@ -17609,7 +17864,7 @@ ${DIM4}Sending test event...${RESET5} `);
17609
17864
  `);
17610
17865
  process.exit(1);
17611
17866
  }
17612
- if (!ok2) {
17867
+ if (!ok3) {
17613
17868
  process.stdout.write(`${RED}failed (HTTP ${statusCode})${RESET5}
17614
17869
  `);
17615
17870
  process.stderr.write(` ${RED}Check your URL and token, then try again.${RESET5}
@@ -17617,13 +17872,13 @@ ${DIM4}Sending test event...${RESET5} `);
17617
17872
  `);
17618
17873
  process.exit(1);
17619
17874
  }
17620
- process.stdout.write(`${GREEN}OK${RESET5}
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(`${GREEN}${BOLD5}Connected!${RESET5} Test event appeared in LogScale.
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(`${GREEN}OK${RESET5}
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(`${GREEN}${BOLD5}Connected!${RESET5} Splunk is receiving chron events.
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(`${GREEN}OK${RESET5}
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(`${GREEN}OK${RESET5}
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(`${GREEN}${BOLD5}Connected!${RESET5} Test event sent to Sentinel workspace.
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, import_os4, import_path3, import_fs3, RESET5, BOLD5, DIM4, CYAN4, GREEN, RED, YELLOW3;
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
- import_os4 = require("os");
17948
- import_path3 = require("path");
17949
- import_fs3 = require("fs");
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
- GREEN = "\x1B[32m";
18209
+ GREEN2 = "\x1B[32m";
17955
18210
  RED = "\x1B[31m";
17956
18211
  YELLOW3 = "\x1B[33m";
17957
18212
  }
@@ -17980,8 +18235,8 @@ function formatLatency(ms) {
17980
18235
  function formatTs(iso) {
17981
18236
  return iso.replace("T", " ").replace(/\.\d+.*$/, "");
17982
18237
  }
17983
- async function buildSummary(sessionPrefix) {
17984
- const db = await initDb();
18238
+ async function buildSummary(sessionPrefix, existingDb) {
18239
+ const db = existingDb ?? await initDb();
17985
18240
  const allSessions = await db.select().from(sessions);
17986
18241
  const session = allSessions.find((s) => s.id.startsWith(sessionPrefix));
17987
18242
  if (!session)
@@ -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 ? `${GREEN2}none${RESET6}` : `${RED2}${secrets.length} detection(s)${RESET6}`}
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, GREEN2, YELLOW4, RED2, MUTATION_PATTERNS, PROD_PATTERNS;
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
- GREEN2 = "\x1B[32m";
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/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`);
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 sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt) {
18139
- const input = `${sessionId}|${finalContentHash}|${messageCount}|${firstCreatedAt}`;
18140
- return (0, import_crypto2.createHash)("sha256").update(input).digest();
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 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}`);
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
- 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;
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
+ `);
18159
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
+ `);
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 import_crypto2, import_fs4, import_path4, import_os5;
18162
- var init_signing = __esm({
18163
- "src/utils/signing.ts"() {
18474
+ var import_fs6, import_path6, import_os7;
18475
+ var init_prune = __esm({
18476
+ "src/cli/prune.ts"() {
18164
18477
  "use strict";
18165
- import_crypto2 = require("crypto");
18166
- import_fs4 = require("fs");
18167
- import_path4 = require("path");
18168
- import_os5 = require("os");
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, import_fs6.existsSync)(privKeyPath(session.id))) {
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, import_path5.join)(process.cwd(), `${session.id.slice(0, 8)}.chron.sig`);
18232
- (0, import_fs5.writeFileSync)(sigFile, JSON.stringify(sigData, null, 2));
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 import_fs5, import_path5, import_fs6;
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
- import_fs5 = require("fs");
18248
- import_path5 = require("path");
18562
+ import_fs7 = require("fs");
18563
+ import_path7 = require("path");
18249
18564
  init_db2();
18250
18565
  init_schema();
18251
18566
  init_signing();
18252
- import_fs6 = require("fs");
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, import_crypto3.createHash)("sha256").update(input).digest("hex");
18575
+ return (0, import_crypto4.createHash)("sha256").update(input).digest("hex");
18261
18576
  }
18262
- var import_crypto3;
18577
+ var import_crypto4;
18263
18578
  var init_hash = __esm({
18264
18579
  "src/utils/hash.ts"() {
18265
18580
  "use strict";
18266
- import_crypto3 = require("crypto");
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(` ${GREEN3}\u2713${RESET7} ${msg}
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 prefix = args2[0];
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("Usage: chron verify <session-id-prefix>\n");
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,24 +18774,374 @@ ${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, GREEN3, RED3, DIM6, YELLOW5;
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
- GREEN3 = "\x1B[32m";
18792
+ GREEN4 = "\x1B[32m";
18373
18793
  RED3 = "\x1B[31m";
18374
18794
  DIM6 = "\x1B[2m";
18375
18795
  YELLOW5 = "\x1B[33m";
18376
18796
  }
18377
18797
  });
18378
18798
 
18799
+ // src/cli/doctor.ts
18800
+ var doctor_exports = {};
18801
+ __export(doctor_exports, {
18802
+ runDoctor: () => runDoctor
18803
+ });
18804
+ function ok2(label, detail = "") {
18805
+ process.stdout.write(` ${GREEN5}\u2713${RESET8} ${label}${detail ? ` ${DIM7}${detail}${RESET8}` : ""}
18806
+ `);
18807
+ }
18808
+ function fail2(label, detail = "") {
18809
+ process.stdout.write(` ${RED4}\u2717${RESET8} ${label}${detail ? ` ${DIM7}${detail}${RESET8}` : ""}
18810
+ `);
18811
+ }
18812
+ function warn2(label, detail = "") {
18813
+ process.stdout.write(` ${YELLOW6}!${RESET8} ${label}${detail ? ` ${DIM7}${detail}${RESET8}` : ""}
18814
+ `);
18815
+ }
18816
+ function info(label, detail = "") {
18817
+ process.stdout.write(` ${CYAN6}\xB7${RESET8} ${label}${detail ? ` ${DIM7}${detail}${RESET8}` : ""}
18818
+ `);
18819
+ }
18820
+ function section(title) {
18821
+ process.stdout.write(`
18822
+ ${BOLD8}${title}${RESET8}
18823
+ `);
18824
+ }
18825
+ function canWrite(dir) {
18826
+ try {
18827
+ (0, import_fs10.accessSync)(dir, import_fs10.constants.W_OK);
18828
+ return true;
18829
+ } catch {
18830
+ return false;
18831
+ }
18832
+ }
18833
+ function testWriteFile(path) {
18834
+ const probe = path + ".chron-probe";
18835
+ try {
18836
+ (0, import_fs10.writeFileSync)(probe, "ok");
18837
+ (0, import_fs10.unlinkSync)(probe);
18838
+ return true;
18839
+ } catch {
18840
+ return false;
18841
+ }
18842
+ }
18843
+ function dbPath2() {
18844
+ return process.env.CHRON_DB_PATH ?? (0, import_path9.join)((0, import_os9.homedir)(), ".chron", "chron.db");
18845
+ }
18846
+ function keysDir2() {
18847
+ return (0, import_path9.join)((0, import_os9.homedir)(), ".chron", "keys");
18848
+ }
18849
+ function loadConfig2() {
18850
+ try {
18851
+ return JSON.parse((0, import_fs10.readFileSync)((0, import_path9.join)((0, import_os9.homedir)(), ".chron", "config.json"), "utf8"));
18852
+ } catch {
18853
+ return {};
18854
+ }
18855
+ }
18856
+ function mcpConfigs() {
18857
+ const home = (0, import_os9.homedir)();
18858
+ const plat = (0, import_os9.platform)();
18859
+ const candidates = [
18860
+ {
18861
+ name: "Claude Desktop",
18862
+ path: plat === "win32" ? (0, import_path9.join)(process.env.APPDATA ?? (0, import_path9.join)(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : (0, import_path9.join)(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
18863
+ },
18864
+ { name: "Claude Code", path: (0, import_path9.join)(home, ".claude.json") },
18865
+ { name: "Cursor", path: (0, import_path9.join)(home, ".cursor", "mcp.json") },
18866
+ { name: "Windsurf", path: (0, import_path9.join)(home, ".codeium", "windsurf", "mcp_config.json") }
18867
+ ];
18868
+ return candidates.map((c) => {
18869
+ if (!(0, import_fs10.existsSync)(c.path)) {
18870
+ return { name: c.name, path: c.path, exists: false, chronConfigured: null };
18871
+ }
18872
+ try {
18873
+ const raw = JSON.parse((0, import_fs10.readFileSync)(c.path, "utf8"));
18874
+ const servers = raw.mcpServers ?? {};
18875
+ const configured = Object.keys(servers).some(
18876
+ (k) => k === "chron" || k === "chron-mcp" || String(servers[k]?.command ?? "").includes("chron")
18877
+ );
18878
+ return { name: c.name, path: c.path, exists: true, chronConfigured: configured };
18879
+ } catch {
18880
+ return { name: c.name, path: c.path, exists: true, chronConfigured: null, note: "Could not parse config" };
18881
+ }
18882
+ });
18883
+ }
18884
+ function npmLatestVersion() {
18885
+ try {
18886
+ return (0, import_child_process3.execSync)("npm view chron-mcp version --json 2>/dev/null", { timeout: 8e3 }).toString().trim().replace(/"/g, "");
18887
+ } catch {
18888
+ return null;
18889
+ }
18890
+ }
18891
+ function npxWorks() {
18892
+ try {
18893
+ (0, import_child_process3.execSync)("npx chron-mcp --version 2>/dev/null", { timeout: 8e3 });
18894
+ return true;
18895
+ } catch {
18896
+ return false;
18897
+ }
18898
+ }
18899
+ async function httpHealthCheck(port = 3001) {
18900
+ try {
18901
+ const controller = new AbortController();
18902
+ const timer = setTimeout(() => controller.abort(), 2e3);
18903
+ const res = await fetch(`http://localhost:${port}/health`, { signal: controller.signal });
18904
+ clearTimeout(timer);
18905
+ if (!res.ok)
18906
+ return { running: false };
18907
+ const body = await res.json();
18908
+ return { running: true, version: body.version };
18909
+ } catch {
18910
+ return { running: false };
18911
+ }
18912
+ }
18913
+ async function runDoctor(args2) {
18914
+ const jsonMode = args2.includes("--json");
18915
+ const port = parseInt(process.env.PORT ?? "3001", 10);
18916
+ const results = [];
18917
+ if (!jsonMode)
18918
+ process.stdout.write(`
18919
+ ${BOLD8}chron doctor${RESET8} ${DIM7}v${import_package2.version}${RESET8}
18920
+ `);
18921
+ if (!jsonMode)
18922
+ section("Runtime");
18923
+ const nodeVer = process.version.replace("v", "");
18924
+ const [nodeMajor] = nodeVer.split(".").map(Number);
18925
+ const nodeOk = (nodeMajor ?? 0) >= 18;
18926
+ results.push({ pass: nodeOk, label: `Node.js ${process.version}`, fix: nodeOk ? void 0 : "Upgrade Node.js to v18 or newer: https://nodejs.org" });
18927
+ if (!jsonMode)
18928
+ (nodeOk ? ok2 : fail2)(`Node.js ${process.version}`, nodeOk ? "" : "requires \u2265 18");
18929
+ const latestVersion = npmLatestVersion();
18930
+ if (latestVersion) {
18931
+ const upToDate = import_package2.version === latestVersion;
18932
+ results.push({
18933
+ pass: upToDate,
18934
+ label: `chron-mcp ${import_package2.version}${upToDate ? "" : ` (latest: ${latestVersion})`}`,
18935
+ fix: upToDate ? void 0 : `npm install -g chron-mcp@latest`
18936
+ });
18937
+ if (!jsonMode)
18938
+ (upToDate ? ok2 : warn2)(`chron-mcp ${import_package2.version}`, upToDate ? "up to date" : `latest is ${latestVersion} \u2014 run: npm install -g chron-mcp@latest`);
18939
+ } else {
18940
+ results.push({ pass: "warn", label: `chron-mcp ${import_package2.version}`, detail: "Could not reach npm to check for updates" });
18941
+ if (!jsonMode)
18942
+ warn2(`chron-mcp ${import_package2.version}`, "could not check npm for updates");
18943
+ }
18944
+ const npxOk = npxWorks();
18945
+ results.push({ pass: npxOk, label: "npx chron-mcp --version", fix: npxOk ? void 0 : "Run: npm install -g chron-mcp" });
18946
+ if (!jsonMode)
18947
+ (npxOk ? ok2 : fail2)("npx chron-mcp --version", npxOk ? "works" : "failed \u2014 run: npm install -g chron-mcp");
18948
+ if (!jsonMode)
18949
+ section("Storage");
18950
+ const db = dbPath2();
18951
+ const dbDir = (0, import_path9.join)(db, "..");
18952
+ const dbDirExists = (0, import_fs10.existsSync)(dbDir);
18953
+ if (!dbDirExists) {
18954
+ try {
18955
+ (0, import_fs10.mkdirSync)(dbDir, { recursive: true });
18956
+ } catch {
18957
+ }
18958
+ }
18959
+ const dbWritable = testWriteFile(dbDir);
18960
+ const dbExists = (0, import_fs10.existsSync)(db);
18961
+ results.push({
18962
+ pass: dbWritable,
18963
+ label: `DB directory: ${dbDir}`,
18964
+ detail: dbExists ? "database found" : "no database yet (will be created on first use)",
18965
+ fix: dbWritable ? void 0 : `mkdir -p "${dbDir}" && chmod 700 "${dbDir}"`
18966
+ });
18967
+ if (!jsonMode)
18968
+ (dbWritable ? ok2 : fail2)(`DB: ${db}`, dbExists ? "found" : "not yet created \u2014 will init on first use");
18969
+ if (!dbWritable && !jsonMode)
18970
+ info("fix", `mkdir -p "${dbDir}" && chmod 700 "${dbDir}"`);
18971
+ const keys = keysDir2();
18972
+ const keysExist = (0, import_fs10.existsSync)(keys);
18973
+ if (!keysExist) {
18974
+ try {
18975
+ (0, import_fs10.mkdirSync)(keys, { recursive: true });
18976
+ } catch {
18977
+ }
18978
+ }
18979
+ const keysWritable = canWrite(keys);
18980
+ results.push({
18981
+ pass: keysWritable,
18982
+ label: `Keys directory: ${keys}`,
18983
+ fix: keysWritable ? void 0 : `mkdir -p "${keys}" && chmod 700 "${keys}"`
18984
+ });
18985
+ if (!jsonMode)
18986
+ (keysWritable ? ok2 : fail2)(`Keys: ${keys}`, keysWritable ? "" : "not writable \u2014 Ed25519 signing will fail");
18987
+ if (!jsonMode)
18988
+ section("MCP Tool Configurations");
18989
+ const tools = mcpConfigs();
18990
+ const foundTools = tools.filter((t) => t.exists);
18991
+ if (foundTools.length === 0) {
18992
+ results.push({ pass: "warn", label: "No MCP tool config files found", fix: "Install Claude Desktop or Claude Code and add chron to mcpServers" });
18993
+ if (!jsonMode)
18994
+ warn2("No MCP tool config files detected (Claude Desktop, Claude Code, Cursor, Windsurf)");
18995
+ }
18996
+ for (const t of tools) {
18997
+ if (!t.exists) {
18998
+ results.push({ pass: "skip", label: `${t.name}: not installed` });
18999
+ if (!jsonMode)
19000
+ info(`${t.name}`, "not installed");
19001
+ continue;
19002
+ }
19003
+ if (t.chronConfigured === null) {
19004
+ results.push({ pass: "warn", label: `${t.name}: config parse error`, detail: t.note });
19005
+ if (!jsonMode)
19006
+ warn2(`${t.name}`, t.note ?? "config parse error");
19007
+ } else if (t.chronConfigured) {
19008
+ results.push({ pass: true, label: `${t.name}: chron configured` });
19009
+ if (!jsonMode)
19010
+ ok2(`${t.name}`, "chron configured");
19011
+ } else {
19012
+ results.push({
19013
+ pass: "warn",
19014
+ label: `${t.name}: chron NOT in mcpServers`,
19015
+ fix: `Add chron to mcpServers in ${t.path}
19016
+ See: https://github.com/sirinivask/chron#installation`
19017
+ });
19018
+ if (!jsonMode)
19019
+ warn2(`${t.name}`, `chron not in mcpServers \u2014 add it to ${t.path}`);
19020
+ }
19021
+ }
19022
+ if (!jsonMode)
19023
+ section("HTTP Mode");
19024
+ const health = await httpHealthCheck(port);
19025
+ if (health.running) {
19026
+ results.push({ pass: true, label: `HTTP server responding on port ${port}`, detail: health.version ? `v${health.version}` : void 0 });
19027
+ if (!jsonMode)
19028
+ ok2(`HTTP /health on port ${port}`, health.version ? `v${health.version}` : "");
19029
+ } else {
19030
+ results.push({ pass: "skip", label: `HTTP server not running on port ${port}`, detail: "optional \u2014 only needed for ChatGPT / non-stdio MCP clients" });
19031
+ if (!jsonMode)
19032
+ info(`HTTP server not running on port ${port}`, "optional \u2014 start with CHRON_TRANSPORT=http npx chron-mcp");
19033
+ }
19034
+ if (!jsonMode)
19035
+ section("SIEM Integrations");
19036
+ const config = loadConfig2();
19037
+ const siems = [
19038
+ {
19039
+ name: "Splunk",
19040
+ envOk: !!(process.env.CHRON_SPLUNK_URL && process.env.CHRON_SPLUNK_TOKEN),
19041
+ cfgOk: !!(config.splunk && config.splunk.url)
19042
+ },
19043
+ {
19044
+ name: "Microsoft Sentinel",
19045
+ envOk: !!(process.env.CHRON_SENTINEL_DCE && process.env.CHRON_SENTINEL_CLIENT_ID),
19046
+ cfgOk: !!(config.sentinel && config.sentinel.dce)
19047
+ },
19048
+ {
19049
+ name: "CrowdStrike LogScale",
19050
+ envOk: !!(process.env.CHRON_LOGSCALE_URL && process.env.CHRON_LOGSCALE_TOKEN),
19051
+ cfgOk: !!(config.logscale && config.logscale.url)
19052
+ }
19053
+ ];
19054
+ let anySiem = false;
19055
+ for (const s of siems) {
19056
+ if (s.envOk || s.cfgOk) {
19057
+ anySiem = true;
19058
+ results.push({ pass: true, label: `${s.name} connected`, detail: s.envOk ? "via env" : "via ~/.chron/config.json" });
19059
+ if (!jsonMode)
19060
+ ok2(`${s.name}`, s.envOk ? "via env vars" : "via ~/.chron/config.json");
19061
+ } else {
19062
+ results.push({ pass: "skip", label: `${s.name}: not configured`, detail: "optional" });
19063
+ if (!jsonMode)
19064
+ info(`${s.name}`, "not configured \u2014 run: chron connect splunk / sentinel / crowdstrike");
19065
+ }
19066
+ }
19067
+ if (!anySiem && !jsonMode) {
19068
+ process.stdout.write(` ${DIM7}No SIEM configured. Events will only be stored locally.${RESET8}
19069
+ `);
19070
+ }
19071
+ const failures = results.filter((r) => r.pass === false);
19072
+ const warnings = results.filter((r) => r.pass === "warn");
19073
+ if (!jsonMode) {
19074
+ process.stdout.write("\n");
19075
+ if (failures.length === 0 && warnings.length === 0) {
19076
+ process.stdout.write(`${GREEN5}${BOLD8}All checks passed.${RESET8} Chron is correctly set up.
19077
+
19078
+ `);
19079
+ } else {
19080
+ if (failures.length > 0) {
19081
+ process.stdout.write(`${RED4}${BOLD8}${failures.length} issue(s) need attention:${RESET8}
19082
+ `);
19083
+ for (const f of failures) {
19084
+ process.stdout.write(` ${RED4}\u2717${RESET8} ${f.label}
19085
+ `);
19086
+ if (f.fix)
19087
+ process.stdout.write(` ${DIM7}\u2192 ${f.fix}${RESET8}
19088
+ `);
19089
+ }
19090
+ process.stdout.write("\n");
19091
+ }
19092
+ if (warnings.length > 0) {
19093
+ process.stdout.write(`${YELLOW6}${BOLD8}${warnings.length} warning(s):${RESET8}
19094
+ `);
19095
+ for (const w of warnings) {
19096
+ process.stdout.write(` ${YELLOW6}!${RESET8} ${w.label}
19097
+ `);
19098
+ if (w.fix)
19099
+ process.stdout.write(` ${DIM7}\u2192 ${w.fix}${RESET8}
19100
+ `);
19101
+ }
19102
+ process.stdout.write("\n");
19103
+ }
19104
+ }
19105
+ }
19106
+ if (jsonMode) {
19107
+ process.stdout.write(JSON.stringify({
19108
+ chron_version: import_package2.version,
19109
+ node_version: process.version,
19110
+ checks: results.map((r) => ({
19111
+ label: r.label,
19112
+ pass: r.pass,
19113
+ detail: r.detail,
19114
+ fix: r.fix
19115
+ })),
19116
+ summary: {
19117
+ failures: failures.length,
19118
+ warnings: warnings.length,
19119
+ ok: results.filter((r) => r.pass === true).length,
19120
+ skipped: results.filter((r) => r.pass === "skip").length
19121
+ }
19122
+ }, null, 2) + "\n");
19123
+ }
19124
+ process.exit(failures.length > 0 ? 1 : 0);
19125
+ }
19126
+ var import_os9, import_path9, import_fs10, import_child_process3, import_package2, RESET8, BOLD8, DIM7, GREEN5, RED4, YELLOW6, CYAN6;
19127
+ var init_doctor = __esm({
19128
+ "src/cli/doctor.ts"() {
19129
+ "use strict";
19130
+ import_os9 = require("os");
19131
+ import_path9 = require("path");
19132
+ import_fs10 = require("fs");
19133
+ import_child_process3 = require("child_process");
19134
+ import_package2 = __toESM(require_package());
19135
+ RESET8 = "\x1B[0m";
19136
+ BOLD8 = "\x1B[1m";
19137
+ DIM7 = "\x1B[2m";
19138
+ GREEN5 = "\x1B[32m";
19139
+ RED4 = "\x1B[31m";
19140
+ YELLOW6 = "\x1B[33m";
19141
+ CYAN6 = "\x1B[36m";
19142
+ }
19143
+ });
19144
+
18379
19145
  // src/cli/index.ts
18380
19146
  var [, , command, ...args] = process.argv;
18381
19147
  async function main() {
@@ -18415,6 +19181,11 @@ async function main() {
18415
19181
  await runSummary2(args);
18416
19182
  break;
18417
19183
  }
19184
+ case "prune": {
19185
+ const { runPrune: runPrune2 } = await Promise.resolve().then(() => (init_prune(), prune_exports));
19186
+ await runPrune2(args);
19187
+ break;
19188
+ }
18418
19189
  case "sign": {
18419
19190
  const { runSign: runSign2 } = await Promise.resolve().then(() => (init_sign(), sign_exports));
18420
19191
  await runSign2(args);
@@ -18425,6 +19196,11 @@ async function main() {
18425
19196
  await runVerify2(args);
18426
19197
  break;
18427
19198
  }
19199
+ case "doctor": {
19200
+ const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
19201
+ await runDoctor2(args);
19202
+ break;
19203
+ }
18428
19204
  default: {
18429
19205
  const name = command ? `Unknown command: ${command}
18430
19206
 
@@ -18442,9 +19218,12 @@ Commands:
18442
19218
  summary Structured summary of a session (timeline, mutations, secrets)
18443
19219
  sign Sign a session with its Ed25519 key \u2014 produces a .chron.sig file
18444
19220
  verify Verify a session's hash chain and Ed25519 signature
19221
+ prune Delete sessions older than a retention cutoff
19222
+ doctor Check your Chron setup \u2014 Node version, DB, MCP configs, SIEM
18445
19223
 
18446
19224
  Options (history):
18447
19225
  --limit=<n> Max sessions to show (default: 20)
19226
+ --ref=<value> Filter by external_ref prefix (e.g. --ref=jira: or --ref=jira:ENG-123)
18448
19227
  <id-prefix> Show full log for the session with this ID prefix
18449
19228
 
18450
19229
  Options (report):
@@ -18452,8 +19231,24 @@ Options (report):
18452
19231
  --format=soc2 Generate SOC 2 HTML evidence package
18453
19232
  --output=<file> Output file for --format=soc2 (default: soc2-report.html)
18454
19233
 
18455
- Options (export / secrets / sign / verify):
18456
- <id-prefix> Scope to a single session
19234
+ Options (export):
19235
+ <id-prefix> Markdown export for a single session
19236
+ --signed Tamper-evident bundle (JSONL + manifest + Ed25519 sig)
19237
+ --session=<id> Filter bundle to a single session
19238
+ --since=<range> Filter bundle by date: 7d, 30d, or YYYY-MM-DD
19239
+ --output=<file> Output path (default: bundle.chron.tar.gz)
19240
+
19241
+ Options (verify):
19242
+ <id-prefix> Verify a session's hash chain + Ed25519 signature
19243
+ --bundle=<file> Verify a signed bundle offline (no DB needed)
19244
+
19245
+ Options (prune):
19246
+ --older-than=<n>d Cutoff in days (falls back to retention_days in config)
19247
+ --dry-run Show what would be deleted without deleting
19248
+ --confirm Required flag to actually delete
19249
+
19250
+ Options (doctor):
19251
+ --json Machine-readable JSON output
18457
19252
  `
18458
19253
  );
18459
19254
  process.exit(command ? 1 : 0);