chron-mcp 0.1.19 → 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/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 join4(chunks, separator) {
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 = join4;
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 readFileSync2 = (path) => fs.readFileSync(path, "utf-8");
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: readFileSync2,
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: readFileSync2 } = require_filesystem();
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 = readFileSync2(LDD_PATH);
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 = readFileSync2(LDD_PATH);
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 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
  }
@@ -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: createHash2 } = 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 = createHash2("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: createHash2 } = 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"
@@ -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[`${version3 === 8 ? "sec-websocket-origin" : "origin"}`],
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 = createHash2("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((join4) => join4.alias === tableName)) {
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((join4) => join4.alias === tableName)) {
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") {
@@ -16541,7 +16541,10 @@ var init_schema = __esm({
16541
16541
  created_at: text("created_at").notNull(),
16542
16542
  updated_at: text("updated_at").notNull(),
16543
16543
  parent_session_id: text("parent_session_id"),
16544
- external_ref: text("external_ref")
16544
+ external_ref: text("external_ref"),
16545
+ public_key: text("public_key"),
16546
+ signature: text("signature"),
16547
+ metadata: text("metadata")
16545
16548
  });
16546
16549
  messages = sqliteTable("messages", {
16547
16550
  id: text("id").primaryKey(),
@@ -16602,6 +16605,15 @@ async function initDb(dbPath2) {
16602
16605
  if (!sessCols.includes("external_ref")) {
16603
16606
  await client.execute("ALTER TABLE sessions ADD COLUMN external_ref TEXT");
16604
16607
  }
16608
+ if (!sessCols.includes("public_key")) {
16609
+ await client.execute("ALTER TABLE sessions ADD COLUMN public_key TEXT");
16610
+ }
16611
+ if (!sessCols.includes("signature")) {
16612
+ await client.execute("ALTER TABLE sessions ADD COLUMN signature TEXT");
16613
+ }
16614
+ if (!sessCols.includes("metadata")) {
16615
+ await client.execute("ALTER TABLE sessions ADD COLUMN metadata TEXT");
16616
+ }
16605
16617
  return drizzle(client, { schema: schema_exports });
16606
16618
  }
16607
16619
  var import_os, import_fs, import_path, CREATE_SQL;
@@ -16622,7 +16634,10 @@ var init_db2 = __esm({
16622
16634
  created_at TEXT NOT NULL,
16623
16635
  updated_at TEXT NOT NULL,
16624
16636
  parent_session_id TEXT,
16625
- external_ref TEXT
16637
+ external_ref TEXT,
16638
+ public_key TEXT,
16639
+ signature TEXT,
16640
+ metadata TEXT
16626
16641
  )`,
16627
16642
  `CREATE TABLE IF NOT EXISTS messages (
16628
16643
  id TEXT PRIMARY KEY,
@@ -16668,16 +16683,23 @@ function fmtTimestamp(iso) {
16668
16683
  const tz = !m[3] || m[3] === "Z" ? "+00:00" : m[3];
16669
16684
  return `${m[1]} ${m[2]} ${tz}`;
16670
16685
  }
16671
- async function printList(limit) {
16686
+ async function printList(limit, refFilter) {
16672
16687
  const db = await initDb();
16673
- const rows = await db.select({
16688
+ let rows = await db.select({
16674
16689
  id: sessions.id,
16675
16690
  title: sessions.title,
16676
16691
  updated_at: sessions.updated_at,
16692
+ external_ref: sessions.external_ref,
16677
16693
  message_count: sql`count(${messages.id})`
16678
- }).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
+ }
16679
16699
  if (rows.length === 0) {
16680
- 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);
16681
16703
  return;
16682
16704
  }
16683
16705
  process.stdout.write("\n");
@@ -16685,8 +16707,9 @@ async function printList(limit) {
16685
16707
  const date = fmtDate(row.updated_at);
16686
16708
  const prefix = row.id.slice(0, 8);
16687
16709
  const count = String(row.message_count).padStart(3);
16710
+ const ref = row.external_ref ? ` ${DIM}[${row.external_ref}]${RESET}` : "";
16688
16711
  process.stdout.write(
16689
- ` ${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}
16690
16713
  `
16691
16714
  );
16692
16715
  }
@@ -16716,25 +16739,66 @@ ${BOLD}${session.title}${RESET}
16716
16739
  `);
16717
16740
  for (const msg of msgs) {
16718
16741
  const ts = fmtTimestamp(msg.created_at);
16719
- const roleColor = msg.role === "user" ? YELLOW : CYAN;
16720
- 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}
16721
16758
  `);
16722
- process.stdout.write(`${msg.content}
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}
16723
16772
 
16724
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
+ }
16725
16788
  }
16726
16789
  }
16727
16790
  async function runHistory(args2) {
16728
16791
  const limitArg = args2.find((a) => a.startsWith("--limit="));
16729
16792
  const limit = limitArg ? parseInt(limitArg.split("=")[1], 10) : 20;
16793
+ const refArg = args2.find((a) => a.startsWith("--ref="))?.split("=").slice(1).join("=");
16730
16794
  const positional = args2.filter((a) => !a.startsWith("--"));
16731
16795
  if (positional.length === 0) {
16732
- await printList(limit);
16796
+ await printList(limit, refArg);
16733
16797
  } else {
16734
16798
  await printDetail(positional[0]);
16735
16799
  }
16736
16800
  }
16737
- var RESET, BOLD, DIM, CYAN, YELLOW;
16801
+ var RESET, BOLD, DIM, CYAN, YELLOW, GREEN, MAGENTA;
16738
16802
  var init_history = __esm({
16739
16803
  "src/cli/history.ts"() {
16740
16804
  "use strict";
@@ -16746,6 +16810,8 @@ var init_history = __esm({
16746
16810
  DIM = "\x1B[2m";
16747
16811
  CYAN = "\x1B[36m";
16748
16812
  YELLOW = "\x1B[33m";
16813
+ GREEN = "\x1B[32m";
16814
+ MAGENTA = "\x1B[35m";
16749
16815
  }
16750
16816
  });
16751
16817
 
@@ -16824,14 +16890,14 @@ async function queryIntegrity(db) {
16824
16890
  unchained++;
16825
16891
  continue;
16826
16892
  }
16827
- let ok = true;
16893
+ let ok2 = true;
16828
16894
  for (let i = 1; i < chained.length; i++) {
16829
16895
  if (chained[i].prev_hash !== chained[i - 1].content_hash) {
16830
- ok = false;
16896
+ ok2 = false;
16831
16897
  break;
16832
16898
  }
16833
16899
  }
16834
- if (ok)
16900
+ if (ok2)
16835
16901
  intact++;
16836
16902
  else {
16837
16903
  broken++;
@@ -17127,15 +17193,15 @@ function parseSince(arg) {
17127
17193
  }
17128
17194
  throw new Error(`Invalid --since value: "${arg}". Use 7d, 30d, or YYYY-MM-DD.`);
17129
17195
  }
17130
- async function buildReport(db, cutoffDate) {
17196
+ async function buildReport(db, cutoffDate2) {
17131
17197
  const allSessions = await db.select({
17132
17198
  id: sessions.id,
17133
17199
  ai_tool: sessions.ai_tool,
17134
17200
  updated_at: sessions.updated_at
17135
17201
  }).from(sessions);
17136
- const filtered = cutoffDate ? allSessions.filter((s) => s.updated_at >= cutoffDate) : allSessions;
17202
+ const filtered = cutoffDate2 ? allSessions.filter((s) => s.updated_at >= cutoffDate2) : allSessions;
17137
17203
  const dateTo = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
17138
- 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);
17139
17205
  if (filtered.length === 0) {
17140
17206
  return { dateFrom, dateTo, sessionCount: 0, providerCounts: {}, userMessages: 0, aiMessages: 0, secretsTotal: 0, byType: [] };
17141
17207
  }
@@ -17203,10 +17269,10 @@ async function runReport(args2) {
17203
17269
  return;
17204
17270
  }
17205
17271
  const sinceArg = args2.find((a) => a.startsWith("--since="))?.slice("--since=".length);
17206
- let cutoffDate = null;
17272
+ let cutoffDate2 = null;
17207
17273
  if (sinceArg) {
17208
17274
  try {
17209
- cutoffDate = parseSince(sinceArg);
17275
+ cutoffDate2 = parseSince(sinceArg);
17210
17276
  } catch (err) {
17211
17277
  process.stderr.write(`${err instanceof Error ? err.message : String(err)}
17212
17278
  `);
@@ -17214,7 +17280,7 @@ async function runReport(args2) {
17214
17280
  }
17215
17281
  }
17216
17282
  const db = await initDb();
17217
- const data = await buildReport(db, cutoffDate);
17283
+ const data = await buildReport(db, cutoffDate2);
17218
17284
  printReport(data);
17219
17285
  }
17220
17286
  var RESET2, BOLD2;
@@ -17229,6 +17295,271 @@ var init_report = __esm({
17229
17295
  }
17230
17296
  });
17231
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
+
17232
17563
  // src/cli/export.ts
17233
17564
  var export_exports = {};
17234
17565
  __export(export_exports, {
@@ -17242,9 +17573,16 @@ function fmtTimestamp2(iso) {
17242
17573
  return `${m[1]} ${m[2]} ${tz}`;
17243
17574
  }
17244
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
+ }
17245
17581
  const prefix = args2.find((a) => !a.startsWith("--"));
17246
17582
  if (!prefix) {
17247
- 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
+ );
17248
17586
  process.exit(1);
17249
17587
  }
17250
17588
  const db = await initDb();
@@ -17302,7 +17640,7 @@ __export(settings_exports, {
17302
17640
  runSettings: () => runSettings
17303
17641
  });
17304
17642
  function dbPath() {
17305
- 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");
17306
17644
  }
17307
17645
  async function runSettings(_args) {
17308
17646
  process.stdout.write(`
@@ -17316,12 +17654,12 @@ ${BOLD3}Chron Settings${RESET3}
17316
17654
 
17317
17655
  `);
17318
17656
  }
17319
- var import_os3, import_path2, RESET3, BOLD3, DIM2, CYAN2;
17657
+ var import_os5, import_path4, RESET3, BOLD3, DIM2, CYAN2;
17320
17658
  var init_settings = __esm({
17321
17659
  "src/cli/settings.ts"() {
17322
17660
  "use strict";
17323
- import_os3 = require("os");
17324
- import_path2 = require("path");
17661
+ import_os5 = require("os");
17662
+ import_path4 = require("path");
17325
17663
  RESET3 = "\x1B[0m";
17326
17664
  BOLD3 = "\x1B[1m";
17327
17665
  DIM2 = "\x1B[2m";
@@ -17414,79 +17752,6 @@ var init_secrets = __esm({
17414
17752
  }
17415
17753
  });
17416
17754
 
17417
- // package.json
17418
- var require_package = __commonJS({
17419
- "package.json"(exports2, module2) {
17420
- module2.exports = {
17421
- name: "chron-mcp",
17422
- version: "0.1.19",
17423
- mcpName: "io.github.sirinivask/chron",
17424
- description: "Audit-grade timestamped logs for every AI conversation",
17425
- repository: {
17426
- type: "git",
17427
- url: "https://github.com/sirinivask/chron.git"
17428
- },
17429
- main: "dist/index.js",
17430
- vitest: {
17431
- include: [
17432
- "tests/**/*.test.ts"
17433
- ],
17434
- globals: true
17435
- },
17436
- bin: {
17437
- "chron-mcp": "dist/index.js",
17438
- chron: "dist/cli/index.js"
17439
- },
17440
- files: [
17441
- "dist",
17442
- "skills",
17443
- ".claude-plugin",
17444
- "assets",
17445
- "dashboards",
17446
- "README.md"
17447
- ],
17448
- engines: {
17449
- node: ">=18"
17450
- },
17451
- scripts: {
17452
- 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",
17453
- "build:cli": "npx esbuild src/cli/index.ts --bundle --format=cjs --outfile=dist/cli/index.js --platform=node && chmod +x dist/cli/index.js",
17454
- typecheck: "tsc --noEmit",
17455
- dev: "tsc --watch",
17456
- start: "node dist/index.js",
17457
- test: "vitest run",
17458
- "test:watch": "vitest",
17459
- prepublishOnly: "npm test && npm run build"
17460
- },
17461
- keywords: [
17462
- "mcp",
17463
- "ai",
17464
- "audit",
17465
- "logging",
17466
- "claude",
17467
- "cursor"
17468
- ],
17469
- license: "SEE LICENSE IN LICENSE",
17470
- dependencies: {
17471
- "@libsql/client": "^0.17.3",
17472
- "@modelcontextprotocol/sdk": "^1.12.0",
17473
- "drizzle-orm": "^0.45.2",
17474
- express: "^4.18.2",
17475
- uuid: "^11.1.1",
17476
- zod: "^3.22.4"
17477
- },
17478
- devDependencies: {
17479
- "@types/express": "^4.17.21",
17480
- "@types/node": "^20.0.0",
17481
- "@types/uuid": "^9.0.0",
17482
- "drizzle-kit": "^0.20.14",
17483
- typescript: "^5.4.5",
17484
- vitest: "^1.4.0"
17485
- }
17486
- };
17487
- }
17488
- });
17489
-
17490
17755
  // src/cli/connect.ts
17491
17756
  var connect_exports = {};
17492
17757
  __export(connect_exports, {
@@ -17496,26 +17761,26 @@ function prompt(rl, question) {
17496
17761
  return new Promise((resolve) => rl.question(question, resolve));
17497
17762
  }
17498
17763
  function configPath() {
17499
- 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");
17500
17765
  }
17501
17766
  function loadConfig() {
17502
17767
  try {
17503
- return JSON.parse((0, import_fs3.readFileSync)(configPath(), "utf8"));
17768
+ return JSON.parse((0, import_fs5.readFileSync)(configPath(), "utf8"));
17504
17769
  } catch {
17505
17770
  return {};
17506
17771
  }
17507
17772
  }
17508
17773
  function saveConfig(data) {
17509
- const dir = (0, import_path3.join)((0, import_os4.homedir)(), ".chron");
17510
- (0, import_fs3.mkdirSync)(dir, { recursive: true });
17511
- (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");
17512
17777
  }
17513
17778
  function patchClaudeJson(vars) {
17514
- const path = (0, import_path3.join)((0, import_os4.homedir)(), ".claude.json");
17515
- 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))
17516
17781
  return false;
17517
17782
  try {
17518
- const raw = (0, import_fs3.readFileSync)(path, "utf8");
17783
+ const raw = (0, import_fs5.readFileSync)(path, "utf8");
17519
17784
  const doc = JSON.parse(raw);
17520
17785
  const servers = doc.mcpServers ?? {};
17521
17786
  const chron = servers.chron ?? {};
@@ -17524,7 +17789,7 @@ function patchClaudeJson(vars) {
17524
17789
  chron.env = { ...chron.env ?? {}, ...vars };
17525
17790
  servers.chron = chron;
17526
17791
  doc.mcpServers = servers;
17527
- (0, import_fs3.writeFileSync)(path, JSON.stringify(doc, null, 2), "utf8");
17792
+ (0, import_fs5.writeFileSync)(path, JSON.stringify(doc, null, 2), "utf8");
17528
17793
  return true;
17529
17794
  } catch {
17530
17795
  return false;
@@ -17578,7 +17843,7 @@ ${DIM4}Sending test event...${RESET5} `);
17578
17843
  }
17579
17844
  }]
17580
17845
  }]);
17581
- let ok = false;
17846
+ let ok2 = false;
17582
17847
  let statusCode = 0;
17583
17848
  try {
17584
17849
  const res = await fetch(url, {
@@ -17590,7 +17855,7 @@ ${DIM4}Sending test event...${RESET5} `);
17590
17855
  body: testPayload
17591
17856
  });
17592
17857
  statusCode = res.status;
17593
- ok = res.ok;
17858
+ ok2 = res.ok;
17594
17859
  } catch (e) {
17595
17860
  process.stdout.write(`${RED}failed${RESET5}
17596
17861
  `);
@@ -17599,7 +17864,7 @@ ${DIM4}Sending test event...${RESET5} `);
17599
17864
  `);
17600
17865
  process.exit(1);
17601
17866
  }
17602
- if (!ok) {
17867
+ if (!ok2) {
17603
17868
  process.stdout.write(`${RED}failed (HTTP ${statusCode})${RESET5}
17604
17869
  `);
17605
17870
  process.stderr.write(` ${RED}Check your URL and token, then try again.${RESET5}
@@ -17607,13 +17872,13 @@ ${DIM4}Sending test event...${RESET5} `);
17607
17872
  `);
17608
17873
  process.exit(1);
17609
17874
  }
17610
- process.stdout.write(`${GREEN}OK${RESET5}
17875
+ process.stdout.write(`${GREEN2}OK${RESET5}
17611
17876
 
17612
17877
  `);
17613
17878
  const config = loadConfig();
17614
17879
  config.logscale = { url, connected_at: (/* @__PURE__ */ new Date()).toISOString() };
17615
17880
  saveConfig(config);
17616
- 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.
17617
17882
 
17618
17883
  `);
17619
17884
  process.stdout.write(`${BOLD5}Add these env vars to your MCP client config:${RESET5}
@@ -17722,7 +17987,7 @@ ${DIM4}Sending test event...${RESET5} `);
17722
17987
  `);
17723
17988
  process.exit(1);
17724
17989
  }
17725
- process.stdout.write(`${GREEN}OK${RESET5}
17990
+ process.stdout.write(`${GREEN2}OK${RESET5}
17726
17991
 
17727
17992
  `);
17728
17993
  } catch (e) {
@@ -17740,7 +18005,7 @@ ${DIM4}Sending test event...${RESET5} `);
17740
18005
  if (isLocalhost(url))
17741
18006
  splunkVars.CHRON_SPLUNK_INSECURE = "1";
17742
18007
  patchClaudeJson(splunkVars);
17743
- 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.
17744
18009
 
17745
18010
  `);
