chron-mcp 0.1.13 → 0.1.17
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/README.md +305 -0
- package/dashboards/logscale/README.md +121 -0
- package/dashboards/logscale/ai-tool-usage.lql +5 -0
- package/dashboards/logscale/api-key-alert.lql +5 -0
- package/dashboards/logscale/chron-ai-activity.yaml +66 -0
- package/dashboards/logscale/daily-active-developers.lql +3 -0
- package/dashboards/logscale/secrets-by-type.lql +3 -0
- package/dashboards/logscale/sessions-by-developer.lql +6 -0
- package/dashboards/sentinel/README.md +123 -0
- package/dashboards/sentinel/ai-tool-usage.kql +7 -0
- package/dashboards/sentinel/daily-active-developers.kql +6 -0
- package/dashboards/sentinel/events-by-type.kql +5 -0
- package/dashboards/sentinel/secrets-alert.kql +7 -0
- package/dashboards/sentinel/secrets-detected.kql +7 -0
- package/dashboards/sentinel/sessions-by-developer.kql +6 -0
- package/dashboards/splunk/README.md +138 -0
- package/dashboards/splunk/ai-tool-usage.spl +3 -0
- package/dashboards/splunk/daily-active-developers.spl +2 -0
- package/dashboards/splunk/events-by-type.spl +2 -0
- package/dashboards/splunk/secrets-alert.spl +3 -0
- package/dashboards/splunk/secrets-detected.spl +4 -0
- package/dashboards/splunk/sessions-by-developer.spl +3 -0
- package/dist/cli/index.js +560 -13
- package/dist/index.js +311 -73
- package/package.json +2 -1
- package/skills/chron/SKILL.md +8 -1
- package/skills/chron.skill.md +7 -0
package/dist/index.js
CHANGED
|
@@ -6402,7 +6402,7 @@ var require_filesystem = __commonJS({
|
|
|
6402
6402
|
"use strict";
|
|
6403
6403
|
var fs = require("fs");
|
|
6404
6404
|
var LDD_PATH = "/usr/bin/ldd";
|
|
6405
|
-
var
|
|
6405
|
+
var readFileSync3 = (path) => fs.readFileSync(path, "utf-8");
|
|
6406
6406
|
var readFile = (path) => new Promise((resolve, reject) => {
|
|
6407
6407
|
fs.readFile(path, "utf-8", (err, data) => {
|
|
6408
6408
|
if (err) {
|
|
@@ -6414,7 +6414,7 @@ var require_filesystem = __commonJS({
|
|
|
6414
6414
|
});
|
|
6415
6415
|
module2.exports = {
|
|
6416
6416
|
LDD_PATH,
|
|
6417
|
-
readFileSync:
|
|
6417
|
+
readFileSync: readFileSync3,
|
|
6418
6418
|
readFile
|
|
6419
6419
|
};
|
|
6420
6420
|
}
|
|
@@ -6426,7 +6426,7 @@ var require_detect_libc = __commonJS({
|
|
|
6426
6426
|
"use strict";
|
|
6427
6427
|
var childProcess = require("child_process");
|
|
6428
6428
|
var { isLinux, getReport } = require_process();
|
|
6429
|
-
var { LDD_PATH, readFile, readFileSync:
|
|
6429
|
+
var { LDD_PATH, readFile, readFileSync: readFileSync3 } = require_filesystem();
|
|
6430
6430
|
var cachedFamilyFilesystem;
|
|
6431
6431
|
var cachedVersionFilesystem;
|
|
6432
6432
|
var command = "getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true";
|
|
@@ -6507,7 +6507,7 @@ var require_detect_libc = __commonJS({
|
|
|
6507
6507
|
}
|
|
6508
6508
|
cachedFamilyFilesystem = null;
|
|
6509
6509
|
try {
|
|
6510
|
-
const lddContent =
|
|
6510
|
+
const lddContent = readFileSync3(LDD_PATH);
|
|
6511
6511
|
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
|
|
6512
6512
|
} catch (e) {
|
|
6513
6513
|
}
|
|
@@ -6564,7 +6564,7 @@ var require_detect_libc = __commonJS({
|
|
|
6564
6564
|
}
|
|
6565
6565
|
cachedVersionFilesystem = null;
|
|
6566
6566
|
try {
|
|
6567
|
-
const lddContent =
|
|
6567
|
+
const lddContent = readFileSync3(LDD_PATH);
|
|
6568
6568
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
|
6569
6569
|
if (versionMatch) {
|
|
6570
6570
|
cachedVersionFilesystem = versionMatch[1];
|
|
@@ -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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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",
|
|
@@ -12014,7 +12014,7 @@ var init_sql = __esm({
|
|
|
12014
12014
|
return new SQL([new StringChunk(str)]);
|
|
12015
12015
|
}
|
|
12016
12016
|
sql2.raw = raw;
|
|
12017
|
-
function
|
|
12017
|
+
function join5(chunks, separator) {
|
|
12018
12018
|
const result = [];
|
|
12019
12019
|
for (const [i, chunk] of chunks.entries()) {
|
|
12020
12020
|
if (i > 0 && separator !== void 0) {
|
|
@@ -12024,7 +12024,7 @@ var init_sql = __esm({
|
|
|
12024
12024
|
}
|
|
12025
12025
|
return new SQL(result);
|
|
12026
12026
|
}
|
|
12027
|
-
sql2.join =
|
|
12027
|
+
sql2.join = join5;
|
|
12028
12028
|
function identifier(value) {
|
|
12029
12029
|
return new Name(value);
|
|
12030
12030
|
}
|
|
@@ -14955,7 +14955,7 @@ var init_select2 = __esm({
|
|
|
14955
14955
|
const tableName = getTableLikeName(table);
|
|
14956
14956
|
for (const item of extractUsedTable(table))
|
|
14957
14957
|
this.usedTables.add(item);
|
|
14958
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
14958
|
+
if (typeof tableName === "string" && this.config.joins?.some((join5) => join5.alias === tableName)) {
|
|
14959
14959
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
14960
14960
|
}
|
|
14961
14961
|
if (!this.isPartialSelect) {
|
|
@@ -15844,7 +15844,7 @@ var init_update = __esm({
|
|
|
15844
15844
|
createJoin(joinType) {
|
|
15845
15845
|
return (table, on) => {
|
|
15846
15846
|
const tableName = getTableLikeName(table);
|
|
15847
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
15847
|
+
if (typeof tableName === "string" && this.config.joins.some((join5) => join5.alias === tableName)) {
|
|
15848
15848
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
15849
15849
|
}
|
|
15850
15850
|
if (typeof on === "function") {
|
|
@@ -32535,6 +32535,234 @@ 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.17";
|
|
32543
|
+
}
|
|
32544
|
+
});
|
|
32545
|
+
|
|
32546
|
+
// src/utils/relay.ts
|
|
32547
|
+
function getSplunkConfig() {
|
|
32548
|
+
const envUrl = process.env.CHRON_SPLUNK_URL;
|
|
32549
|
+
const envToken = process.env.CHRON_SPLUNK_TOKEN;
|
|
32550
|
+
if (envUrl && envToken) {
|
|
32551
|
+
return { url: envUrl, token: envToken, insecure: process.env.CHRON_SPLUNK_INSECURE === "1" };
|
|
32552
|
+
}
|
|
32553
|
+
const now = Date.now();
|
|
32554
|
+
if (now < _splunkCacheExpiry)
|
|
32555
|
+
return _splunkCache;
|
|
32556
|
+
_splunkCacheExpiry = now + 1e4;
|
|
32557
|
+
try {
|
|
32558
|
+
const cfg = JSON.parse((0, import_fs2.readFileSync)((0, import_path2.join)((0, import_os2.homedir)(), ".chron", "config.json"), "utf8"));
|
|
32559
|
+
const s = cfg?.splunk;
|
|
32560
|
+
_splunkCache = s?.url && s?.token ? { url: s.url, token: s.token, insecure: s.insecure ?? false } : null;
|
|
32561
|
+
} catch {
|
|
32562
|
+
_splunkCache = null;
|
|
32563
|
+
}
|
|
32564
|
+
if (_splunkCache?.insecure)
|
|
32565
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
32566
|
+
return _splunkCache;
|
|
32567
|
+
}
|
|
32568
|
+
async function getAzureToken(tenantId, clientId, clientSecret) {
|
|
32569
|
+
const now = Date.now();
|
|
32570
|
+
if (_azureTokenCache && _azureTokenCache.expiry > now + 6e4) {
|
|
32571
|
+
return _azureTokenCache.token;
|
|
32572
|
+
}
|
|
32573
|
+
try {
|
|
32574
|
+
const res = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
|
|
32575
|
+
method: "POST",
|
|
32576
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
32577
|
+
body: new URLSearchParams({
|
|
32578
|
+
grant_type: "client_credentials",
|
|
32579
|
+
client_id: clientId,
|
|
32580
|
+
client_secret: clientSecret,
|
|
32581
|
+
scope: "https://monitor.azure.com/.default"
|
|
32582
|
+
}).toString()
|
|
32583
|
+
});
|
|
32584
|
+
if (!res.ok)
|
|
32585
|
+
return null;
|
|
32586
|
+
const data = await res.json();
|
|
32587
|
+
_azureTokenCache = { token: data.access_token, expiry: now + data.expires_in * 1e3 };
|
|
32588
|
+
return data.access_token;
|
|
32589
|
+
} catch {
|
|
32590
|
+
return null;
|
|
32591
|
+
}
|
|
32592
|
+
}
|
|
32593
|
+
function getMachineId() {
|
|
32594
|
+
if (!_machineId) {
|
|
32595
|
+
_machineId = "sha256:" + (0, import_crypto3.createHash)("sha256").update((0, import_os2.hostname)()).digest("hex").slice(0, 16);
|
|
32596
|
+
}
|
|
32597
|
+
return _machineId;
|
|
32598
|
+
}
|
|
32599
|
+
function toSplunkEvent(payload) {
|
|
32600
|
+
const event = {
|
|
32601
|
+
event_type: payload.event_type,
|
|
32602
|
+
session_id_prefix: payload.session.id_prefix,
|
|
32603
|
+
ai_tool: payload.session.ai_tool ?? "",
|
|
32604
|
+
os: process.platform,
|
|
32605
|
+
chron_version: version4
|
|
32606
|
+
};
|
|
32607
|
+
if (payload.event_type === "message_logged") {
|
|
32608
|
+
event.role = payload.message.role;
|
|
32609
|
+
} else if (payload.event_type === "secret_detected") {
|
|
32610
|
+
event.detection_type = payload.detection.type;
|
|
32611
|
+
event.masked_value = payload.detection.masked_value;
|
|
32612
|
+
}
|
|
32613
|
+
return {
|
|
32614
|
+
time: new Date(payload.timestamp).getTime() / 1e3,
|
|
32615
|
+
host: (0, import_os2.hostname)(),
|
|
32616
|
+
source: "chron-mcp",
|
|
32617
|
+
sourcetype: "chron:event",
|
|
32618
|
+
event
|
|
32619
|
+
};
|
|
32620
|
+
}
|
|
32621
|
+
function emitToSplunk(payload) {
|
|
32622
|
+
const cfg = getSplunkConfig();
|
|
32623
|
+
if (!cfg)
|
|
32624
|
+
return;
|
|
32625
|
+
const url = `${cfg.url.replace(/\/$/, "")}/services/collector/event`;
|
|
32626
|
+
setImmediate(
|
|
32627
|
+
() => fetch(url, {
|
|
32628
|
+
method: "POST",
|
|
32629
|
+
headers: {
|
|
32630
|
+
"Authorization": `Splunk ${cfg.token}`,
|
|
32631
|
+
"Content-Type": "application/json"
|
|
32632
|
+
},
|
|
32633
|
+
body: JSON.stringify(toSplunkEvent(payload))
|
|
32634
|
+
}).catch(() => void 0)
|
|
32635
|
+
);
|
|
32636
|
+
}
|
|
32637
|
+
function toSentinelRecord(payload) {
|
|
32638
|
+
const record2 = {
|
|
32639
|
+
TimeGenerated: payload.timestamp,
|
|
32640
|
+
EventType: payload.event_type,
|
|
32641
|
+
SessionIdPrefix: payload.session.id_prefix,
|
|
32642
|
+
AiTool: payload.session.ai_tool ?? "",
|
|
32643
|
+
OS: process.platform,
|
|
32644
|
+
ChronVersion: version4,
|
|
32645
|
+
Computer: (0, import_os2.hostname)(),
|
|
32646
|
+
Role: "",
|
|
32647
|
+
DetectionType: "",
|
|
32648
|
+
MaskedValue: ""
|
|
32649
|
+
};
|
|
32650
|
+
if (payload.event_type === "message_logged") {
|
|
32651
|
+
record2.Role = payload.message.role;
|
|
32652
|
+
} else if (payload.event_type === "secret_detected") {
|
|
32653
|
+
record2.DetectionType = payload.detection.type;
|
|
32654
|
+
record2.MaskedValue = payload.detection.masked_value;
|
|
32655
|
+
}
|
|
32656
|
+
return record2;
|
|
32657
|
+
}
|
|
32658
|
+
function emitToSentinel(payload) {
|
|
32659
|
+
const dce = process.env.CHRON_SENTINEL_DCE;
|
|
32660
|
+
const dcrId = process.env.CHRON_SENTINEL_DCR_ID;
|
|
32661
|
+
const stream = process.env.CHRON_SENTINEL_STREAM;
|
|
32662
|
+
const tenantId = process.env.CHRON_SENTINEL_TENANT_ID;
|
|
32663
|
+
const clientId = process.env.CHRON_SENTINEL_CLIENT_ID;
|
|
32664
|
+
const clientSecret = process.env.CHRON_SENTINEL_CLIENT_SECRET;
|
|
32665
|
+
if (!dce || !dcrId || !stream || !tenantId || !clientId || !clientSecret)
|
|
32666
|
+
return;
|
|
32667
|
+
setImmediate(async () => {
|
|
32668
|
+
try {
|
|
32669
|
+
const token = await getAzureToken(tenantId, clientId, clientSecret);
|
|
32670
|
+
if (!token)
|
|
32671
|
+
return;
|
|
32672
|
+
const url = `${dce}/dataCollectionRules/${dcrId}/streams/${stream}?api-version=2023-01-01`;
|
|
32673
|
+
await fetch(url, {
|
|
32674
|
+
method: "POST",
|
|
32675
|
+
headers: {
|
|
32676
|
+
"Authorization": `Bearer ${token}`,
|
|
32677
|
+
"Content-Type": "application/json"
|
|
32678
|
+
},
|
|
32679
|
+
body: JSON.stringify([toSentinelRecord(payload)])
|
|
32680
|
+
});
|
|
32681
|
+
} catch {
|
|
32682
|
+
}
|
|
32683
|
+
});
|
|
32684
|
+
}
|
|
32685
|
+
function toLogScaleAttributes(payload) {
|
|
32686
|
+
const attrs = {
|
|
32687
|
+
event_type: payload.event_type,
|
|
32688
|
+
session_id_prefix: payload.session.id_prefix,
|
|
32689
|
+
ai_tool: payload.session.ai_tool ?? "",
|
|
32690
|
+
os: process.platform,
|
|
32691
|
+
chron_version: version4
|
|
32692
|
+
};
|
|
32693
|
+
if (payload.event_type === "message_logged") {
|
|
32694
|
+
attrs.role = payload.message.role;
|
|
32695
|
+
} else if (payload.event_type === "secret_detected") {
|
|
32696
|
+
attrs.detection_type = payload.detection.type;
|
|
32697
|
+
attrs.masked_value = payload.detection.masked_value;
|
|
32698
|
+
}
|
|
32699
|
+
return attrs;
|
|
32700
|
+
}
|
|
32701
|
+
function emitToLogScale(payload) {
|
|
32702
|
+
const url = process.env.CHRON_LOGSCALE_URL;
|
|
32703
|
+
const token = process.env.CHRON_LOGSCALE_TOKEN;
|
|
32704
|
+
if (!url || !token)
|
|
32705
|
+
return;
|
|
32706
|
+
const body = JSON.stringify([{
|
|
32707
|
+
tags: { host: (0, import_os2.hostname)(), source: "chron-mcp" },
|
|
32708
|
+
events: [{ timestamp: payload.timestamp, attributes: toLogScaleAttributes(payload) }]
|
|
32709
|
+
}]);
|
|
32710
|
+
setImmediate(
|
|
32711
|
+
() => fetch(url, {
|
|
32712
|
+
method: "POST",
|
|
32713
|
+
headers: {
|
|
32714
|
+
"Authorization": `Bearer ${token}`,
|
|
32715
|
+
"Content-Type": "application/json"
|
|
32716
|
+
},
|
|
32717
|
+
body
|
|
32718
|
+
}).catch(() => void 0)
|
|
32719
|
+
);
|
|
32720
|
+
}
|
|
32721
|
+
function emitEvent(payload) {
|
|
32722
|
+
const url = process.env.CHRON_RELAY_URL;
|
|
32723
|
+
const token = process.env.CHRON_RELAY_TOKEN;
|
|
32724
|
+
if (url && token) {
|
|
32725
|
+
const event = {
|
|
32726
|
+
schema_version: "1.0",
|
|
32727
|
+
chron_version: version4,
|
|
32728
|
+
machine_id: getMachineId(),
|
|
32729
|
+
os: process.platform,
|
|
32730
|
+
...payload
|
|
32731
|
+
};
|
|
32732
|
+
setImmediate(
|
|
32733
|
+
() => fetch(url, {
|
|
32734
|
+
method: "POST",
|
|
32735
|
+
headers: {
|
|
32736
|
+
"Authorization": `Bearer ${token}`,
|
|
32737
|
+
"Content-Type": "application/json"
|
|
32738
|
+
},
|
|
32739
|
+
body: JSON.stringify(event)
|
|
32740
|
+
}).catch(() => void 0)
|
|
32741
|
+
);
|
|
32742
|
+
}
|
|
32743
|
+
emitToLogScale(payload);
|
|
32744
|
+
emitToSentinel(payload);
|
|
32745
|
+
emitToSplunk(payload);
|
|
32746
|
+
}
|
|
32747
|
+
var import_fs2, import_path2, import_crypto3, import_os2, _splunkCache, _splunkCacheExpiry, _azureTokenCache, _machineId;
|
|
32748
|
+
var init_relay = __esm({
|
|
32749
|
+
"src/utils/relay.ts"() {
|
|
32750
|
+
"use strict";
|
|
32751
|
+
import_fs2 = require("fs");
|
|
32752
|
+
import_path2 = require("path");
|
|
32753
|
+
import_crypto3 = require("crypto");
|
|
32754
|
+
import_os2 = require("os");
|
|
32755
|
+
init_package();
|
|
32756
|
+
if (process.env.CHRON_SPLUNK_INSECURE === "1") {
|
|
32757
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
32758
|
+
}
|
|
32759
|
+
_splunkCache = null;
|
|
32760
|
+
_splunkCacheExpiry = 0;
|
|
32761
|
+
_azureTokenCache = null;
|
|
32762
|
+
_machineId = null;
|
|
32763
|
+
}
|
|
32764
|
+
});
|
|
32765
|
+
|
|
32538
32766
|
// src/tools/sessions.ts
|
|
32539
32767
|
function startSession(db) {
|
|
32540
32768
|
return async (args) => {
|
|
@@ -32548,6 +32776,7 @@ function startSession(db) {
|
|
|
32548
32776
|
created_at: now,
|
|
32549
32777
|
updated_at: now
|
|
32550
32778
|
});
|
|
32779
|
+
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: id.slice(0, 8), ai_tool: args.ai_tool ?? null } });
|
|
32551
32780
|
return {
|
|
32552
32781
|
content: [{
|
|
32553
32782
|
type: "text",
|
|
@@ -32562,6 +32791,7 @@ function startSession(db) {
|
|
|
32562
32791
|
const session = existing[0];
|
|
32563
32792
|
const [countRow] = await db.select({ count: sql`count(*)` }).from(messages).where(eq(messages.session_id, session.id));
|
|
32564
32793
|
await db.update(sessions).set({ updated_at: now }).where(eq(sessions.id, session.id));
|
|
32794
|
+
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: session.id.slice(0, 8), ai_tool: session.ai_tool } });
|
|
32565
32795
|
return {
|
|
32566
32796
|
content: [{
|
|
32567
32797
|
type: "text",
|
|
@@ -32600,6 +32830,7 @@ function initSession(db) {
|
|
|
32600
32830
|
created = false;
|
|
32601
32831
|
ai_tool = session.ai_tool;
|
|
32602
32832
|
}
|
|
32833
|
+
emitEvent({ event_type: "session_started", timestamp: now, session: { id_prefix: session_id.slice(0, 8), ai_tool } });
|
|
32603
32834
|
const limit = args.limit ?? 10;
|
|
32604
32835
|
const [countRow] = await db.select({
|
|
32605
32836
|
count: sql`count(*)`,
|
|
@@ -32683,18 +32914,19 @@ var init_sessions = __esm({
|
|
|
32683
32914
|
init_esm2();
|
|
32684
32915
|
init_schema();
|
|
32685
32916
|
init_time();
|
|
32917
|
+
init_relay();
|
|
32686
32918
|
}
|
|
32687
32919
|
});
|
|
32688
32920
|
|
|
32689
32921
|
// src/utils/hash.ts
|
|
32690
32922
|
function computeContentHash(sessionId, role, content, createdAt, prevHash) {
|
|
32691
|
-
return (0,
|
|
32923
|
+
return (0, import_crypto4.createHash)("sha256").update(`${sessionId}|${role}|${content}|${createdAt}|${prevHash ?? ""}`).digest("hex");
|
|
32692
32924
|
}
|
|
32693
|
-
var
|
|
32925
|
+
var import_crypto4;
|
|
32694
32926
|
var init_hash = __esm({
|
|
32695
32927
|
"src/utils/hash.ts"() {
|
|
32696
32928
|
"use strict";
|
|
32697
|
-
|
|
32929
|
+
import_crypto4 = require("crypto");
|
|
32698
32930
|
}
|
|
32699
32931
|
});
|
|
32700
32932
|
|
|
@@ -32823,16 +33055,19 @@ async function writeDetections(db, session_id, message_id, content) {
|
|
|
32823
33055
|
if (found.length === 0)
|
|
32824
33056
|
return;
|
|
32825
33057
|
const now = localISOString();
|
|
32826
|
-
|
|
32827
|
-
|
|
32828
|
-
|
|
32829
|
-
|
|
32830
|
-
|
|
32831
|
-
|
|
32832
|
-
|
|
32833
|
-
|
|
32834
|
-
|
|
32835
|
-
);
|
|
33058
|
+
const rows = found.map((s) => ({
|
|
33059
|
+
id: v4_default(),
|
|
33060
|
+
session_id,
|
|
33061
|
+
message_id,
|
|
33062
|
+
type: s.type,
|
|
33063
|
+
masked_value: maskValue(s.value),
|
|
33064
|
+
detected_at: now
|
|
33065
|
+
}));
|
|
33066
|
+
await db.insert(secrets_detected).values(rows);
|
|
33067
|
+
const sessionIdPrefix = session_id.slice(0, 8);
|
|
33068
|
+
for (const row of rows) {
|
|
33069
|
+
emitEvent({ event_type: "secret_detected", timestamp: now, session: { id_prefix: sessionIdPrefix, ai_tool: null }, detection: { type: row.type, masked_value: row.masked_value } });
|
|
33070
|
+
}
|
|
32836
33071
|
}
|
|
32837
33072
|
function isFkError(e) {
|
|
32838
33073
|
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 +33077,7 @@ function logMessage(db) {
|
|
|
32842
33077
|
let id;
|
|
32843
33078
|
let now;
|
|
32844
33079
|
let contentHash;
|
|
33080
|
+
let sessionAiTool;
|
|
32845
33081
|
try {
|
|
32846
33082
|
const result = await db.transaction(async (tx) => {
|
|
32847
33083
|
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 +33095,20 @@ function logMessage(db) {
|
|
|
32859
33095
|
content_hash: hash
|
|
32860
33096
|
});
|
|
32861
33097
|
await tx.update(sessions).set({ updated_at: ts }).where(eq(sessions.id, args.session_id));
|
|
32862
|
-
|
|
33098
|
+
const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, args.session_id)).limit(1);
|
|
33099
|
+
return { id: msgId, now: ts, contentHash: hash, ai_tool: sessionRow?.ai_tool ?? null };
|
|
32863
33100
|
});
|
|
32864
33101
|
id = result.id;
|
|
32865
33102
|
now = result.now;
|
|
32866
33103
|
contentHash = result.contentHash;
|
|
33104
|
+
sessionAiTool = result.ai_tool;
|
|
32867
33105
|
} catch (e) {
|
|
32868
33106
|
if (isFkError(e)) {
|
|
32869
33107
|
return { content: [{ type: "text", text: `Session not found: ${args.session_id}` }], isError: true };
|
|
32870
33108
|
}
|
|
32871
33109
|
throw e;
|
|
32872
33110
|
}
|
|
33111
|
+
emitEvent({ event_type: "message_logged", timestamp: now, session: { id_prefix: args.session_id.slice(0, 8), ai_tool: sessionAiTool }, message: { role: args.role } });
|
|
32873
33112
|
if (args.role === "user") {
|
|
32874
33113
|
setImmediate(() => writeDetections(db, args.session_id, id, args.content).catch(() => void 0));
|
|
32875
33114
|
}
|
|
@@ -32886,6 +33125,7 @@ function logExchange(db) {
|
|
|
32886
33125
|
let assistantNow;
|
|
32887
33126
|
let userHash;
|
|
32888
33127
|
let assistantHash;
|
|
33128
|
+
let sessionAiTool;
|
|
32889
33129
|
try {
|
|
32890
33130
|
const result = await db.transaction(async (tx) => {
|
|
32891
33131
|
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 +33155,8 @@ function logExchange(db) {
|
|
|
32915
33155
|
content_hash: aHash
|
|
32916
33156
|
});
|
|
32917
33157
|
await tx.update(sessions).set({ updated_at: aNow }).where(eq(sessions.id, args.session_id));
|
|
32918
|
-
|
|
33158
|
+
const [sessionRow] = await tx.select({ ai_tool: sessions.ai_tool }).from(sessions).where(eq(sessions.id, args.session_id)).limit(1);
|
|
33159
|
+
return { uId, aId, uNow, aNow, uHash, aHash, ai_tool: sessionRow?.ai_tool ?? null };
|
|
32919
33160
|
});
|
|
32920
33161
|
userId = result.uId;
|
|
32921
33162
|
assistantId = result.aId;
|
|
@@ -32923,12 +33164,16 @@ function logExchange(db) {
|
|
|
32923
33164
|
assistantNow = result.aNow;
|
|
32924
33165
|
userHash = result.uHash;
|
|
32925
33166
|
assistantHash = result.aHash;
|
|
33167
|
+
sessionAiTool = result.ai_tool;
|
|
32926
33168
|
} catch (e) {
|
|
32927
33169
|
if (isFkError(e)) {
|
|
32928
33170
|
return { content: [{ type: "text", text: `Session not found: ${args.session_id}` }], isError: true };
|
|
32929
33171
|
}
|
|
32930
33172
|
throw e;
|
|
32931
33173
|
}
|
|
33174
|
+
const idPrefix = args.session_id.slice(0, 8);
|
|
33175
|
+
emitEvent({ event_type: "message_logged", timestamp: userNow, session: { id_prefix: idPrefix, ai_tool: sessionAiTool }, message: { role: "user" } });
|
|
33176
|
+
emitEvent({ event_type: "message_logged", timestamp: assistantNow, session: { id_prefix: idPrefix, ai_tool: sessionAiTool }, message: { role: "assistant" } });
|
|
32932
33177
|
setImmediate(() => writeDetections(db, args.session_id, userId, args.user_content).catch(() => void 0));
|
|
32933
33178
|
return {
|
|
32934
33179
|
content: [{
|
|
@@ -32954,6 +33199,7 @@ var init_messages = __esm({
|
|
|
32954
33199
|
init_time();
|
|
32955
33200
|
init_hash();
|
|
32956
33201
|
init_detect();
|
|
33202
|
+
init_relay();
|
|
32957
33203
|
}
|
|
32958
33204
|
});
|
|
32959
33205
|
|
|
@@ -33082,14 +33328,6 @@ var init_detect2 = __esm({
|
|
|
33082
33328
|
}
|
|
33083
33329
|
});
|
|
33084
33330
|
|
|
33085
|
-
// package.json
|
|
33086
|
-
var version4;
|
|
33087
|
-
var init_package = __esm({
|
|
33088
|
-
"package.json"() {
|
|
33089
|
-
version4 = "0.1.13";
|
|
33090
|
-
}
|
|
33091
|
-
});
|
|
33092
|
-
|
|
33093
33331
|
// src/server.ts
|
|
33094
33332
|
function createServer(db) {
|
|
33095
33333
|
const server = new McpServer({
|
|
@@ -33208,24 +33446,24 @@ __export(setup_exports, {
|
|
|
33208
33446
|
runSetup: () => runSetup
|
|
33209
33447
|
});
|
|
33210
33448
|
function configPath(...parts) {
|
|
33211
|
-
return (0,
|
|
33449
|
+
return (0, import_path3.join)((0, import_os3.homedir)(), ...parts);
|
|
33212
33450
|
}
|
|
33213
33451
|
function readJson(filePath) {
|
|
33214
|
-
if (!(0,
|
|
33452
|
+
if (!(0, import_fs3.existsSync)(filePath))
|
|
33215
33453
|
return {};
|
|
33216
33454
|
try {
|
|
33217
|
-
return JSON.parse((0,
|
|
33455
|
+
return JSON.parse((0, import_fs3.readFileSync)(filePath, "utf8"));
|
|
33218
33456
|
} catch {
|
|
33219
33457
|
return {};
|
|
33220
33458
|
}
|
|
33221
33459
|
}
|
|
33222
33460
|
function writeJson(filePath, data) {
|
|
33223
|
-
(0,
|
|
33224
|
-
(0,
|
|
33461
|
+
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
|
|
33462
|
+
(0, import_fs3.writeFileSync)(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
33225
33463
|
}
|
|
33226
33464
|
function configureTool(name, filePath) {
|
|
33227
|
-
const dir = (0,
|
|
33228
|
-
if (!(0,
|
|
33465
|
+
const dir = (0, import_path3.dirname)(filePath);
|
|
33466
|
+
if (!(0, import_fs3.existsSync)(dir) && name !== "Claude Code") {
|
|
33229
33467
|
return { tool: name, status: "skipped" };
|
|
33230
33468
|
}
|
|
33231
33469
|
try {
|
|
@@ -33246,7 +33484,7 @@ function configureClaudeCode() {
|
|
|
33246
33484
|
try {
|
|
33247
33485
|
(0, import_child_process.execSync)("claude mcp add chron -- npx -y chron-mcp", { stdio: "pipe" });
|
|
33248
33486
|
} catch {
|
|
33249
|
-
const result = configureTool("Claude Code", (0,
|
|
33487
|
+
const result = configureTool("Claude Code", (0, import_path3.join)((0, import_os3.homedir)(), ".claude", "settings.json"));
|
|
33250
33488
|
if (result.status === "error")
|
|
33251
33489
|
return result;
|
|
33252
33490
|
}
|
|
@@ -33254,13 +33492,13 @@ function configureClaudeCode() {
|
|
|
33254
33492
|
return { tool: "Claude Code", status: "added" };
|
|
33255
33493
|
}
|
|
33256
33494
|
function installClaudeCodeHook() {
|
|
33257
|
-
const skillSrc = (0,
|
|
33258
|
-
const skillDst = (0,
|
|
33259
|
-
if ((0,
|
|
33260
|
-
(0,
|
|
33261
|
-
(0,
|
|
33495
|
+
const skillSrc = (0, import_path3.join)(__dirname, "..", "skills", "chron.skill.md");
|
|
33496
|
+
const skillDst = (0, import_path3.join)((0, import_os3.homedir)(), ".chron", "chron.skill.md");
|
|
33497
|
+
if ((0, import_fs3.existsSync)(skillSrc)) {
|
|
33498
|
+
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(skillDst), { recursive: true });
|
|
33499
|
+
(0, import_fs3.copyFileSync)(skillSrc, skillDst);
|
|
33262
33500
|
}
|
|
33263
|
-
const settingsPath = (0,
|
|
33501
|
+
const settingsPath = (0, import_path3.join)((0, import_os3.homedir)(), ".claude", "settings.json");
|
|
33264
33502
|
const settings = readJson(settingsPath);
|
|
33265
33503
|
if (!settings.hooks)
|
|
33266
33504
|
settings.hooks = {};
|
|
@@ -33284,13 +33522,13 @@ async function runSetup() {
|
|
|
33284
33522
|
}
|
|
33285
33523
|
return results.filter((r) => r.status !== "skipped");
|
|
33286
33524
|
}
|
|
33287
|
-
var
|
|
33525
|
+
var import_fs3, import_os3, import_path3, import_child_process, CHRON_ENTRY, TOOLS;
|
|
33288
33526
|
var init_setup = __esm({
|
|
33289
33527
|
"src/setup.ts"() {
|
|
33290
33528
|
"use strict";
|
|
33291
|
-
|
|
33292
|
-
|
|
33293
|
-
|
|
33529
|
+
import_fs3 = require("fs");
|
|
33530
|
+
import_os3 = require("os");
|
|
33531
|
+
import_path3 = require("path");
|
|
33294
33532
|
import_child_process = require("child_process");
|
|
33295
33533
|
CHRON_ENTRY = {
|
|
33296
33534
|
command: "npx",
|
|
@@ -33299,11 +33537,11 @@ var init_setup = __esm({
|
|
|
33299
33537
|
TOOLS = [
|
|
33300
33538
|
{
|
|
33301
33539
|
name: "Claude Desktop",
|
|
33302
|
-
path: process.platform === "win32" ? (0,
|
|
33540
|
+
path: process.platform === "win32" ? (0, import_path3.join)(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : configPath("Library", "Application Support", "Claude", "claude_desktop_config.json")
|
|
33303
33541
|
},
|
|
33304
33542
|
{
|
|
33305
33543
|
name: "Cursor",
|
|
33306
|
-
path: process.platform === "win32" ? (0,
|
|
33544
|
+
path: process.platform === "win32" ? (0, import_path3.join)(process.env.APPDATA ?? "", "Cursor", "User", "globalStorage", "cursor.mcp", "mcp.json") : configPath(".cursor", "mcp.json")
|
|
33307
33545
|
},
|
|
33308
33546
|
{
|
|
33309
33547
|
name: "Windsurf",
|
|
@@ -53491,7 +53729,7 @@ var require_view = __commonJS({
|
|
|
53491
53729
|
var dirname3 = path.dirname;
|
|
53492
53730
|
var basename = path.basename;
|
|
53493
53731
|
var extname = path.extname;
|
|
53494
|
-
var
|
|
53732
|
+
var join5 = path.join;
|
|
53495
53733
|
var resolve = path.resolve;
|
|
53496
53734
|
module2.exports = View2;
|
|
53497
53735
|
function View2(name, options) {
|
|
@@ -53539,12 +53777,12 @@ var require_view = __commonJS({
|
|
|
53539
53777
|
};
|
|
53540
53778
|
View2.prototype.resolve = function resolve2(dir, file) {
|
|
53541
53779
|
var ext = this.ext;
|
|
53542
|
-
var path2 =
|
|
53780
|
+
var path2 = join5(dir, file);
|
|
53543
53781
|
var stat = tryStat(path2);
|
|
53544
53782
|
if (stat && stat.isFile()) {
|
|
53545
53783
|
return path2;
|
|
53546
53784
|
}
|
|
53547
|
-
path2 =
|
|
53785
|
+
path2 = join5(dir, basename(file, ext), "index" + ext);
|
|
53548
53786
|
stat = tryStat(path2);
|
|
53549
53787
|
if (stat && stat.isFile()) {
|
|
53550
53788
|
return path2;
|
|
@@ -54611,7 +54849,7 @@ var require_send = __commonJS({
|
|
|
54611
54849
|
var Stream2 = require("stream");
|
|
54612
54850
|
var util2 = require("util");
|
|
54613
54851
|
var extname = path.extname;
|
|
54614
|
-
var
|
|
54852
|
+
var join5 = path.join;
|
|
54615
54853
|
var normalize = path.normalize;
|
|
54616
54854
|
var resolve = path.resolve;
|
|
54617
54855
|
var sep = path.sep;
|
|
@@ -54830,7 +55068,7 @@ var require_send = __commonJS({
|
|
|
54830
55068
|
return res;
|
|
54831
55069
|
}
|
|
54832
55070
|
parts = path2.split(sep);
|
|
54833
|
-
path2 = normalize(
|
|
55071
|
+
path2 = normalize(join5(root, path2));
|
|
54834
55072
|
} else {
|
|
54835
55073
|
if (UP_PATH_REGEXP.test(path2)) {
|
|
54836
55074
|
debug('malicious path "%s"', path2);
|
|
@@ -54971,7 +55209,7 @@ var require_send = __commonJS({
|
|
|
54971
55209
|
return self.onStatError(err);
|
|
54972
55210
|
return self.error(404);
|
|
54973
55211
|
}
|
|
54974
|
-
var p =
|
|
55212
|
+
var p = join5(path2, self._index[i]);
|
|
54975
55213
|
debug('stat "%s"', p);
|
|
54976
55214
|
fs.stat(p, function(err2, stat) {
|
|
54977
55215
|
if (err2)
|
|
@@ -57090,17 +57328,17 @@ var require_request = __commonJS({
|
|
|
57090
57328
|
return addrs;
|
|
57091
57329
|
});
|
|
57092
57330
|
defineGetter(req, "subdomains", function subdomains() {
|
|
57093
|
-
var
|
|
57094
|
-
if (!
|
|
57331
|
+
var hostname3 = this.hostname;
|
|
57332
|
+
if (!hostname3)
|
|
57095
57333
|
return [];
|
|
57096
57334
|
var offset = this.app.get("subdomain offset");
|
|
57097
|
-
var subdomains2 = !isIP(
|
|
57335
|
+
var subdomains2 = !isIP(hostname3) ? hostname3.split(".").reverse() : [hostname3];
|
|
57098
57336
|
return subdomains2.slice(offset);
|
|
57099
57337
|
});
|
|
57100
57338
|
defineGetter(req, "path", function path() {
|
|
57101
57339
|
return parse3(this).pathname;
|
|
57102
57340
|
});
|
|
57103
|
-
defineGetter(req, "hostname", function
|
|
57341
|
+
defineGetter(req, "hostname", function hostname3() {
|
|
57104
57342
|
var trust = this.app.get("trust proxy fn");
|
|
57105
57343
|
var host = this.get("X-Forwarded-Host");
|
|
57106
57344
|
if (!host || !trust(this.connection.remoteAddress, 0)) {
|
|
@@ -62419,8 +62657,8 @@ var StdioServerTransport = class {
|
|
|
62419
62657
|
};
|
|
62420
62658
|
|
|
62421
62659
|
// src/index.ts
|
|
62422
|
-
var
|
|
62423
|
-
var
|
|
62660
|
+
var import_os4 = require("os");
|
|
62661
|
+
var import_path4 = require("path");
|
|
62424
62662
|
|
|
62425
62663
|
// node_modules/@libsql/core/lib-esm/api.js
|
|
62426
62664
|
var LibsqlError = class extends Error {
|
|
@@ -66420,8 +66658,8 @@ var HttpStream = class extends Stream {
|
|
|
66420
66658
|
let promise;
|
|
66421
66659
|
try {
|
|
66422
66660
|
const request = createRequest();
|
|
66423
|
-
const
|
|
66424
|
-
promise =
|
|
66661
|
+
const fetch2 = this.#fetch;
|
|
66662
|
+
promise = fetch2(request);
|
|
66425
66663
|
} catch (error2) {
|
|
66426
66664
|
promise = Promise.reject(error2);
|
|
66427
66665
|
}
|
|
@@ -66641,11 +66879,11 @@ var HttpClient = class extends Client {
|
|
|
66641
66879
|
}
|
|
66642
66880
|
};
|
|
66643
66881
|
async function findEndpoint(customFetch, clientUrl) {
|
|
66644
|
-
const
|
|
66882
|
+
const fetch2 = customFetch;
|
|
66645
66883
|
for (const endpoint of checkEndpoints) {
|
|
66646
66884
|
const url = new URL(endpoint.versionPath, clientUrl);
|
|
66647
66885
|
const request = new Request(url.toString(), { method: "GET" });
|
|
66648
|
-
const response = await
|
|
66886
|
+
const response = await fetch2(request);
|
|
66649
66887
|
await response.arrayBuffer();
|
|
66650
66888
|
if (response.ok) {
|
|
66651
66889
|
return endpoint;
|
|
@@ -67896,7 +68134,7 @@ if (process.argv[2] === "--version" || process.argv[2] === "-v") {
|
|
|
67896
68134
|
process.exit(0);
|
|
67897
68135
|
}
|
|
67898
68136
|
async function main() {
|
|
67899
|
-
const dbPath = process.env.CHRON_DB_PATH ?? (0,
|
|
68137
|
+
const dbPath = process.env.CHRON_DB_PATH ?? (0, import_path4.join)((0, import_os4.homedir)(), ".chron", "chron.db");
|
|
67900
68138
|
if (process.stdin.isTTY) {
|
|
67901
68139
|
process.stdout.write(`chron-mcp ${version4}
|
|
67902
68140
|
|