chron-mcp 0.1.13 → 0.1.14

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.
@@ -0,0 +1,5 @@
1
+ // AI tool adoption breakdown — pie/bar widget
2
+ event_type = "session_started"
3
+ | groupby(ai_tool, function=count())
4
+ | rename(count(), as=sessions)
5
+ | sort(sessions, order=desc)
@@ -0,0 +1,5 @@
1
+ // Alert: API key or credential sent to AI tool
2
+ // Use as a Scheduled Search alert — notify SOC on any match
3
+ event_type = "secret_detected"
4
+ | detection_type in ["api_key", "aws_key", "gcp_key", "azure_key", "private_key", "jwt"]
5
+ | table([#host, ai_tool, detection_type, masked_value, session_id_prefix, @timestamp])
@@ -0,0 +1,66 @@
1
+ $schema: "https://schemas.humio.com/dashboard/v0.1.0"
2
+ name: "Chron — AI Activity"
3
+ updateFrequency: 60
4
+ timeSelector:
5
+ defaultRelativeTimeRange: "7d"
6
+
7
+ widgets:
8
+ - id: daily-active-developers
9
+ title: "Daily Active Developers"
10
+ type: time-chart
11
+ x: 0
12
+ y: 0
13
+ width: 8
14
+ height: 4
15
+ query: |
16
+ event_type = "session_started"
17
+ | timechart(unit=1d, function=count(#host, distinct=true))
18
+
19
+ - id: ai-tool-usage
20
+ title: "AI Tool Usage"
21
+ type: pie-chart
22
+ x: 8
23
+ y: 0
24
+ width: 4
25
+ height: 4
26
+ query: |
27
+ event_type = "session_started"
28
+ | groupby(ai_tool, function=count())
29
+ | rename(count(), as=sessions)
30
+ | sort(sessions, order=desc)
31
+
32
+ - id: secrets-by-type
33
+ title: "Secrets Detected by Type (7d)"
34
+ type: time-chart
35
+ x: 0
36
+ y: 4
37
+ width: 8
38
+ height: 4
39
+ query: |
40
+ event_type = "secret_detected"
41
+ | timechart(detection_type, unit=1d, function=count())
42
+
43
+ - id: sessions-by-developer
44
+ title: "Sessions by Developer"
45
+ type: table
46
+ x: 8
47
+ y: 4
48
+ width: 4
49
+ height: 4
50
+ query: |
51
+ event_type = "session_started"
52
+ | groupby([#host, ai_tool], function=count())
53
+ | rename(count(), as=sessions)
54
+ | sort(sessions, order=desc)
55
+
56
+ - id: api-key-alerts
57
+ title: "Credential Exposures — Requires Attention"
58
+ type: table
59
+ x: 0
60
+ y: 8
61
+ width: 12
62
+ height: 4
63
+ query: |
64
+ event_type = "secret_detected"
65
+ | detection_type in ["api_key", "aws_key", "gcp_key", "azure_key", "private_key", "jwt"]
66
+ | table([#host, ai_tool, detection_type, masked_value, session_id_prefix, @timestamp])
@@ -0,0 +1,3 @@
1
+ // Daily active developers — time-series widget
2
+ event_type = "session_started"
3
+ | timechart(unit=1d, function=count(#host, distinct=true))
@@ -0,0 +1,3 @@
1
+ // Secrets detected by type — timechart (use as time-series widget, 7d window)
2
+ event_type = "secret_detected"
3
+ | timechart(detection_type, unit=1d, function=count())
@@ -0,0 +1,6 @@
1
+ // Sessions per developer — bar/table widget
2
+ // #host is the actual hostname sent in the LogScale tags (set by chron MCP)
3
+ event_type = "session_started"
4
+ | groupby([#host, ai_tool], function=count())
5
+ | rename(count(), as=sessions)
6
+ | sort(sessions, order=desc)
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 join3(chunks, separator) {
1187
+ function join4(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 = join3;
1197
+ sql2.join = join4;
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 readFileSync = (path) => fs.readFileSync(path, "utf-8");
2809
+ var readFileSync2 = (path) => fs.readFileSync(path, "utf-8");
2810
2810
  var readFile = (path) => new Promise((resolve, reject) => {
2811
2811
  fs.readFile(path, "utf-8", (err, data) => {
2812
2812
  if (err) {
@@ -2818,7 +2818,7 @@ var require_filesystem = __commonJS({
2818
2818
  });
2819
2819
  module2.exports = {
2820
2820
  LDD_PATH,
2821
- readFileSync,
2821
+ readFileSync: readFileSync2,
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 } = require_filesystem();
2833
+ var { LDD_PATH, readFile, readFileSync: readFileSync2 } = 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 = readFileSync(LDD_PATH);
2914
+ const lddContent = readFileSync2(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 = readFileSync(LDD_PATH);
2971
+ const lddContent = readFileSync2(LDD_PATH);
2972
2972
  const versionMatch = lddContent.match(RE_GLIBC_VERSION);
2973
2973
  if (versionMatch) {
2974
2974
  cachedVersionFilesystem = versionMatch[1];
@@ -11068,8 +11068,8 @@ var init_stream3 = __esm({
11068
11068
  let promise;
11069
11069
  try {
11070
11070
  const request = createRequest();
11071
- const fetch = this.#fetch;
11072
- promise = fetch(request);
11071
+ const fetch2 = this.#fetch;
11072
+ promise = fetch2(request);
11073
11073
  } catch (error) {
11074
11074
  promise = Promise.reject(error);
11075
11075
  }
@@ -11136,11 +11136,11 @@ var init_stream3 = __esm({
11136
11136
 
11137
11137
  // node_modules/@libsql/hrana-client/lib-esm/http/client.js
11138
11138
  async function findEndpoint(customFetch, clientUrl) {
11139
- const fetch = customFetch;
11139
+ const fetch2 = customFetch;
11140
11140
  for (const endpoint of checkEndpoints) {
11141
11141
  const url = new URL(endpoint.versionPath, clientUrl);
11142
11142
  const request = new Request(url.toString(), { method: "GET" });
11143
- const response = await fetch(request);
11143
+ const response = await fetch2(request);
11144
11144
  await response.arrayBuffer();
11145
11145
  if (response.ok) {
11146
11146
  return endpoint;
@@ -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((join3) => join3.alias === tableName)) {
14255
+ if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.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((join3) => join3.alias === tableName)) {
15144
+ if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
15145
15145
  throw new Error(`Alias "${tableName}" is already used in this query`);
15146
15146
  }
15147
15147
  if (typeof on === "function") {
@@ -17026,6 +17026,250 @@ var init_secrets = __esm({
17026
17026
  }
17027
17027
  });
17028
17028
 
17029
+ // package.json
17030
+ var require_package = __commonJS({
17031
+ "package.json"(exports2, module2) {
17032
+ module2.exports = {
17033
+ name: "chron-mcp",
17034
+ version: "0.1.14",
17035
+ mcpName: "io.github.sirinivask/chron",
17036
+ description: "Audit-grade timestamped logs for every AI conversation",
17037
+ repository: {
17038
+ type: "git",
17039
+ url: "https://github.com/sirinivask/chron.git"
17040
+ },
17041
+ main: "dist/index.js",
17042
+ vitest: {
17043
+ include: [
17044
+ "tests/**/*.test.ts"
17045
+ ],
17046
+ globals: true
17047
+ },
17048
+ bin: {
17049
+ "chron-mcp": "dist/index.js",
17050
+ chron: "dist/cli/index.js"
17051
+ },
17052
+ files: [
17053
+ "dist",
17054
+ "skills",
17055
+ ".claude-plugin",
17056
+ "assets",
17057
+ "dashboards",
17058
+ "README.md"
17059
+ ],
17060
+ engines: {
17061
+ node: ">=18"
17062
+ },
17063
+ scripts: {
17064
+ 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",
17065
+ "build:cli": "npx esbuild src/cli/index.ts --bundle --format=cjs --outfile=dist/cli/index.js --platform=node && chmod +x dist/cli/index.js",
17066
+ typecheck: "tsc --noEmit",
17067
+ dev: "tsc --watch",
17068
+ start: "node dist/index.js",
17069
+ test: "vitest run",
17070
+ "test:watch": "vitest",
17071
+ prepublishOnly: "npm test && npm run build"
17072
+ },
17073
+ keywords: [
17074
+ "mcp",
17075
+ "ai",
17076
+ "audit",
17077
+ "logging",
17078
+ "claude",
17079
+ "cursor"
17080
+ ],
17081
+ license: "SEE LICENSE IN LICENSE",
17082
+ dependencies: {
17083
+ "@libsql/client": "^0.17.3",
17084
+ "@modelcontextprotocol/sdk": "^1.12.0",
17085
+ "drizzle-orm": "^0.45.2",
17086
+ express: "^4.18.2",
17087
+ uuid: "^11.1.1",
17088
+ zod: "^3.22.4"
17089
+ },
17090
+ devDependencies: {
17091
+ "@types/express": "^4.17.21",
17092
+ "@types/node": "^20.0.0",
17093
+ "@types/uuid": "^9.0.0",
17094
+ "drizzle-kit": "^0.20.14",
17095
+ typescript: "^5.4.5",
17096
+ vitest: "^1.4.0"
17097
+ }
17098
+ };
17099
+ }
17100
+ });
17101
+
17102
+ // src/cli/connect.ts
17103
+ var connect_exports = {};
17104
+ __export(connect_exports, {
17105
+ runConnect: () => runConnect
17106
+ });
17107
+ function prompt(rl, question) {
17108
+ return new Promise((resolve) => rl.question(question, resolve));
17109
+ }
17110
+ function configPath() {
17111
+ return (0, import_path3.join)((0, import_os3.homedir)(), ".chron", "config.json");
17112
+ }
17113
+ function loadConfig() {
17114
+ try {
17115
+ return JSON.parse((0, import_fs2.readFileSync)(configPath(), "utf8"));
17116
+ } catch {
17117
+ return {};
17118
+ }
17119
+ }
17120
+ function saveConfig(data) {
17121
+ const dir = (0, import_path3.join)((0, import_os3.homedir)(), ".chron");
17122
+ (0, import_fs2.mkdirSync)(dir, { recursive: true });
17123
+ (0, import_fs2.writeFileSync)(configPath(), JSON.stringify(data, null, 2), "utf8");
17124
+ }
17125
+ async function connectCrowdStrike() {
17126
+ process.stdout.write(`
17127
+ ${BOLD5}Connect Chron \u2192 CrowdStrike LogScale${RESET5}
17128
+
17129
+ `);
17130
+ process.stdout.write(`${DIM4}Events will be sent directly to your LogScale repository.
17131
+ `);
17132
+ process.stdout.write(`No message content is transmitted \u2014 metadata only.${RESET5}
17133
+
17134
+ `);
17135
+ const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
17136
+ let url;
17137
+ let token;
17138
+ try {
17139
+ url = (await prompt(rl, ` LogScale ingest URL ${DIM4}(e.g. https://cloud.us.humio.com/api/v1/ingest/humio-structured)${RESET5}
17140
+ ${CYAN4}>${RESET5} `)).trim();
17141
+ if (!url.startsWith("https://")) {
17142
+ process.stdout.write(`
17143
+ ${RED}URL must start with https://${RESET5}
17144
+ `);
17145
+ process.exit(1);
17146
+ }
17147
+ token = (await prompt(rl, `
17148
+ LogScale ingest token
17149
+ ${CYAN4}>${RESET5} `)).trim();
17150
+ if (!token) {
17151
+ process.stdout.write(`
17152
+ ${RED}Token cannot be empty${RESET5}
17153
+ `);
17154
+ process.exit(1);
17155
+ }
17156
+ } finally {
17157
+ rl.close();
17158
+ }
17159
+ process.stdout.write(`
17160
+ ${DIM4}Sending test event...${RESET5} `);
17161
+ const testPayload = JSON.stringify([{
17162
+ tags: { host: require("os").hostname(), source: "chron-mcp" },
17163
+ events: [{
17164
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
17165
+ attributes: {
17166
+ event_type: "connection_test",
17167
+ chron_version: require_package().version,
17168
+ os: process.platform,
17169
+ message: "chron connect crowdstrike \u2014 test event"
17170
+ }
17171
+ }]
17172
+ }]);
17173
+ let ok = false;
17174
+ let statusCode = 0;
17175
+ try {
17176
+ const res = await fetch(url, {
17177
+ method: "POST",
17178
+ headers: {
17179
+ "Authorization": `Bearer ${token}`,
17180
+ "Content-Type": "application/json"
17181
+ },
17182
+ body: testPayload
17183
+ });
17184
+ statusCode = res.status;
17185
+ ok = res.ok;
17186
+ } catch (e) {
17187
+ process.stdout.write(`${RED}failed${RESET5}
17188
+ `);
17189
+ process.stderr.write(` ${RED}Error: ${e.message}${RESET5}
17190
+
17191
+ `);
17192
+ process.exit(1);
17193
+ }
17194
+ if (!ok) {
17195
+ process.stdout.write(`${RED}failed (HTTP ${statusCode})${RESET5}
17196
+ `);
17197
+ process.stderr.write(` ${RED}Check your URL and token, then try again.${RESET5}
17198
+
17199
+ `);
17200
+ process.exit(1);
17201
+ }
17202
+ process.stdout.write(`${GREEN}OK${RESET5}
17203
+
17204
+ `);
17205
+ const config = loadConfig();
17206
+ config.logscale = { url, connected_at: (/* @__PURE__ */ new Date()).toISOString() };
17207
+ saveConfig(config);
17208
+ process.stdout.write(`${GREEN}${BOLD5}Connected!${RESET5} Test event appeared in LogScale.
17209
+
17210
+ `);
17211
+ process.stdout.write(`${BOLD5}Add these env vars to your MCP client config:${RESET5}
17212
+
17213
+ `);
17214
+ process.stdout.write(` ${CYAN4}CHRON_LOGSCALE_URL${RESET5} ${url}
17215
+ `);
17216
+ process.stdout.write(` ${CYAN4}CHRON_LOGSCALE_TOKEN${RESET5} ${DIM4}<your-token>${RESET5}
17217
+
17218
+ `);
17219
+ process.stdout.write(`${DIM4}For Claude Code \u2014 add to the "env" block of the chron entry in ~/.claude.json:${RESET5}
17220
+
17221
+ `);
17222
+ process.stdout.write(` ${DIM4}"env": {
17223
+ `);
17224
+ process.stdout.write(` "CHRON_LOGSCALE_URL": "${url}",
17225
+ `);
17226
+ process.stdout.write(` "CHRON_LOGSCALE_TOKEN": "<your-token>"
17227
+ `);
17228
+ process.stdout.write(` }${RESET5}
17229
+
17230
+ `);
17231
+ process.stdout.write(`${DIM4}Connection saved to ~/.chron/config.json${RESET5}
17232
+
17233
+ `);
17234
+ }
17235
+ async function runConnect(args2) {
17236
+ const [subcommand] = args2;
17237
+ switch (subcommand) {
17238
+ case "crowdstrike":
17239
+ await connectCrowdStrike();
17240
+ break;
17241
+ default: {
17242
+ const name = subcommand ? `Unknown integration: ${subcommand}
17243
+
17244
+ ` : "";
17245
+ process.stdout.write(
17246
+ `${name}Usage: chron connect <integration>
17247
+
17248
+ Integrations:
17249
+ crowdstrike Connect to CrowdStrike LogScale (direct ingest)
17250
+ `
17251
+ );
17252
+ process.exit(subcommand ? 1 : 0);
17253
+ }
17254
+ }
17255
+ }
17256
+ var import_readline, import_os3, import_path3, import_fs2, RESET5, BOLD5, DIM4, CYAN4, GREEN, RED;
17257
+ var init_connect = __esm({
17258
+ "src/cli/connect.ts"() {
17259
+ "use strict";
17260
+ import_readline = require("readline");
17261
+ import_os3 = require("os");
17262
+ import_path3 = require("path");
17263
+ import_fs2 = require("fs");
17264
+ RESET5 = "\x1B[0m";
17265
+ BOLD5 = "\x1B[1m";
17266
+ DIM4 = "\x1B[2m";
17267
+ CYAN4 = "\x1B[36m";
17268
+ GREEN = "\x1B[32m";
17269
+ RED = "\x1B[31m";
17270
+ }
17271
+ });
17272
+
17029
17273
  // src/cli/index.ts
17030
17274
  var [, , command, ...args] = process.argv;
17031
17275
  async function main() {
@@ -17055,6 +17299,11 @@ async function main() {
17055
17299
  await runSecrets2(args);
17056
17300
  break;
17057
17301
  }
17302
+ case "connect": {
17303
+ const { runConnect: runConnect2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
17304
+ await runConnect2(args);
17305
+ break;
17306
+ }
17058
17307
  default: {
17059
17308
  const name = command ? `Unknown command: ${command}
17060
17309
 
@@ -17068,6 +17317,7 @@ Commands:
17068
17317
  export Export a session as markdown
17069
17318
  secrets List detected secrets across sessions
17070
17319
  settings View current configuration
17320
+ connect Connect to a SIEM integration (crowdstrike)
17071
17321
 
17072
17322
  Options (history):
17073
17323
  --limit=<n> Max sessions to show (default: 20)
package/dist/index.js CHANGED
@@ -9320,7 +9320,7 @@ var require_websocket = __commonJS({
9320
9320
  var http = require("http");
9321
9321
  var net = require("net");
9322
9322
  var tls = require("tls");
9323
- var { randomBytes, createHash: createHash2 } = require("crypto");
9323
+ var { randomBytes, createHash: createHash3 } = require("crypto");
9324
9324
  var { Duplex, Readable } = require("stream");
9325
9325
  var { URL: URL3 } = require("url");
9326
9326
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -10000,7 +10000,7 @@ var require_websocket = __commonJS({
10000
10000
  abortHandshake(websocket, socket, "Invalid Upgrade header");
10001
10001
  return;
10002
10002
  }
10003
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
10003
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
10004
10004
  if (res.headers["sec-websocket-accept"] !== digest) {
10005
10005
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
10006
10006
  return;
@@ -10387,7 +10387,7 @@ var require_websocket_server = __commonJS({
10387
10387
  var EventEmitter = require("events");
10388
10388
  var http = require("http");
10389
10389
  var { Duplex } = require("stream");
10390
- var { createHash: createHash2 } = require("crypto");
10390
+ var { createHash: createHash3 } = require("crypto");
10391
10391
  var extension2 = require_extension();
10392
10392
  var PerMessageDeflate2 = require_permessage_deflate();
10393
10393
  var subprotocol2 = require_subprotocol();
@@ -10696,7 +10696,7 @@ var require_websocket_server = __commonJS({
10696
10696
  }
10697
10697
  if (this._state > RUNNING)
10698
10698
  return abortHandshake(socket, 503);
10699
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
10699
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
10700
10700
  const headers = [
10701
10701
  "HTTP/1.1 101 Switching Protocols",
10702
10702
  "Upgrade: websocket",
@@ -32535,6 +32535,92 @@ var init_time = __esm({
32535
32535
  }
32536
32536
  });
32537
32537
 
32538
+ // package.json
32539
+ var version4;
32540
+ var init_package = __esm({
32541
+ "package.json"() {
32542
+ version4 = "0.1.14";
32543
+ }
32544
+ });
32545
+
32546
+ // src/utils/relay.ts
32547
+ function getMachineId() {
32548
+ if (!_machineId) {
32549
+ _machineId = "sha256:" + (0, import_crypto3.createHash)("sha256").update((0, import_os2.hostname)()).digest("hex").slice(0, 16);
32550
+ }
32551
+ return _machineId;
32552
+ }
32553
+ function toLogScaleAttributes(payload) {
32554
+ const attrs = {
32555
+ event_type: payload.event_type,
32556
+ session_id_prefix: payload.session.id_prefix,
32557
+ ai_tool: payload.session.ai_tool ?? "",
32558
+ os: process.platform,
32559
+ chron_version: version4
32560
+ };
32561
+ if (payload.event_type === "message_logged") {
32562
+ attrs.role = payload.message.role;
32563
+ } else if (payload.event_type === "secret_detected") {
32564
+ attrs.detection_type = payload.detection.type;
32565
+ attrs.masked_value = payload.detection.masked_value;
32566
+ }
32567
+ return attrs;
32568
+ }
32569
+ function emitToLogScale(payload) {
32570
+ const url = process.env.CHRON_LOGSCALE_URL;
32571
+ const token = process.env.CHRON_LOGSCALE_TOKEN;
32572
+ if (!url || !token)
32573
+ return;
32574
+ const body = JSON.stringify([{
32575
+ tags: { host: (0, import_os2.hostname)(), source: "chron-mcp" },
32576
+ events: [{ timestamp: payload.timestamp, attributes: toLogScaleAttributes(payload) }]
32577
+ }]);
32578
+ setImmediate(
32579
+ () => fetch(url, {
32580
+ method: "POST",
32581
+ headers: {
32582
+ "Authorization": `Bearer ${token}`,
32583
+ "Content-Type": "application/json"
32584
+ },
32585
+ body
32586
+ }).catch(() => void 0)
32587
+ );
32588
+ }
32589
+ function emitEvent(payload) {
32590
+ const url = process.env.CHRON_RELAY_URL;
32591
+ const token = process.env.CHRON_RELAY_TOKEN;
32592
+ if (url && token) {
32593
+ const event = {
32594
+ schema_version: "1.0",
32595
+ chron_version: version4,
32596
+ machine_id: getMachineId(),
32597
+ os: process.platform,
32598
+ ...payload
32599
+ };
32600
+ setImmediate(
32601
+ () => fetch(url, {
32602
+ method: "POST",
32603
+ headers: {
32604
+ "Authorization": `Bearer ${token}`,
32605
+ "Content-Type": "application/json"
32606
+ },
32607
+ body: JSON.stringify(event)
32608
+ }).catch(() => void 0)
32609
+ );
32610
+ }
32611
+ emitToLogScale(payload);
32612
+ }
32613
+ var import_crypto3, import_os2, _machineId;
32614
+ var init_relay = __esm({
32615
+ "src/utils/relay.ts"() {
32616
+ "use strict";
32617
+ import_crypto3 = require("crypto");
32618
+ import_os2 = require("os");
32619
+ init_package();
32620
+ _machineId = null;
32621
+ }
32622
+ });
32623
+
32538
32624
  // src/tools/sessions.ts
32539
32625
  function startSession(db) {
32540
32626
  return async (args) => {
@@ -32548,6 +32634,7 @@ function startSession(db) {
32548
32634
  created_at: now,
32549
32635
  updated_at: now
32550
32636
  });
32637
+ emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: id.slice(0, 8), ai_tool: args.ai_tool ?? null } });
32551
32638
  return {
32552
32639
  content: [{
32553
32640
  type: "text",
@@ -32562,6 +32649,7 @@ function startSession(db) {
32562
32649
  const session = existing[0];
32563
32650
  const [countRow] = await db.select({ count: sql`count(*)` }).from(messages).where(eq(messages.session_id, session.id));
32564
32651
  await db.update(sessions).set({ updated_at: now }).where(eq(sessions.id, session.id));
32652
+ emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: session.id.slice(0, 8), ai_tool: session.ai_tool } });
32565
32653
  return {
32566
32654
  content: [{
32567
32655
  type: "text",
@@ -32600,6 +32688,7 @@ function initSession(db) {
32600
32688
  created = false;
32601
32689
  ai_tool = session.ai_tool;
32602
32690
  }
32691
+ emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: session_id.slice(0, 8), ai_tool } });
32603
32692
  const limit = args.limit ?? 10;
32604
32693
  const [countRow] = await db.select({
32605
32694
  count: sql`count(*)`,
@@ -32683,18 +32772,19 @@ var init_sessions = __esm({
32683
32772
  init_esm2();
32684
32773
  init_schema();
32685
32774
  init_time();
32775
+ init_relay();
32686
32776
  }
32687
32777
  });
32688
32778
 
32689
32779
  // src/utils/hash.ts
32690
32780
  function computeContentHash(sessionId, role, content, createdAt, prevHash) {
32691
- return (0, import_crypto3.createHash)("sha256").update(`${sessionId}|${role}|${content}|${createdAt}|${prevHash ?? ""}`).digest("hex");
32781
+ return (0, import_crypto4.createHash)("sha256").update(`${sessionId}|${role}|${content}|${createdAt}|${prevHash ?? ""}`).digest("hex");
32692
32782
  }
32693
- var import_crypto3;
32783
+ var import_crypto4;
32694
32784
  var init_hash = __esm({
32695
32785
  "src/utils/hash.ts"() {
32696
32786
  "use strict";
32697
- import_crypto3 = require("crypto");
32787
+ import_crypto4 = require("crypto");
32698
32788
  }
32699
32789
  });
32700
32790
 
@@ -32823,16 +32913,19 @@ async function writeDetections(db, session_id, message_id, content) {
32823
32913
  if (found.length === 0)
32824
32914
  return;
32825
32915
  const now = localISOString();
32826
- await db.insert(secrets_detected).values(
32827
- found.map((s) => ({
32828
- id: v4_default(),
32829
- session_id,
32830
- message_id,
32831
- type: s.type,
32832
- masked_value: maskValue(s.value),
32833
- detected_at: now
32834
- }))
32835
- );
32916
+ const rows = found.map((s) => ({
32917
+ id: v4_default(),
32918
+ session_id,
32919
+ message_id,
32920
+ type: s.type,
32921
+ masked_value: maskValue(s.value),
32922
+ detected_at: now
32923
+ }));
32924
+ await db.insert(secrets_detected).values(rows);
32925
+ const sessionIdPrefix = session_id.slice(0, 8);
32926
+ for (const row of rows) {
32927
+ emitEvent({ event_type: "secret_detected", timestamp: now, session: { id_prefix: sessionIdPrefix, ai_tool: null }, detection: { type: row.type, masked_value: row.masked_value } });
32928
+ }
32836
32929
  }
32837
32930
  function isFkError(e) {
32838
32931
  return e?.message?.includes("FOREIGN KEY constraint failed") || e?.code === "SQLITE_CONSTRAINT_FOREIGNKEY" || e?.cause?.message?.includes("FOREIGN KEY constraint failed") || e?.cause?.extendedCode === "SQLITE_CONSTRAINT_FOREIGNKEY";
@@ -32842,6 +32935,7 @@ function logMessage(db) {
32842
32935
  let id;
32843
32936
  let now;
32844
32937
  let contentHash;
32938
+ let sessionAiTool;
32845
32939
  try {
32846
32940
  const result = await db.transaction(async (tx) => {
32847
32941
  const last = await tx.select({ content_hash: messages.content_hash }).from(messages).where(eq(messages.session_id, args.session_id)).orderBy(desc(messages.created_at), desc(sql`rowid`)).limit(1);
@@ -32859,17 +32953,20 @@ function logMessage(db) {
32859
32953
  content_hash: hash
32860
32954
  });
32861
32955
  await tx.update(sessions).set({ updated_at: ts }).where(eq(sessions.id, args.session_id));
32862
- return { id: msgId, now: ts, contentHash: hash };
32956
+ const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, args.session_id)).limit(1);
32957
+ return { id: msgId, now: ts, contentHash: hash, ai_tool: sessionRow?.ai_tool ?? null };
32863
32958
  });
32864
32959
  id = result.id;
32865
32960
  now = result.now;
32866
32961
  contentHash = result.contentHash;
32962
+ sessionAiTool = result.ai_tool;
32867
32963
  } catch (e) {
32868
32964
  if (isFkError(e)) {
32869
32965
  return { content: [{ type: "text", text: `Session not found: ${args.session_id}` }], isError: true };
32870
32966
  }
32871
32967
  throw e;
32872
32968
  }
32969
+ emitEvent({ event_type: "message_logged", timestamp: now, session: { id_prefix: args.session_id.slice(0, 8), ai_tool: sessionAiTool }, message: { role: args.role } });
32873
32970
  if (args.role === "user") {
32874
32971
  setImmediate(() => writeDetections(db, args.session_id, id, args.content).catch(() => void 0));
32875
32972
  }
@@ -32886,6 +32983,7 @@ function logExchange(db) {
32886
32983
  let assistantNow;
32887
32984
  let userHash;
32888
32985
  let assistantHash;
32986
+ let sessionAiTool;
32889
32987
  try {
32890
32988
  const result = await db.transaction(async (tx) => {
32891
32989
  const last = await tx.select({ content_hash: messages.content_hash }).from(messages).where(eq(messages.session_id, args.session_id)).orderBy(desc(messages.created_at), desc(sql`rowid`)).limit(1);
@@ -32915,7 +33013,8 @@ function logExchange(db) {
32915
33013
  content_hash: aHash
32916
33014
  });
32917
33015
  await tx.update(sessions).set({ updated_at: aNow }).where(eq(sessions.id, args.session_id));
32918
- return { uId, aId, uNow, aNow, uHash, aHash };
33016
+ const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, args.session_id)).limit(1);
33017
+ return { uId, aId, uNow, aNow, uHash, aHash, ai_tool: sessionRow?.ai_tool ?? null };
32919
33018
  });
32920
33019
  userId = result.uId;
32921
33020
  assistantId = result.aId;
@@ -32923,12 +33022,16 @@ function logExchange(db) {
32923
33022
  assistantNow = result.aNow;
32924
33023
  userHash = result.uHash;
32925
33024
  assistantHash = result.aHash;
33025
+ sessionAiTool = result.ai_tool;
32926
33026
  } catch (e) {
32927
33027
  if (isFkError(e)) {
32928
33028
  return { content: [{ type: "text", text: `Session not found: ${args.session_id}` }], isError: true };
32929
33029
  }
32930
33030
  throw e;
32931
33031
  }
33032
+ const idPrefix = args.session_id.slice(0, 8);
33033
+ emitEvent({ event_type: "message_logged", timestamp: userNow, session: { id_prefix: idPrefix, ai_tool: sessionAiTool }, message: { role: "user" } });
33034
+ emitEvent({ event_type: "message_logged", timestamp: assistantNow, session: { id_prefix: idPrefix, ai_tool: sessionAiTool }, message: { role: "assistant" } });
32932
33035
  setImmediate(() => writeDetections(db, args.session_id, userId, args.user_content).catch(() => void 0));
32933
33036
  return {
32934
33037
  content: [{
@@ -32954,6 +33057,7 @@ var init_messages = __esm({
32954
33057
  init_time();
32955
33058
  init_hash();
32956
33059
  init_detect();
33060
+ init_relay();
32957
33061
  }
32958
33062
  });
32959
33063
 
@@ -33082,14 +33186,6 @@ var init_detect2 = __esm({
33082
33186
  }
33083
33187
  });
33084
33188
 
33085
- // package.json
33086
- var version4;
33087
- var init_package = __esm({
33088
- "package.json"() {
33089
- version4 = "0.1.13";
33090
- }
33091
- });
33092
-
33093
33189
  // src/server.ts
33094
33190
  function createServer(db) {
33095
33191
  const server = new McpServer({
@@ -33208,7 +33304,7 @@ __export(setup_exports, {
33208
33304
  runSetup: () => runSetup
33209
33305
  });
33210
33306
  function configPath(...parts) {
33211
- return (0, import_path2.join)((0, import_os2.homedir)(), ...parts);
33307
+ return (0, import_path2.join)((0, import_os3.homedir)(), ...parts);
33212
33308
  }
33213
33309
  function readJson(filePath) {
33214
33310
  if (!(0, import_fs2.existsSync)(filePath))
@@ -33246,7 +33342,7 @@ function configureClaudeCode() {
33246
33342
  try {
33247
33343
  (0, import_child_process.execSync)("claude mcp add chron -- npx -y chron-mcp", { stdio: "pipe" });
33248
33344
  } catch {
33249
- const result = configureTool("Claude Code", (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "settings.json"));
33345
+ const result = configureTool("Claude Code", (0, import_path2.join)((0, import_os3.homedir)(), ".claude", "settings.json"));
33250
33346
  if (result.status === "error")
33251
33347
  return result;
33252
33348
  }
@@ -33255,12 +33351,12 @@ function configureClaudeCode() {
33255
33351
  }
33256
33352
  function installClaudeCodeHook() {
33257
33353
  const skillSrc = (0, import_path2.join)(__dirname, "..", "skills", "chron.skill.md");
33258
- const skillDst = (0, import_path2.join)((0, import_os2.homedir)(), ".chron", "chron.skill.md");
33354
+ const skillDst = (0, import_path2.join)((0, import_os3.homedir)(), ".chron", "chron.skill.md");
33259
33355
  if ((0, import_fs2.existsSync)(skillSrc)) {
33260
33356
  (0, import_fs2.mkdirSync)((0, import_path2.dirname)(skillDst), { recursive: true });
33261
33357
  (0, import_fs2.copyFileSync)(skillSrc, skillDst);
33262
33358
  }
33263
- const settingsPath = (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "settings.json");
33359
+ const settingsPath = (0, import_path2.join)((0, import_os3.homedir)(), ".claude", "settings.json");
33264
33360
  const settings = readJson(settingsPath);
33265
33361
  if (!settings.hooks)
33266
33362
  settings.hooks = {};
@@ -33284,12 +33380,12 @@ async function runSetup() {
33284
33380
  }
33285
33381
  return results.filter((r) => r.status !== "skipped");
33286
33382
  }
33287
- var import_fs2, import_os2, import_path2, import_child_process, CHRON_ENTRY, TOOLS;
33383
+ var import_fs2, import_os3, import_path2, import_child_process, CHRON_ENTRY, TOOLS;
33288
33384
  var init_setup = __esm({
33289
33385
  "src/setup.ts"() {
33290
33386
  "use strict";
33291
33387
  import_fs2 = require("fs");
33292
- import_os2 = require("os");
33388
+ import_os3 = require("os");
33293
33389
  import_path2 = require("path");
33294
33390
  import_child_process = require("child_process");
33295
33391
  CHRON_ENTRY = {
@@ -57090,17 +57186,17 @@ var require_request = __commonJS({
57090
57186
  return addrs;
57091
57187
  });
57092
57188
  defineGetter(req, "subdomains", function subdomains() {
57093
- var hostname2 = this.hostname;
57094
- if (!hostname2)
57189
+ var hostname3 = this.hostname;
57190
+ if (!hostname3)
57095
57191
  return [];
57096
57192
  var offset = this.app.get("subdomain offset");
57097
- var subdomains2 = !isIP(hostname2) ? hostname2.split(".").reverse() : [hostname2];
57193
+ var subdomains2 = !isIP(hostname3) ? hostname3.split(".").reverse() : [hostname3];
57098
57194
  return subdomains2.slice(offset);
57099
57195
  });
57100
57196
  defineGetter(req, "path", function path() {
57101
57197
  return parse3(this).pathname;
57102
57198
  });
57103
- defineGetter(req, "hostname", function hostname2() {
57199
+ defineGetter(req, "hostname", function hostname3() {
57104
57200
  var trust = this.app.get("trust proxy fn");
57105
57201
  var host = this.get("X-Forwarded-Host");
57106
57202
  if (!host || !trust(this.connection.remoteAddress, 0)) {
@@ -62419,7 +62515,7 @@ var StdioServerTransport = class {
62419
62515
  };
62420
62516
 
62421
62517
  // src/index.ts
62422
- var import_os3 = require("os");
62518
+ var import_os4 = require("os");
62423
62519
  var import_path3 = require("path");
62424
62520
 
62425
62521
  // node_modules/@libsql/core/lib-esm/api.js
@@ -66420,8 +66516,8 @@ var HttpStream = class extends Stream {
66420
66516
  let promise;
66421
66517
  try {
66422
66518
  const request = createRequest();
66423
- const fetch = this.#fetch;
66424
- promise = fetch(request);
66519
+ const fetch2 = this.#fetch;
66520
+ promise = fetch2(request);
66425
66521
  } catch (error2) {
66426
66522
  promise = Promise.reject(error2);
66427
66523
  }
@@ -66641,11 +66737,11 @@ var HttpClient = class extends Client {
66641
66737
  }
66642
66738
  };
66643
66739
  async function findEndpoint(customFetch, clientUrl) {
66644
- const fetch = customFetch;
66740
+ const fetch2 = customFetch;
66645
66741
  for (const endpoint of checkEndpoints) {
66646
66742
  const url = new URL(endpoint.versionPath, clientUrl);
66647
66743
  const request = new Request(url.toString(), { method: "GET" });
66648
- const response = await fetch(request);
66744
+ const response = await fetch2(request);
66649
66745
  await response.arrayBuffer();
66650
66746
  if (response.ok) {
66651
66747
  return endpoint;
@@ -67896,7 +67992,7 @@ if (process.argv[2] === "--version" || process.argv[2] === "-v") {
67896
67992
  process.exit(0);
67897
67993
  }
67898
67994
  async function main() {
67899
- const dbPath = process.env.CHRON_DB_PATH ?? (0, import_path3.join)((0, import_os3.homedir)(), ".chron", "chron.db");
67995
+ const dbPath = process.env.CHRON_DB_PATH ?? (0, import_path3.join)((0, import_os4.homedir)(), ".chron", "chron.db");
67900
67996
  if (process.stdin.isTTY) {
67901
67997
  process.stdout.write(`chron-mcp ${version4}
67902
67998
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chron-mcp",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "mcpName": "io.github.sirinivask/chron",
5
5
  "description": "Audit-grade timestamped logs for every AI conversation",
6
6
  "repository": {
@@ -23,6 +23,7 @@
23
23
  "skills",
24
24
  ".claude-plugin",
25
25
  "assets",
26
+ "dashboards",
26
27
  "README.md"
27
28
  ],
28
29
  "engines": {