17746
18011
  process.stdout.write(`${DIM4}Config saved to ~/.chron/config.json \u2014 events will flow immediately in all running sessions.${RESET5}
@@ -17829,7 +18094,7 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
17829
18094
  }
17830
18095
  const tokenData = await tokenRes.json();
17831
18096
  accessToken = tokenData.access_token;
17832
- process.stdout.write(`${GREEN}OK${RESET5}
18097
+ process.stdout.write(`${GREEN2}OK${RESET5}
17833
18098
  `);
17834
18099
  } catch (e) {
17835
18100
  process.stdout.write(`${RED}failed${RESET5}
@@ -17871,7 +18136,7 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
17871
18136
  `);
17872
18137
  process.exit(1);
17873
18138
  }
17874
- process.stdout.write(`${GREEN}OK${RESET5}
18139
+ process.stdout.write(`${GREEN2}OK${RESET5}
17875
18140
 
17876
18141
  `);
17877
18142
  } catch (e) {
@@ -17893,7 +18158,7 @@ ${DIM4}Authenticating with Azure AD...${RESET5} `);
17893
18158
  CHRON_SENTINEL_DCR_ID: dcrId,
17894
18159
  CHRON_SENTINEL_STREAM: stream
17895
18160
  });
17896
- 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.
17897
18162
 
17898
18163
  `);
17899
18164
  process.stdout.write(`${DIM4}Config saved to ~/.chron/config.json \u2014 events will flow immediately in all running sessions.${RESET5}
@@ -17929,19 +18194,19 @@ Integrations:
17929
18194
  }
17930
18195
  }
17931
18196
  }
17932
- 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;
17933
18198
  var init_connect = __esm({
17934
18199
  "src/cli/connect.ts"() {
17935
18200
  "use strict";
17936
18201
  import_readline = require("readline");
17937
- import_os4 = require("os");
17938
- import_path3 = require("path");
17939
- import_fs3 = require("fs");
18202
+ import_os6 = require("os");
18203
+ import_path5 = require("path");
18204
+ import_fs5 = require("fs");
17940
18205
  RESET5 = "\x1B[0m";
17941
18206
  BOLD5 = "\x1B[1m";
17942
18207
  DIM4 = "\x1B[2m";
17943
18208
  CYAN4 = "\x1B[36m";
17944
- GREEN = "\x1B[32m";
18209
+ GREEN2 = "\x1B[32m";
17945
18210
  RED = "\x1B[31m";
17946
18211
  YELLOW3 = "\x1B[33m";
17947
18212
  }
@@ -18037,7 +18302,7 @@ ${BOLD6}Session Summary${RESET6}
18037
18302
  process.stdout.write(` Duration ${stats.duration_minutes}m
18038
18303
 
18039
18304
  `);
18040
- 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}`}
18041
18306
  `);
18042
18307
  for (const s of secrets) {
18043
18308
  process.stdout.write(` ${RED2}[${s.type}]${RESET6} ${DIM5}${s.masked_value}${RESET6}
@@ -18091,7 +18356,7 @@ async function runSummary(args2) {
18091
18356
  printSummary(data);
18092
18357
  }
18093
18358
  }
18094
- 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;
18095
18360
  var init_summary = __esm({
18096
18361
  "src/cli/summary.ts"() {
18097
18362
  "use strict";
@@ -18102,7 +18367,7 @@ var init_summary = __esm({
18102
18367
  BOLD6 = "\x1B[1m";
18103
18368
  DIM5 = "\x1B[2m";
18104
18369
  CYAN5 = "\x1B[36m";
18105
- GREEN2 = "\x1B[32m";
18370
+ GREEN3 = "\x1B[32m";
18106
18371
  YELLOW4 = "\x1B[33m";
18107
18372
  RED2 = "\x1B[31m";
18108
18373
  MUTATION_PATTERNS = [
@@ -18118,6 +18383,419 @@ var init_summary = __esm({
18118
18383
  }
18119
18384
  });
18120
18385
 
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
+ }
18402
+ }
18403
+ function cutoffDate(days) {
18404
+ const d = /* @__PURE__ */ new Date();
18405
+ d.setDate(d.getDate() - days);
18406
+ return d.toISOString().slice(0, 10);
18407
+ }
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);
18417
+ }
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
+ `);
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
+ `);
18473
+ }
18474
+ var import_fs6, import_path6, import_os7;
18475
+ var init_prune = __esm({
18476
+ "src/cli/prune.ts"() {
18477
+ "use strict";
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();
18484
+ }
18485
+ });
18486
+
18487
+ // src/cli/sign.ts
18488
+ var sign_exports = {};
18489
+ __export(sign_exports, {
18490
+ runSign: () => runSign
18491
+ });
18492
+ async function runSign(args2) {
18493
+ const prefix = args2[0];
18494
+ if (!prefix) {
18495
+ process.stderr.write("Usage: chron sign <session-id-prefix>\n");
18496
+ process.exit(1);
18497
+ }
18498
+ const db = await initDb();
18499
+ const allSessions = await db.select().from(sessions);
18500
+ const session = allSessions.find((s) => s.id.startsWith(prefix));
18501
+ if (!session) {
18502
+ process.stderr.write(`Session not found: ${prefix}
18503
+ `);
18504
+ process.exit(1);
18505
+ }
18506
+ if (!session.public_key) {
18507
+ process.stderr.write(`Session ${session.id.slice(0, 8)} has no public key \u2014 was it created before v0.1.19?
18508
+ `);
18509
+ process.exit(1);
18510
+ }
18511
+ if (!(0, import_fs8.existsSync)(privKeyPath(session.id))) {
18512
+ process.stderr.write(`Private key not found: ${privKeyPath(session.id)}
18513
+ `);
18514
+ process.stderr.write("The key is stored on the machine where the session was created.\n");
18515
+ process.exit(1);
18516
+ }
18517
+ const [countRow] = await db.select({
18518
+ count: sql`count(*)`,
18519
+ first_created_at: sql`min(${messages.created_at})`
18520
+ }).from(messages).where(eq(messages.session_id, session.id));
18521
+ const messageCount = countRow?.count ?? 0;
18522
+ const firstCreatedAt = countRow?.first_created_at ?? "";
18523
+ if (messageCount === 0) {
18524
+ process.stderr.write("Session has no messages \u2014 nothing to sign.\n");
18525
+ process.exit(1);
18526
+ }
18527
+ const lastMsg = await db.select({ content_hash: messages.content_hash }).from(messages).where(eq(messages.session_id, session.id)).orderBy(asc(sql`rowid`)).limit(1e3);
18528
+ const finalContentHash = lastMsg[lastMsg.length - 1]?.content_hash ?? "";
18529
+ if (!finalContentHash) {
18530
+ process.stderr.write("Session has no hash chain \u2014 cannot sign.\n");
18531
+ process.exit(1);
18532
+ }
18533
+ const signature = signSession(session.id, finalContentHash, messageCount, firstCreatedAt);
18534
+ await db.update(sessions).set({ signature }).where(eq(sessions.id, session.id));
18535
+ const sigData = {
18536
+ chron_signature: "v1",
18537
+ session_id: session.id,
18538
+ session_title: session.title,
18539
+ public_key_pem: session.public_key,
18540
+ message_count: messageCount,
18541
+ first_message_at: firstCreatedAt,
18542
+ final_content_hash: finalContentHash,
18543
+ signature,
18544
+ signed_at: (/* @__PURE__ */ new Date()).toISOString()
18545
+ };
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));
18548
+ process.stdout.write(`Signed session ${session.id.slice(0, 8)}
18549
+ `);
18550
+ process.stdout.write(` messages : ${messageCount}
18551
+ `);
18552
+ process.stdout.write(` final hash: ${finalContentHash.slice(0, 16)}\u2026
18553
+ `);
18554
+ process.stdout.write(` sig file : ${sigFile}
18555
+ `);
18556
+ }
18557
+ var import_fs7, import_path7, import_fs8;
18558
+ var init_sign = __esm({
18559
+ "src/cli/sign.ts"() {
18560
+ "use strict";
18561
+ init_drizzle_orm();
18562
+ import_fs7 = require("fs");
18563
+ import_path7 = require("path");
18564
+ init_db2();
18565
+ init_schema();
18566
+ init_signing();
18567
+ import_fs8 = require("fs");
18568
+ }
18569
+ });
18570
+
18571
+ // src/utils/hash.ts
18572
+ function computeContentHash(sessionId, role, content, createdAt, prevHash, eventType) {
18573
+ const base = `${sessionId}|${role}|${content}|${createdAt}|${prevHash ?? ""}`;
18574
+ const input = eventType != null ? `${base}|${eventType}` : base;
18575
+ return (0, import_crypto4.createHash)("sha256").update(input).digest("hex");
18576
+ }
18577
+ var import_crypto4;
18578
+ var init_hash = __esm({
18579
+ "src/utils/hash.ts"() {
18580
+ "use strict";
18581
+ import_crypto4 = require("crypto");
18582
+ }
18583
+ });
18584
+
18585
+ // src/cli/verify.ts
18586
+ var verify_exports = {};
18587
+ __export(verify_exports, {
18588
+ runVerify: () => runVerify
18589
+ });
18590
+ function ok(msg) {
18591
+ process.stdout.write(` ${GREEN4}\u2713${RESET7} ${msg}
18592
+ `);
18593
+ }
18594
+ function fail(msg) {
18595
+ process.stdout.write(` ${RED3}\u2717${RESET7} ${msg}
18596
+ `);
18597
+ }
18598
+ function warn(msg) {
18599
+ process.stdout.write(` ${YELLOW5}!${RESET7} ${msg}
18600
+ `);
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
+ }
18675
+ async function runVerify(args2) {
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("--"));
18682
+ if (!prefix) {
18683
+ process.stderr.write(
18684
+ "Usage: chron verify <session-id-prefix>\n chron verify --bundle=<bundle.chron.tar.gz>\n"
18685
+ );
18686
+ process.exit(1);
18687
+ }
18688
+ const db = await initDb();
18689
+ const allSessions = await db.select().from(sessions);
18690
+ const session = allSessions.find((s) => s.id.startsWith(prefix));
18691
+ if (!session) {
18692
+ process.stderr.write(`Session not found: ${prefix}
18693
+ `);
18694
+ process.exit(1);
18695
+ }
18696
+ process.stdout.write(`
18697
+ ${BOLD7}Verifying session ${session.id.slice(0, 8)}${RESET7} \u2014 ${session.title}
18698
+
18699
+ `);
18700
+ const rows = await db.select().from(messages).where(eq(messages.session_id, session.id)).orderBy(asc(messages.created_at), asc(sql`rowid`));
18701
+ process.stdout.write(`${BOLD7}Hash chain${RESET7} (${rows.length} messages)
18702
+ `);
18703
+ const chained = rows.filter((r) => r.content_hash !== null);
18704
+ let chainOk = true;
18705
+ if (chained.length === 0) {
18706
+ warn("No chained messages \u2014 session pre-dates hash chaining");
18707
+ } else {
18708
+ for (let i = 0; i < chained.length; i++) {
18709
+ const row = chained[i];
18710
+ const expectedPrev = i === 0 ? null : chained[i - 1].content_hash;
18711
+ if (row.prev_hash !== expectedPrev) {
18712
+ fail(`Row ${i + 1} (${row.id.slice(0, 8)}): prev_hash mismatch \u2014 chain broken`);
18713
+ chainOk = false;
18714
+ break;
18715
+ }
18716
+ const expected = computeContentHash(row.session_id, row.role, row.content, row.created_at, row.prev_hash, row.event_type ?? void 0);
18717
+ if (row.content_hash !== expected) {
18718
+ fail(`Row ${i + 1} (${row.id.slice(0, 8)}): content_hash mismatch \u2014 row tampered`);
18719
+ chainOk = false;
18720
+ break;
18721
+ }
18722
+ }
18723
+ if (chainOk)
18724
+ ok(`All ${chained.length} hashes valid`);
18725
+ }
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(`
18748
+ ${BOLD7}Signature${RESET7}
18749
+ `);
18750
+ if (!session.public_key) {
18751
+ warn("No public key \u2014 session was created before signing support (v0.1.19)");
18752
+ } else if (!session.signature) {
18753
+ warn("Not yet signed \u2014 run: chron sign " + session.id.slice(0, 8));
18754
+ } else {
18755
+ const finalHash = chained[chained.length - 1]?.content_hash ?? "";
18756
+ const firstCreatedAt = rows[0]?.created_at ?? "";
18757
+ const sigValid = verifySignature(
18758
+ session.public_key,
18759
+ session.signature,
18760
+ session.id,
18761
+ finalHash,
18762
+ rows.length,
18763
+ firstCreatedAt
18764
+ );
18765
+ if (sigValid) {
18766
+ ok("Ed25519 signature valid");
18767
+ process.stdout.write(` ${DIM6}public key: ${session.public_key.split("\n")[1]?.slice(0, 40)}\u2026${RESET7}
18768
+ `);
18769
+ } else {
18770
+ fail("Ed25519 signature INVALID \u2014 session data may have been altered after signing");
18771
+ }
18772
+ }
18773
+ process.stdout.write("\n");
18774
+ const allOk = chainOk && (session.signature ? true : true);
18775
+ process.exit(allOk ? 0 : 1);
18776
+ }
18777
+ var import_fs9, import_path8, import_os8, import_child_process2, RESET7, BOLD7, GREEN4, RED3, DIM6, YELLOW5;
18778
+ var init_verify = __esm({
18779
+ "src/cli/verify.ts"() {
18780
+ "use strict";
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");
18786
+ init_db2();
18787
+ init_schema();
18788
+ init_hash();
18789
+ init_signing();
18790
+ RESET7 = "\x1B[0m";
18791
+ BOLD7 = "\x1B[1m";
18792
+ GREEN4 = "\x1B[32m";
18793
+ RED3 = "\x1B[31m";
18794
+ DIM6 = "\x1B[2m";
18795
+ YELLOW5 = "\x1B[33m";
18796
+ }
18797
+ });
18798
+
18121
18799
  // src/cli/index.ts
18122
18800
  var [, , command, ...args] = process.argv;
18123
18801
  async function main() {
@@ -18157,6 +18835,21 @@ async function main() {
18157
18835
  await runSummary2(args);
18158
18836
  break;
18159
18837
  }
18838
+ case "prune": {
18839
+ const { runPrune: runPrune2 } = await Promise.resolve().then(() => (init_prune(), prune_exports));
18840
+ await runPrune2(args);
18841
+ break;
18842
+ }
18843
+ case "sign": {
18844
+ const { runSign: runSign2 } = await Promise.resolve().then(() => (init_sign(), sign_exports));
18845
+ await runSign2(args);
18846
+ break;
18847
+ }
18848
+ case "verify": {
18849
+ const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
18850
+ await runVerify2(args);
18851
+ break;
18852
+ }
18160
18853
  default: {
18161
18854
  const name = command ? `Unknown command: ${command}
18162
18855
 
@@ -18172,9 +18865,13 @@ Commands:
18172
18865
  settings View current configuration
18173
18866
  connect Connect to a SIEM integration (crowdstrike, sentinel, splunk)
18174
18867
  summary Structured summary of a session (timeline, mutations, secrets)
18868
+ sign Sign a session with its Ed25519 key \u2014 produces a .chron.sig file
18869
+ verify Verify a session's hash chain and Ed25519 signature
18870
+ prune Delete sessions older than a retention cutoff
18175
18871
 
18176
18872
  Options (history):
18177
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)
18178
18875
  <id-prefix> Show full log for the session with this ID prefix
18179
18876
 
18180
18877
  Options (report):
@@ -18182,8 +18879,21 @@ Options (report):
18182
18879
  --format=soc2 Generate SOC 2 HTML evidence package
18183
18880
  --output=<file> Output file for --format=soc2 (default: soc2-report.html)
18184
18881
 
18185
- Options (export / secrets):
18186
- <id-prefix> Scope to a single session
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
18187
18897
  `
18188
18898
  );
18189
18899
  process.exit(command ? 1 : 0);