chron-mcp 0.1.24 → 0.1.26
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 +171 -10
- package/dashboards/logscale/README.md +3 -3
- package/dashboards/logscale/api-key-alert.lql +1 -1
- package/dashboards/logscale/chron-ai-activity.yaml +1 -1
- package/dashboards/sentinel/README.md +1 -1
- package/dashboards/sentinel/secrets-alert.kql +1 -1
- package/dashboards/sentinel/secrets-detected.kql +1 -1
- package/dashboards/splunk/README.md +1 -1
- package/dashboards/splunk/secrets-alert.spl +1 -1
- package/dashboards/splunk/secrets-detected.spl +1 -1
- package/dist/cli/index.js +1020 -30
- package/dist/index.js +4 -4
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1184,7 +1184,7 @@ var init_sql = __esm({
|
|
|
1184
1184
|
return new SQL([new StringChunk(str)]);
|
|
1185
1185
|
}
|
|
1186
1186
|
sql2.raw = raw;
|
|
1187
|
-
function
|
|
1187
|
+
function join11(chunks, separator) {
|
|
1188
1188
|
const result = [];
|
|
1189
1189
|
for (const [i, chunk] of chunks.entries()) {
|
|
1190
1190
|
if (i > 0 && separator !== void 0) {
|
|
@@ -1194,7 +1194,7 @@ var init_sql = __esm({
|
|
|
1194
1194
|
}
|
|
1195
1195
|
return new SQL(result);
|
|
1196
1196
|
}
|
|
1197
|
-
sql2.join =
|
|
1197
|
+
sql2.join = join11;
|
|
1198
1198
|
function identifier(value) {
|
|
1199
1199
|
return new Name(value);
|
|
1200
1200
|
}
|
|
@@ -2806,7 +2806,7 @@ var require_filesystem = __commonJS({
|
|
|
2806
2806
|
"use strict";
|
|
2807
2807
|
var fs = require("fs");
|
|
2808
2808
|
var LDD_PATH = "/usr/bin/ldd";
|
|
2809
|
-
var
|
|
2809
|
+
var readFileSync8 = (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: readFileSync8,
|
|
2822
2822
|
readFile
|
|
2823
2823
|
};
|
|
2824
2824
|
}
|
|
@@ -2830,7 +2830,7 @@ var require_detect_libc = __commonJS({
|
|
|
2830
2830
|
"use strict";
|
|
2831
2831
|
var childProcess = require("child_process");
|
|
2832
2832
|
var { isLinux, getReport } = require_process();
|
|
2833
|
-
var { LDD_PATH, readFile, readFileSync:
|
|
2833
|
+
var { LDD_PATH, readFile, readFileSync: readFileSync8 } = require_filesystem();
|
|
2834
2834
|
var cachedFamilyFilesystem;
|
|
2835
2835
|
var cachedVersionFilesystem;
|
|
2836
2836
|
var command2 = "getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true";
|
|
@@ -2911,7 +2911,7 @@ var require_detect_libc = __commonJS({
|
|
|
2911
2911
|
}
|
|
2912
2912
|
cachedFamilyFilesystem = null;
|
|
2913
2913
|
try {
|
|
2914
|
-
const lddContent =
|
|
2914
|
+
const lddContent = readFileSync8(LDD_PATH);
|
|
2915
2915
|
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
|
|
2916
2916
|
} catch (e) {
|
|
2917
2917
|
}
|
|
@@ -2968,7 +2968,7 @@ var require_detect_libc = __commonJS({
|
|
|
2968
2968
|
}
|
|
2969
2969
|
cachedVersionFilesystem = null;
|
|
2970
2970
|
try {
|
|
2971
|
-
const lddContent =
|
|
2971
|
+
const lddContent = readFileSync8(LDD_PATH);
|
|
2972
2972
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
|
2973
2973
|
if (versionMatch) {
|
|
2974
2974
|
cachedVersionFilesystem = versionMatch[1];
|
|
@@ -3553,9 +3553,9 @@ function executeStmt(db, stmt, intMode) {
|
|
|
3553
3553
|
const lastInsertRowid = void 0;
|
|
3554
3554
|
return new ResultSetImpl(columns, columnTypes, rows, rowsAffected, lastInsertRowid);
|
|
3555
3555
|
} else {
|
|
3556
|
-
const
|
|
3557
|
-
const rowsAffected =
|
|
3558
|
-
const lastInsertRowid = BigInt(
|
|
3556
|
+
const info2 = sqlStmt.run(args2);
|
|
3557
|
+
const rowsAffected = info2.changes;
|
|
3558
|
+
const lastInsertRowid = BigInt(info2.lastInsertRowid);
|
|
3559
3559
|
return new ResultSetImpl([], [], [], rowsAffected, lastInsertRowid);
|
|
3560
3560
|
}
|
|
3561
3561
|
} catch (e) {
|
|
@@ -5265,7 +5265,7 @@ var require_sender = __commonJS({
|
|
|
5265
5265
|
"node_modules/ws/lib/sender.js"(exports2, module2) {
|
|
5266
5266
|
"use strict";
|
|
5267
5267
|
var { Duplex } = require("stream");
|
|
5268
|
-
var { randomFillSync } = require("crypto");
|
|
5268
|
+
var { randomFillSync: randomFillSync2 } = require("crypto");
|
|
5269
5269
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
5270
5270
|
var { EMPTY_BUFFER, kWebSocket, NOOP } = require_constants();
|
|
5271
5271
|
var { isBlob, isValidStatusCode } = require_validation();
|
|
@@ -5337,7 +5337,7 @@ var require_sender = __commonJS({
|
|
|
5337
5337
|
if (randomPool === void 0) {
|
|
5338
5338
|
randomPool = Buffer.alloc(RANDOM_POOL_SIZE);
|
|
5339
5339
|
}
|
|
5340
|
-
|
|
5340
|
+
randomFillSync2(randomPool, 0, RANDOM_POOL_SIZE);
|
|
5341
5341
|
randomPoolPointer = 0;
|
|
5342
5342
|
}
|
|
5343
5343
|
mask[0] = randomPool[randomPoolPointer++];
|
|
@@ -7488,13 +7488,13 @@ var require_websocket_server = __commonJS({
|
|
|
7488
7488
|
}
|
|
7489
7489
|
}
|
|
7490
7490
|
if (this.options.verifyClient) {
|
|
7491
|
-
const
|
|
7491
|
+
const info2 = {
|
|
7492
7492
|
origin: req.headers[`${version4 === 8 ? "sec-websocket-origin" : "origin"}`],
|
|
7493
7493
|
secure: !!(req.socket.authorized || req.socket.encrypted),
|
|
7494
7494
|
req
|
|
7495
7495
|
};
|
|
7496
7496
|
if (this.options.verifyClient.length === 2) {
|
|
7497
|
-
this.options.verifyClient(
|
|
7497
|
+
this.options.verifyClient(info2, (verified, code, message, headers) => {
|
|
7498
7498
|
if (!verified) {
|
|
7499
7499
|
return abortHandshake(socket, code || 401, message, headers);
|
|
7500
7500
|
}
|
|
@@ -7510,7 +7510,7 @@ var require_websocket_server = __commonJS({
|
|
|
7510
7510
|
});
|
|
7511
7511
|
return;
|
|
7512
7512
|
}
|
|
7513
|
-
if (!this.options.verifyClient(
|
|
7513
|
+
if (!this.options.verifyClient(info2))
|
|
7514
7514
|
return abortHandshake(socket, 401);
|
|
7515
7515
|
}
|
|
7516
7516
|
this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
|
|
@@ -14252,7 +14252,7 @@ var init_select2 = __esm({
|
|
|
14252
14252
|
const tableName = getTableLikeName(table);
|
|
14253
14253
|
for (const item of extractUsedTable(table))
|
|
14254
14254
|
this.usedTables.add(item);
|
|
14255
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
14255
|
+
if (typeof tableName === "string" && this.config.joins?.some((join11) => join11.alias === tableName)) {
|
|
14256
14256
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
14257
14257
|
}
|
|
14258
14258
|
if (!this.isPartialSelect) {
|
|
@@ -15141,7 +15141,7 @@ var init_update = __esm({
|
|
|
15141
15141
|
createJoin(joinType) {
|
|
15142
15142
|
return (table, on) => {
|
|
15143
15143
|
const tableName = getTableLikeName(table);
|
|
15144
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
15144
|
+
if (typeof tableName === "string" && this.config.joins.some((join11) => join11.alias === tableName)) {
|
|
15145
15145
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
15146
15146
|
}
|
|
15147
15147
|
if (typeof on === "function") {
|
|
@@ -16575,8 +16575,8 @@ function getDbPath() {
|
|
|
16575
16575
|
return envPath;
|
|
16576
16576
|
return (0, import_path.join)((0, import_os.homedir)(), ".chron", "chron.db");
|
|
16577
16577
|
}
|
|
16578
|
-
async function initDb(
|
|
16579
|
-
const path =
|
|
16578
|
+
async function initDb(dbPath3) {
|
|
16579
|
+
const path = dbPath3 ?? getDbPath();
|
|
16580
16580
|
if (!path.startsWith(":")) {
|
|
16581
16581
|
(0, import_fs.mkdirSync)((0, import_path.dirname)(path), { recursive: true });
|
|
16582
16582
|
}
|
|
@@ -16890,14 +16890,14 @@ async function queryIntegrity(db) {
|
|
|
16890
16890
|
unchained++;
|
|
16891
16891
|
continue;
|
|
16892
16892
|
}
|
|
16893
|
-
let
|
|
16893
|
+
let ok3 = true;
|
|
16894
16894
|
for (let i = 1; i < chained.length; i++) {
|
|
16895
16895
|
if (chained[i].prev_hash !== chained[i - 1].content_hash) {
|
|
16896
|
-
|
|
16896
|
+
ok3 = false;
|
|
16897
16897
|
break;
|
|
16898
16898
|
}
|
|
16899
16899
|
}
|
|
16900
|
-
if (
|
|
16900
|
+
if (ok3)
|
|
16901
16901
|
intact++;
|
|
16902
16902
|
else {
|
|
16903
16903
|
broken++;
|
|
@@ -17302,6 +17302,20 @@ function keysDir() {
|
|
|
17302
17302
|
function privKeyPath(sessionId) {
|
|
17303
17303
|
return (0, import_path2.join)(keysDir(), `${sessionId}.key`);
|
|
17304
17304
|
}
|
|
17305
|
+
function pubKeyPath(sessionId) {
|
|
17306
|
+
return (0, import_path2.join)(keysDir(), `${sessionId}.pub`);
|
|
17307
|
+
}
|
|
17308
|
+
function generateSessionKeypair(sessionId) {
|
|
17309
|
+
const { privateKey, publicKey } = (0, import_crypto2.generateKeyPairSync)("ed25519", {
|
|
17310
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
17311
|
+
publicKeyEncoding: { type: "spki", format: "pem" }
|
|
17312
|
+
});
|
|
17313
|
+
const dir = keysDir();
|
|
17314
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
17315
|
+
(0, import_fs3.writeFileSync)(privKeyPath(sessionId), privateKey, { mode: 384 });
|
|
17316
|
+
(0, import_fs3.writeFileSync)(pubKeyPath(sessionId), publicKey);
|
|
17317
|
+
return publicKey;
|
|
17318
|
+
}
|
|
17305
17319
|
function sessionDigest(sessionId, finalContentHash, messageCount, firstCreatedAt) {
|
|
17306
17320
|
const input = `${sessionId}|${finalContentHash}|${messageCount}|${firstCreatedAt}`;
|
|
17307
17321
|
return (0, import_crypto2.createHash)("sha256").update(input).digest();
|
|
@@ -17356,7 +17370,7 @@ var require_package = __commonJS({
|
|
|
17356
17370
|
"package.json"(exports2, module2) {
|
|
17357
17371
|
module2.exports = {
|
|
17358
17372
|
name: "chron-mcp",
|
|
17359
|
-
version: "0.1.
|
|
17373
|
+
version: "0.1.26",
|
|
17360
17374
|
mcpName: "io.github.sirinivask/chron",
|
|
17361
17375
|
description: "Audit-grade timestamped logs for every AI conversation",
|
|
17362
17376
|
repository: {
|
|
@@ -17843,7 +17857,7 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17843
17857
|
}
|
|
17844
17858
|
}]
|
|
17845
17859
|
}]);
|
|
17846
|
-
let
|
|
17860
|
+
let ok3 = false;
|
|
17847
17861
|
let statusCode = 0;
|
|
17848
17862
|
try {
|
|
17849
17863
|
const res = await fetch(url, {
|
|
@@ -17855,7 +17869,7 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17855
17869
|
body: testPayload
|
|
17856
17870
|
});
|
|
17857
17871
|
statusCode = res.status;
|
|
17858
|
-
|
|
17872
|
+
ok3 = res.ok;
|
|
17859
17873
|
} catch (e) {
|
|
17860
17874
|
process.stdout.write(`${RED}failed${RESET5}
|
|
17861
17875
|
`);
|
|
@@ -17864,7 +17878,7 @@ ${DIM4}Sending test event...${RESET5} `);
|
|
|
17864
17878
|
`);
|
|
17865
17879
|
process.exit(1);
|
|
17866
17880
|
}
|
|
17867
|
-
if (!
|
|
17881
|
+
if (!ok3) {
|
|
17868
17882
|
process.stdout.write(`${RED}failed (HTTP ${statusCode})${RESET5}
|
|
17869
17883
|
`);
|
|
17870
17884
|
process.stderr.write(` ${RED}Check your URL and token, then try again.${RESET5}
|
|
@@ -18235,8 +18249,8 @@ function formatLatency(ms) {
|
|
|
18235
18249
|
function formatTs(iso) {
|
|
18236
18250
|
return iso.replace("T", " ").replace(/\.\d+.*$/, "");
|
|
18237
18251
|
}
|
|
18238
|
-
async function buildSummary(sessionPrefix) {
|
|
18239
|
-
const db = await initDb();
|
|
18252
|
+
async function buildSummary(sessionPrefix, existingDb) {
|
|
18253
|
+
const db = existingDb ?? await initDb();
|
|
18240
18254
|
const allSessions = await db.select().from(sessions);
|
|
18241
18255
|
const session = allSessions.find((s) => s.id.startsWith(sessionPrefix));
|
|
18242
18256
|
if (!session)
|
|
@@ -18651,9 +18665,9 @@ ${BOLD7}Signature${RESET7}
|
|
|
18651
18665
|
warn("No manifest.sig \u2014 bundle was not signed");
|
|
18652
18666
|
}
|
|
18653
18667
|
if (sigFile?.session_id && sigFile?.signature) {
|
|
18654
|
-
const
|
|
18668
|
+
const pubKeyPath2 = (0, import_path8.join)(tempDir, "pubkeys", `${sigFile.session_id}.pub`);
|
|
18655
18669
|
try {
|
|
18656
|
-
const pubKey = (0, import_fs9.readFileSync)(
|
|
18670
|
+
const pubKey = (0, import_fs9.readFileSync)(pubKeyPath2, "utf8");
|
|
18657
18671
|
const valid = verifyBufferSignature(pubKey, sigFile.signature, manifestBytes);
|
|
18658
18672
|
if (valid) {
|
|
18659
18673
|
ok(`Ed25519 signature valid (session ${sigFile.session_id.slice(0, 8)})`);
|
|
@@ -18796,6 +18810,964 @@ var init_verify = __esm({
|
|
|
18796
18810
|
}
|
|
18797
18811
|
});
|
|
18798
18812
|
|
|
18813
|
+
// src/cli/doctor.ts
|
|
18814
|
+
var doctor_exports = {};
|
|
18815
|
+
__export(doctor_exports, {
|
|
18816
|
+
runDoctor: () => runDoctor
|
|
18817
|
+
});
|
|
18818
|
+
function ok2(label, detail = "") {
|
|
18819
|
+
process.stdout.write(` ${GREEN5}\u2713${RESET8} ${label}${detail ? ` ${DIM7}${detail}${RESET8}` : ""}
|
|
18820
|
+
`);
|
|
18821
|
+
}
|
|
18822
|
+
function fail2(label, detail = "") {
|
|
18823
|
+
process.stdout.write(` ${RED4}\u2717${RESET8} ${label}${detail ? ` ${DIM7}${detail}${RESET8}` : ""}
|
|
18824
|
+
`);
|
|
18825
|
+
}
|
|
18826
|
+
function warn2(label, detail = "") {
|
|
18827
|
+
process.stdout.write(` ${YELLOW6}!${RESET8} ${label}${detail ? ` ${DIM7}${detail}${RESET8}` : ""}
|
|
18828
|
+
`);
|
|
18829
|
+
}
|
|
18830
|
+
function info(label, detail = "") {
|
|
18831
|
+
process.stdout.write(` ${CYAN6}\xB7${RESET8} ${label}${detail ? ` ${DIM7}${detail}${RESET8}` : ""}
|
|
18832
|
+
`);
|
|
18833
|
+
}
|
|
18834
|
+
function section(title) {
|
|
18835
|
+
process.stdout.write(`
|
|
18836
|
+
${BOLD8}${title}${RESET8}
|
|
18837
|
+
`);
|
|
18838
|
+
}
|
|
18839
|
+
function canWrite(dir) {
|
|
18840
|
+
try {
|
|
18841
|
+
(0, import_fs10.accessSync)(dir, import_fs10.constants.W_OK);
|
|
18842
|
+
return true;
|
|
18843
|
+
} catch {
|
|
18844
|
+
return false;
|
|
18845
|
+
}
|
|
18846
|
+
}
|
|
18847
|
+
function testWriteFile(path) {
|
|
18848
|
+
const probe = path + ".chron-probe";
|
|
18849
|
+
try {
|
|
18850
|
+
(0, import_fs10.writeFileSync)(probe, "ok");
|
|
18851
|
+
(0, import_fs10.unlinkSync)(probe);
|
|
18852
|
+
return true;
|
|
18853
|
+
} catch {
|
|
18854
|
+
return false;
|
|
18855
|
+
}
|
|
18856
|
+
}
|
|
18857
|
+
function dbPath2() {
|
|
18858
|
+
return process.env.CHRON_DB_PATH ?? (0, import_path9.join)((0, import_os9.homedir)(), ".chron", "chron.db");
|
|
18859
|
+
}
|
|
18860
|
+
function keysDir2() {
|
|
18861
|
+
return (0, import_path9.join)((0, import_os9.homedir)(), ".chron", "keys");
|
|
18862
|
+
}
|
|
18863
|
+
function loadConfig2() {
|
|
18864
|
+
try {
|
|
18865
|
+
return JSON.parse((0, import_fs10.readFileSync)((0, import_path9.join)((0, import_os9.homedir)(), ".chron", "config.json"), "utf8"));
|
|
18866
|
+
} catch {
|
|
18867
|
+
return {};
|
|
18868
|
+
}
|
|
18869
|
+
}
|
|
18870
|
+
function mcpConfigs() {
|
|
18871
|
+
const home = (0, import_os9.homedir)();
|
|
18872
|
+
const plat = (0, import_os9.platform)();
|
|
18873
|
+
const candidates = [
|
|
18874
|
+
{
|
|
18875
|
+
name: "Claude Desktop",
|
|
18876
|
+
path: plat === "win32" ? (0, import_path9.join)(process.env.APPDATA ?? (0, import_path9.join)(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : (0, import_path9.join)(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
|
|
18877
|
+
},
|
|
18878
|
+
{ name: "Claude Code", path: (0, import_path9.join)(home, ".claude.json") },
|
|
18879
|
+
{ name: "Cursor", path: (0, import_path9.join)(home, ".cursor", "mcp.json") },
|
|
18880
|
+
{ name: "Windsurf", path: (0, import_path9.join)(home, ".codeium", "windsurf", "mcp_config.json") }
|
|
18881
|
+
];
|
|
18882
|
+
return candidates.map((c) => {
|
|
18883
|
+
if (!(0, import_fs10.existsSync)(c.path)) {
|
|
18884
|
+
return { name: c.name, path: c.path, exists: false, chronConfigured: null };
|
|
18885
|
+
}
|
|
18886
|
+
try {
|
|
18887
|
+
const raw = JSON.parse((0, import_fs10.readFileSync)(c.path, "utf8"));
|
|
18888
|
+
const servers = raw.mcpServers ?? {};
|
|
18889
|
+
const configured = Object.keys(servers).some(
|
|
18890
|
+
(k) => k === "chron" || k === "chron-mcp" || String(servers[k]?.command ?? "").includes("chron")
|
|
18891
|
+
);
|
|
18892
|
+
return { name: c.name, path: c.path, exists: true, chronConfigured: configured };
|
|
18893
|
+
} catch {
|
|
18894
|
+
return { name: c.name, path: c.path, exists: true, chronConfigured: null, note: "Could not parse config" };
|
|
18895
|
+
}
|
|
18896
|
+
});
|
|
18897
|
+
}
|
|
18898
|
+
function npmLatestVersion() {
|
|
18899
|
+
try {
|
|
18900
|
+
return (0, import_child_process3.execSync)("npm view chron-mcp version --json 2>/dev/null", { timeout: 8e3 }).toString().trim().replace(/"/g, "");
|
|
18901
|
+
} catch {
|
|
18902
|
+
return null;
|
|
18903
|
+
}
|
|
18904
|
+
}
|
|
18905
|
+
function npxWorks() {
|
|
18906
|
+
try {
|
|
18907
|
+
(0, import_child_process3.execSync)("npx chron-mcp --version 2>/dev/null", { timeout: 8e3 });
|
|
18908
|
+
return true;
|
|
18909
|
+
} catch {
|
|
18910
|
+
return false;
|
|
18911
|
+
}
|
|
18912
|
+
}
|
|
18913
|
+
async function httpHealthCheck(port = 3001) {
|
|
18914
|
+
try {
|
|
18915
|
+
const controller = new AbortController();
|
|
18916
|
+
const timer = setTimeout(() => controller.abort(), 2e3);
|
|
18917
|
+
const res = await fetch(`http://localhost:${port}/health`, { signal: controller.signal });
|
|
18918
|
+
clearTimeout(timer);
|
|
18919
|
+
if (!res.ok)
|
|
18920
|
+
return { running: false };
|
|
18921
|
+
const body = await res.json();
|
|
18922
|
+
return { running: true, version: body.version };
|
|
18923
|
+
} catch {
|
|
18924
|
+
return { running: false };
|
|
18925
|
+
}
|
|
18926
|
+
}
|
|
18927
|
+
async function runDoctor(args2) {
|
|
18928
|
+
const jsonMode = args2.includes("--json");
|
|
18929
|
+
const port = parseInt(process.env.PORT ?? "3001", 10);
|
|
18930
|
+
const results = [];
|
|
18931
|
+
if (!jsonMode)
|
|
18932
|
+
process.stdout.write(`
|
|
18933
|
+
${BOLD8}chron doctor${RESET8} ${DIM7}v${import_package2.version}${RESET8}
|
|
18934
|
+
`);
|
|
18935
|
+
if (!jsonMode)
|
|
18936
|
+
section("Runtime");
|
|
18937
|
+
const nodeVer = process.version.replace("v", "");
|
|
18938
|
+
const [nodeMajor] = nodeVer.split(".").map(Number);
|
|
18939
|
+
const nodeOk = (nodeMajor ?? 0) >= 18;
|
|
18940
|
+
results.push({ pass: nodeOk, label: `Node.js ${process.version}`, fix: nodeOk ? void 0 : "Upgrade Node.js to v18 or newer: https://nodejs.org" });
|
|
18941
|
+
if (!jsonMode)
|
|
18942
|
+
(nodeOk ? ok2 : fail2)(`Node.js ${process.version}`, nodeOk ? "" : "requires \u2265 18");
|
|
18943
|
+
const latestVersion = npmLatestVersion();
|
|
18944
|
+
if (latestVersion) {
|
|
18945
|
+
const upToDate = import_package2.version === latestVersion;
|
|
18946
|
+
results.push({
|
|
18947
|
+
pass: upToDate,
|
|
18948
|
+
label: `chron-mcp ${import_package2.version}${upToDate ? "" : ` (latest: ${latestVersion})`}`,
|
|
18949
|
+
fix: upToDate ? void 0 : `npm install -g chron-mcp@latest`
|
|
18950
|
+
});
|
|
18951
|
+
if (!jsonMode)
|
|
18952
|
+
(upToDate ? ok2 : warn2)(`chron-mcp ${import_package2.version}`, upToDate ? "up to date" : `latest is ${latestVersion} \u2014 run: npm install -g chron-mcp@latest`);
|
|
18953
|
+
} else {
|
|
18954
|
+
results.push({ pass: "warn", label: `chron-mcp ${import_package2.version}`, detail: "Could not reach npm to check for updates" });
|
|
18955
|
+
if (!jsonMode)
|
|
18956
|
+
warn2(`chron-mcp ${import_package2.version}`, "could not check npm for updates");
|
|
18957
|
+
}
|
|
18958
|
+
const npxOk = npxWorks();
|
|
18959
|
+
results.push({ pass: npxOk, label: "npx chron-mcp --version", fix: npxOk ? void 0 : "Run: npm install -g chron-mcp" });
|
|
18960
|
+
if (!jsonMode)
|
|
18961
|
+
(npxOk ? ok2 : fail2)("npx chron-mcp --version", npxOk ? "works" : "failed \u2014 run: npm install -g chron-mcp");
|
|
18962
|
+
if (!jsonMode)
|
|
18963
|
+
section("Storage");
|
|
18964
|
+
const db = dbPath2();
|
|
18965
|
+
const dbDir = (0, import_path9.join)(db, "..");
|
|
18966
|
+
const dbDirExists = (0, import_fs10.existsSync)(dbDir);
|
|
18967
|
+
if (!dbDirExists) {
|
|
18968
|
+
try {
|
|
18969
|
+
(0, import_fs10.mkdirSync)(dbDir, { recursive: true });
|
|
18970
|
+
} catch {
|
|
18971
|
+
}
|
|
18972
|
+
}
|
|
18973
|
+
const dbWritable = testWriteFile(dbDir);
|
|
18974
|
+
const dbExists = (0, import_fs10.existsSync)(db);
|
|
18975
|
+
results.push({
|
|
18976
|
+
pass: dbWritable,
|
|
18977
|
+
label: `DB directory: ${dbDir}`,
|
|
18978
|
+
detail: dbExists ? "database found" : "no database yet (will be created on first use)",
|
|
18979
|
+
fix: dbWritable ? void 0 : `mkdir -p "${dbDir}" && chmod 700 "${dbDir}"`
|
|
18980
|
+
});
|
|
18981
|
+
if (!jsonMode)
|
|
18982
|
+
(dbWritable ? ok2 : fail2)(`DB: ${db}`, dbExists ? "found" : "not yet created \u2014 will init on first use");
|
|
18983
|
+
if (!dbWritable && !jsonMode)
|
|
18984
|
+
info("fix", `mkdir -p "${dbDir}" && chmod 700 "${dbDir}"`);
|
|
18985
|
+
const keys = keysDir2();
|
|
18986
|
+
const keysExist = (0, import_fs10.existsSync)(keys);
|
|
18987
|
+
if (!keysExist) {
|
|
18988
|
+
try {
|
|
18989
|
+
(0, import_fs10.mkdirSync)(keys, { recursive: true });
|
|
18990
|
+
} catch {
|
|
18991
|
+
}
|
|
18992
|
+
}
|
|
18993
|
+
const keysWritable = canWrite(keys);
|
|
18994
|
+
results.push({
|
|
18995
|
+
pass: keysWritable,
|
|
18996
|
+
label: `Keys directory: ${keys}`,
|
|
18997
|
+
fix: keysWritable ? void 0 : `mkdir -p "${keys}" && chmod 700 "${keys}"`
|
|
18998
|
+
});
|
|
18999
|
+
if (!jsonMode)
|
|
19000
|
+
(keysWritable ? ok2 : fail2)(`Keys: ${keys}`, keysWritable ? "" : "not writable \u2014 Ed25519 signing will fail");
|
|
19001
|
+
if (!jsonMode)
|
|
19002
|
+
section("MCP Tool Configurations");
|
|
19003
|
+
const tools = mcpConfigs();
|
|
19004
|
+
const foundTools = tools.filter((t) => t.exists);
|
|
19005
|
+
if (foundTools.length === 0) {
|
|
19006
|
+
results.push({ pass: "warn", label: "No MCP tool config files found", fix: "Install Claude Desktop or Claude Code and add chron to mcpServers" });
|
|
19007
|
+
if (!jsonMode)
|
|
19008
|
+
warn2("No MCP tool config files detected (Claude Desktop, Claude Code, Cursor, Windsurf)");
|
|
19009
|
+
}
|
|
19010
|
+
for (const t of tools) {
|
|
19011
|
+
if (!t.exists) {
|
|
19012
|
+
results.push({ pass: "skip", label: `${t.name}: not installed` });
|
|
19013
|
+
if (!jsonMode)
|
|
19014
|
+
info(`${t.name}`, "not installed");
|
|
19015
|
+
continue;
|
|
19016
|
+
}
|
|
19017
|
+
if (t.chronConfigured === null) {
|
|
19018
|
+
results.push({ pass: "warn", label: `${t.name}: config parse error`, detail: t.note });
|
|
19019
|
+
if (!jsonMode)
|
|
19020
|
+
warn2(`${t.name}`, t.note ?? "config parse error");
|
|
19021
|
+
} else if (t.chronConfigured) {
|
|
19022
|
+
results.push({ pass: true, label: `${t.name}: chron configured` });
|
|
19023
|
+
if (!jsonMode)
|
|
19024
|
+
ok2(`${t.name}`, "chron configured");
|
|
19025
|
+
} else {
|
|
19026
|
+
results.push({
|
|
19027
|
+
pass: "warn",
|
|
19028
|
+
label: `${t.name}: chron NOT in mcpServers`,
|
|
19029
|
+
fix: `Add chron to mcpServers in ${t.path}
|
|
19030
|
+
See: https://github.com/sirinivask/chron#installation`
|
|
19031
|
+
});
|
|
19032
|
+
if (!jsonMode)
|
|
19033
|
+
warn2(`${t.name}`, `chron not in mcpServers \u2014 add it to ${t.path}`);
|
|
19034
|
+
}
|
|
19035
|
+
}
|
|
19036
|
+
if (!jsonMode)
|
|
19037
|
+
section("HTTP Mode");
|
|
19038
|
+
const health = await httpHealthCheck(port);
|
|
19039
|
+
if (health.running) {
|
|
19040
|
+
results.push({ pass: true, label: `HTTP server responding on port ${port}`, detail: health.version ? `v${health.version}` : void 0 });
|
|
19041
|
+
if (!jsonMode)
|
|
19042
|
+
ok2(`HTTP /health on port ${port}`, health.version ? `v${health.version}` : "");
|
|
19043
|
+
} else {
|
|
19044
|
+
results.push({ pass: "skip", label: `HTTP server not running on port ${port}`, detail: "optional \u2014 only needed for ChatGPT / non-stdio MCP clients" });
|
|
19045
|
+
if (!jsonMode)
|
|
19046
|
+
info(`HTTP server not running on port ${port}`, "optional \u2014 start with CHRON_TRANSPORT=http npx chron-mcp");
|
|
19047
|
+
}
|
|
19048
|
+
if (!jsonMode)
|
|
19049
|
+
section("SIEM Integrations");
|
|
19050
|
+
const config = loadConfig2();
|
|
19051
|
+
const siems = [
|
|
19052
|
+
{
|
|
19053
|
+
name: "Splunk",
|
|
19054
|
+
envOk: !!(process.env.CHRON_SPLUNK_URL && process.env.CHRON_SPLUNK_TOKEN),
|
|
19055
|
+
cfgOk: !!(config.splunk && config.splunk.url)
|
|
19056
|
+
},
|
|
19057
|
+
{
|
|
19058
|
+
name: "Microsoft Sentinel",
|
|
19059
|
+
envOk: !!(process.env.CHRON_SENTINEL_DCE && process.env.CHRON_SENTINEL_CLIENT_ID),
|
|
19060
|
+
cfgOk: !!(config.sentinel && config.sentinel.dce)
|
|
19061
|
+
},
|
|
19062
|
+
{
|
|
19063
|
+
name: "CrowdStrike LogScale",
|
|
19064
|
+
envOk: !!(process.env.CHRON_LOGSCALE_URL && process.env.CHRON_LOGSCALE_TOKEN),
|
|
19065
|
+
cfgOk: !!(config.logscale && config.logscale.url)
|
|
19066
|
+
}
|
|
19067
|
+
];
|
|
19068
|
+
let anySiem = false;
|
|
19069
|
+
for (const s of siems) {
|
|
19070
|
+
if (s.envOk || s.cfgOk) {
|
|
19071
|
+
anySiem = true;
|
|
19072
|
+
results.push({ pass: true, label: `${s.name} connected`, detail: s.envOk ? "via env" : "via ~/.chron/config.json" });
|
|
19073
|
+
if (!jsonMode)
|
|
19074
|
+
ok2(`${s.name}`, s.envOk ? "via env vars" : "via ~/.chron/config.json");
|
|
19075
|
+
} else {
|
|
19076
|
+
results.push({ pass: "skip", label: `${s.name}: not configured`, detail: "optional" });
|
|
19077
|
+
if (!jsonMode)
|
|
19078
|
+
info(`${s.name}`, "not configured \u2014 run: chron connect splunk / sentinel / crowdstrike");
|
|
19079
|
+
}
|
|
19080
|
+
}
|
|
19081
|
+
if (!anySiem && !jsonMode) {
|
|
19082
|
+
process.stdout.write(` ${DIM7}No SIEM configured. Events will only be stored locally.${RESET8}
|
|
19083
|
+
`);
|
|
19084
|
+
}
|
|
19085
|
+
const failures = results.filter((r) => r.pass === false);
|
|
19086
|
+
const warnings = results.filter((r) => r.pass === "warn");
|
|
19087
|
+
if (!jsonMode) {
|
|
19088
|
+
process.stdout.write("\n");
|
|
19089
|
+
if (failures.length === 0 && warnings.length === 0) {
|
|
19090
|
+
process.stdout.write(`${GREEN5}${BOLD8}All checks passed.${RESET8} Chron is correctly set up.
|
|
19091
|
+
|
|
19092
|
+
`);
|
|
19093
|
+
} else {
|
|
19094
|
+
if (failures.length > 0) {
|
|
19095
|
+
process.stdout.write(`${RED4}${BOLD8}${failures.length} issue(s) need attention:${RESET8}
|
|
19096
|
+
`);
|
|
19097
|
+
for (const f of failures) {
|
|
19098
|
+
process.stdout.write(` ${RED4}\u2717${RESET8} ${f.label}
|
|
19099
|
+
`);
|
|
19100
|
+
if (f.fix)
|
|
19101
|
+
process.stdout.write(` ${DIM7}\u2192 ${f.fix}${RESET8}
|
|
19102
|
+
`);
|
|
19103
|
+
}
|
|
19104
|
+
process.stdout.write("\n");
|
|
19105
|
+
}
|
|
19106
|
+
if (warnings.length > 0) {
|
|
19107
|
+
process.stdout.write(`${YELLOW6}${BOLD8}${warnings.length} warning(s):${RESET8}
|
|
19108
|
+
`);
|
|
19109
|
+
for (const w of warnings) {
|
|
19110
|
+
process.stdout.write(` ${YELLOW6}!${RESET8} ${w.label}
|
|
19111
|
+
`);
|
|
19112
|
+
if (w.fix)
|
|
19113
|
+
process.stdout.write(` ${DIM7}\u2192 ${w.fix}${RESET8}
|
|
19114
|
+
`);
|
|
19115
|
+
}
|
|
19116
|
+
process.stdout.write("\n");
|
|
19117
|
+
}
|
|
19118
|
+
}
|
|
19119
|
+
}
|
|
19120
|
+
if (jsonMode) {
|
|
19121
|
+
process.stdout.write(JSON.stringify({
|
|
19122
|
+
chron_version: import_package2.version,
|
|
19123
|
+
node_version: process.version,
|
|
19124
|
+
checks: results.map((r) => ({
|
|
19125
|
+
label: r.label,
|
|
19126
|
+
pass: r.pass,
|
|
19127
|
+
detail: r.detail,
|
|
19128
|
+
fix: r.fix
|
|
19129
|
+
})),
|
|
19130
|
+
summary: {
|
|
19131
|
+
failures: failures.length,
|
|
19132
|
+
warnings: warnings.length,
|
|
19133
|
+
ok: results.filter((r) => r.pass === true).length,
|
|
19134
|
+
skipped: results.filter((r) => r.pass === "skip").length
|
|
19135
|
+
}
|
|
19136
|
+
}, null, 2) + "\n");
|
|
19137
|
+
}
|
|
19138
|
+
process.exit(failures.length > 0 ? 1 : 0);
|
|
19139
|
+
}
|
|
19140
|
+
var import_os9, import_path9, import_fs10, import_child_process3, import_package2, RESET8, BOLD8, DIM7, GREEN5, RED4, YELLOW6, CYAN6;
|
|
19141
|
+
var init_doctor = __esm({
|
|
19142
|
+
"src/cli/doctor.ts"() {
|
|
19143
|
+
"use strict";
|
|
19144
|
+
import_os9 = require("os");
|
|
19145
|
+
import_path9 = require("path");
|
|
19146
|
+
import_fs10 = require("fs");
|
|
19147
|
+
import_child_process3 = require("child_process");
|
|
19148
|
+
import_package2 = __toESM(require_package());
|
|
19149
|
+
RESET8 = "\x1B[0m";
|
|
19150
|
+
BOLD8 = "\x1B[1m";
|
|
19151
|
+
DIM7 = "\x1B[2m";
|
|
19152
|
+
GREEN5 = "\x1B[32m";
|
|
19153
|
+
RED4 = "\x1B[31m";
|
|
19154
|
+
YELLOW6 = "\x1B[33m";
|
|
19155
|
+
CYAN6 = "\x1B[36m";
|
|
19156
|
+
}
|
|
19157
|
+
});
|
|
19158
|
+
|
|
19159
|
+
// node_modules/uuid/dist/esm/stringify.js
|
|
19160
|
+
function unsafeStringify(arr, offset = 0) {
|
|
19161
|
+
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
19162
|
+
}
|
|
19163
|
+
var byteToHex;
|
|
19164
|
+
var init_stringify = __esm({
|
|
19165
|
+
"node_modules/uuid/dist/esm/stringify.js"() {
|
|
19166
|
+
byteToHex = [];
|
|
19167
|
+
for (let i = 0; i < 256; ++i) {
|
|
19168
|
+
byteToHex.push((i + 256).toString(16).slice(1));
|
|
19169
|
+
}
|
|
19170
|
+
}
|
|
19171
|
+
});
|
|
19172
|
+
|
|
19173
|
+
// node_modules/uuid/dist/esm/rng.js
|
|
19174
|
+
function rng() {
|
|
19175
|
+
if (poolPtr > rnds8Pool.length - 16) {
|
|
19176
|
+
(0, import_crypto5.randomFillSync)(rnds8Pool);
|
|
19177
|
+
poolPtr = 0;
|
|
19178
|
+
}
|
|
19179
|
+
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
19180
|
+
}
|
|
19181
|
+
var import_crypto5, rnds8Pool, poolPtr;
|
|
19182
|
+
var init_rng = __esm({
|
|
19183
|
+
"node_modules/uuid/dist/esm/rng.js"() {
|
|
19184
|
+
import_crypto5 = require("crypto");
|
|
19185
|
+
rnds8Pool = new Uint8Array(256);
|
|
19186
|
+
poolPtr = rnds8Pool.length;
|
|
19187
|
+
}
|
|
19188
|
+
});
|
|
19189
|
+
|
|
19190
|
+
// node_modules/uuid/dist/esm/native.js
|
|
19191
|
+
var import_crypto6, native_default;
|
|
19192
|
+
var init_native = __esm({
|
|
19193
|
+
"node_modules/uuid/dist/esm/native.js"() {
|
|
19194
|
+
import_crypto6 = require("crypto");
|
|
19195
|
+
native_default = { randomUUID: import_crypto6.randomUUID };
|
|
19196
|
+
}
|
|
19197
|
+
});
|
|
19198
|
+
|
|
19199
|
+
// node_modules/uuid/dist/esm/v4.js
|
|
19200
|
+
function v4(options, buf, offset) {
|
|
19201
|
+
if (native_default.randomUUID && !buf && !options) {
|
|
19202
|
+
return native_default.randomUUID();
|
|
19203
|
+
}
|
|
19204
|
+
options = options || {};
|
|
19205
|
+
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
19206
|
+
if (rnds.length < 16) {
|
|
19207
|
+
throw new Error("Random bytes length must be >= 16");
|
|
19208
|
+
}
|
|
19209
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
19210
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
19211
|
+
if (buf) {
|
|
19212
|
+
offset = offset || 0;
|
|
19213
|
+
if (offset < 0 || offset + 16 > buf.length) {
|
|
19214
|
+
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
19215
|
+
}
|
|
19216
|
+
for (let i = 0; i < 16; ++i) {
|
|
19217
|
+
buf[offset + i] = rnds[i];
|
|
19218
|
+
}
|
|
19219
|
+
return buf;
|
|
19220
|
+
}
|
|
19221
|
+
return unsafeStringify(rnds);
|
|
19222
|
+
}
|
|
19223
|
+
var v4_default;
|
|
19224
|
+
var init_v4 = __esm({
|
|
19225
|
+
"node_modules/uuid/dist/esm/v4.js"() {
|
|
19226
|
+
init_native();
|
|
19227
|
+
init_rng();
|
|
19228
|
+
init_stringify();
|
|
19229
|
+
v4_default = v4;
|
|
19230
|
+
}
|
|
19231
|
+
});
|
|
19232
|
+
|
|
19233
|
+
// node_modules/uuid/dist/esm/index.js
|
|
19234
|
+
var init_esm = __esm({
|
|
19235
|
+
"node_modules/uuid/dist/esm/index.js"() {
|
|
19236
|
+
init_v4();
|
|
19237
|
+
}
|
|
19238
|
+
});
|
|
19239
|
+
|
|
19240
|
+
// src/utils/detect.ts
|
|
19241
|
+
function luhnCheck(value) {
|
|
19242
|
+
const digits = value.replace(/\D/g, "");
|
|
19243
|
+
let sum = 0;
|
|
19244
|
+
let alt = false;
|
|
19245
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
19246
|
+
let n = parseInt(digits[i], 10);
|
|
19247
|
+
if (alt) {
|
|
19248
|
+
n *= 2;
|
|
19249
|
+
if (n > 9)
|
|
19250
|
+
n -= 9;
|
|
19251
|
+
}
|
|
19252
|
+
sum += n;
|
|
19253
|
+
alt = !alt;
|
|
19254
|
+
}
|
|
19255
|
+
return sum % 10 === 0;
|
|
19256
|
+
}
|
|
19257
|
+
function ibanCheck(value) {
|
|
19258
|
+
const normalized = value.replace(/\s/g, "").toUpperCase();
|
|
19259
|
+
if (normalized.length < 15 || normalized.length > 34)
|
|
19260
|
+
return false;
|
|
19261
|
+
const rearranged = normalized.slice(4) + normalized.slice(0, 4);
|
|
19262
|
+
const numeric2 = rearranged.replace(/[A-Z]/g, (c) => String(c.charCodeAt(0) - 55));
|
|
19263
|
+
let remainder = 0;
|
|
19264
|
+
for (const ch of numeric2) {
|
|
19265
|
+
remainder = (remainder * 10 + parseInt(ch, 10)) % 97;
|
|
19266
|
+
}
|
|
19267
|
+
return remainder === 1;
|
|
19268
|
+
}
|
|
19269
|
+
function contextMatches(kw, input, idx) {
|
|
19270
|
+
const lo = Math.max(0, idx - CONTEXT_WINDOW);
|
|
19271
|
+
const hi = Math.min(input.length, idx + CONTEXT_WINDOW);
|
|
19272
|
+
return kw.test(input.slice(lo, hi));
|
|
19273
|
+
}
|
|
19274
|
+
function annotate(s) {
|
|
19275
|
+
return {
|
|
19276
|
+
...s,
|
|
19277
|
+
severity: SEVERITY2[s.type] ?? "low",
|
|
19278
|
+
category: CATEGORY[s.type] ?? "other"
|
|
19279
|
+
};
|
|
19280
|
+
}
|
|
19281
|
+
function scanForSecrets(input) {
|
|
19282
|
+
const withPriority = [];
|
|
19283
|
+
for (let i = 0; i < PATTERNS.length; i++) {
|
|
19284
|
+
const { type, regex, validate } = PATTERNS[i];
|
|
19285
|
+
regex.lastIndex = 0;
|
|
19286
|
+
let match;
|
|
19287
|
+
while ((match = regex.exec(input)) !== null) {
|
|
19288
|
+
if (validate && !validate(match[0], input, match.index))
|
|
19289
|
+
continue;
|
|
19290
|
+
withPriority.push({
|
|
19291
|
+
type,
|
|
19292
|
+
value: match[0],
|
|
19293
|
+
start: match.index,
|
|
19294
|
+
end: match.index + match[0].length,
|
|
19295
|
+
priority: i
|
|
19296
|
+
});
|
|
19297
|
+
}
|
|
19298
|
+
}
|
|
19299
|
+
withPriority.sort((a, b) => a.priority - b.priority || a.start - b.start);
|
|
19300
|
+
let deduped = deduplicateOverlaps(withPriority);
|
|
19301
|
+
const pairs = detectCredentialPairs(input, deduped);
|
|
19302
|
+
if (pairs.length > 0) {
|
|
19303
|
+
for (const pair of pairs) {
|
|
19304
|
+
deduped.push({ ...pair, priority: -1 });
|
|
19305
|
+
}
|
|
19306
|
+
deduped.sort((a, b) => a.priority - b.priority || a.start - b.start);
|
|
19307
|
+
deduped = deduplicateOverlaps(deduped);
|
|
19308
|
+
}
|
|
19309
|
+
deduped.sort((a, b) => a.start - b.start);
|
|
19310
|
+
return deduped.map(({ priority: _p, ...s }) => annotate(s));
|
|
19311
|
+
}
|
|
19312
|
+
function looksLikePassword(s) {
|
|
19313
|
+
if (s.length < 8)
|
|
19314
|
+
return false;
|
|
19315
|
+
const hasUpper = /[A-Z]/.test(s);
|
|
19316
|
+
const hasLower = /[a-z]/.test(s);
|
|
19317
|
+
const hasDigit = /[0-9]/.test(s);
|
|
19318
|
+
const hasSpecial = /[@!#$%^&*\-_+=?]/.test(s);
|
|
19319
|
+
const score = [hasUpper, hasLower, hasDigit, hasSpecial].filter(Boolean).length;
|
|
19320
|
+
return score >= 3;
|
|
19321
|
+
}
|
|
19322
|
+
function detectCredentialPairs(input, _existing) {
|
|
19323
|
+
const results = [];
|
|
19324
|
+
EMAIL_RE.lastIndex = 0;
|
|
19325
|
+
let match;
|
|
19326
|
+
while ((match = EMAIL_RE.exec(input)) !== null) {
|
|
19327
|
+
const emailStart = match.index;
|
|
19328
|
+
const emailEnd = match.index + match[0].length;
|
|
19329
|
+
const charBefore = emailStart > 0 ? input[emailStart - 1] : "";
|
|
19330
|
+
if (charBefore === "@" || charBefore === ":")
|
|
19331
|
+
continue;
|
|
19332
|
+
const windowStart = Math.max(0, emailStart - PAIR_WINDOW);
|
|
19333
|
+
const windowEnd = Math.min(input.length, emailEnd + PAIR_WINDOW);
|
|
19334
|
+
const surrounding = input.slice(windowStart, windowEnd);
|
|
19335
|
+
const tokens = surrounding.match(/\S+/g) ?? [];
|
|
19336
|
+
for (const token of tokens) {
|
|
19337
|
+
if (token === match[0])
|
|
19338
|
+
continue;
|
|
19339
|
+
if (!looksLikePassword(token))
|
|
19340
|
+
continue;
|
|
19341
|
+
const tokenAbsoluteStart = windowStart + surrounding.indexOf(token);
|
|
19342
|
+
const pairStart = Math.min(emailStart, tokenAbsoluteStart);
|
|
19343
|
+
const pairEnd = Math.max(emailEnd, tokenAbsoluteStart + token.length);
|
|
19344
|
+
results.push({
|
|
19345
|
+
type: "credential_pair",
|
|
19346
|
+
value: input.slice(pairStart, pairEnd),
|
|
19347
|
+
start: pairStart,
|
|
19348
|
+
end: pairEnd
|
|
19349
|
+
});
|
|
19350
|
+
break;
|
|
19351
|
+
}
|
|
19352
|
+
}
|
|
19353
|
+
return results;
|
|
19354
|
+
}
|
|
19355
|
+
function deduplicateOverlaps(secrets) {
|
|
19356
|
+
const kept = [];
|
|
19357
|
+
for (const candidate of secrets) {
|
|
19358
|
+
const overlaps = kept.some((k) => candidate.start < k.end && candidate.end > k.start);
|
|
19359
|
+
if (!overlaps)
|
|
19360
|
+
kept.push(candidate);
|
|
19361
|
+
}
|
|
19362
|
+
return kept;
|
|
19363
|
+
}
|
|
19364
|
+
var CONTEXT_WINDOW, DOB_KEYWORDS, PASS_KEYWORDS, SSN_KEYWORDS, PATTERNS, SEVERITY2, CATEGORY, EMAIL_RE, PAIR_WINDOW;
|
|
19365
|
+
var init_detect = __esm({
|
|
19366
|
+
"src/utils/detect.ts"() {
|
|
19367
|
+
"use strict";
|
|
19368
|
+
CONTEXT_WINDOW = 120;
|
|
19369
|
+
DOB_KEYWORDS = /\b(?:dob|date\s+of\s+birth|birth\s+date|born\s+on|birthday)\b/i;
|
|
19370
|
+
PASS_KEYWORDS = /\b(?:passport|travel\s+document|document\s+no(?:\.|\b))\b/i;
|
|
19371
|
+
SSN_KEYWORDS = /\b(?:ssn|social\s+security|tin\b)\b/i;
|
|
19372
|
+
PATTERNS = [
|
|
19373
|
+
// Private key blocks (must come first — multiline, unambiguous)
|
|
19374
|
+
{ type: "private_key", regex: /-----BEGIN [A-Z ]+ PRIVATE KEY-----[\s\S]+?-----END [A-Z ]+ PRIVATE KEY-----/g },
|
|
19375
|
+
// Cloud / AI provider keys
|
|
19376
|
+
{ type: "aws_access_key", regex: /AKIA[0-9A-Z]{16}/g },
|
|
19377
|
+
{ type: "anthropic_api_key", regex: /sk-ant-api\d{2}-[a-zA-Z0-9\-_]{40,}/g },
|
|
19378
|
+
{ type: "openai_api_key", regex: /sk-proj-[a-zA-Z0-9\-_]{20,}/g },
|
|
19379
|
+
{ type: "google_api_key", regex: /AIza[0-9A-Za-z\-_]{35}/g },
|
|
19380
|
+
// SaaS API keys
|
|
19381
|
+
{ type: "github_token", regex: /gh[pousr]_[a-zA-Z0-9]{30,}/g },
|
|
19382
|
+
{ type: "slack_token", regex: /xox[bpas]-[0-9]{8,13}-[0-9]{8,13}-[a-zA-Z0-9]{20,}/g },
|
|
19383
|
+
{ type: "stripe_key", regex: /sk_(live|test)_[a-zA-Z0-9]{24,}/g },
|
|
19384
|
+
{ type: "sendgrid_key", regex: /SG\.[a-zA-Z0-9\-_]{22,}\.[a-zA-Z0-9\-_]{22,}/g },
|
|
19385
|
+
{ type: "huggingface_token", regex: /hf_[a-zA-Z0-9]{30,}/g },
|
|
19386
|
+
// Auth / session tokens
|
|
19387
|
+
{ type: "jwt", regex: /eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/g },
|
|
19388
|
+
// Credentials in URLs (before env_value so it wins on connection strings)
|
|
19389
|
+
{ type: "url_credentials", regex: /\w+:\/\/[^:@\s\/]{1,64}:[^@\s]{4,}@[^\s]/g },
|
|
19390
|
+
// Passwords in key=value and JSON
|
|
19391
|
+
{ type: "password", regex: /(?:password|passwd|pwd)["']?\s*[=:]\s*["']?([^\s"',]{6,})/gi },
|
|
19392
|
+
// PII — financial
|
|
19393
|
+
{
|
|
19394
|
+
type: "credit_card",
|
|
19395
|
+
regex: /\b\d{4}[\s\-]\d{4}[\s\-]\d{4}[\s\-]\d{4}\b/g,
|
|
19396
|
+
validate: (v) => luhnCheck(v)
|
|
19397
|
+
},
|
|
19398
|
+
{
|
|
19399
|
+
type: "iban",
|
|
19400
|
+
regex: /\b[A-Z]{2}\d{2}[A-Z0-9]{4,30}\b/g,
|
|
19401
|
+
validate: (v) => ibanCheck(v)
|
|
19402
|
+
},
|
|
19403
|
+
// PII — identity (context-aware)
|
|
19404
|
+
{
|
|
19405
|
+
type: "ssn",
|
|
19406
|
+
regex: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
19407
|
+
validate: (_v, input, idx) => {
|
|
19408
|
+
if (contextMatches(SSN_KEYWORDS, input, idx))
|
|
19409
|
+
return true;
|
|
19410
|
+
const surrounding = input.slice(Math.max(0, idx - 20), idx + 20);
|
|
19411
|
+
if (/(?:19|20)\d{2}/.test(surrounding))
|
|
19412
|
+
return false;
|
|
19413
|
+
return true;
|
|
19414
|
+
}
|
|
19415
|
+
},
|
|
19416
|
+
{
|
|
19417
|
+
type: "dob",
|
|
19418
|
+
regex: /\b(?:0?[1-9]|1[0-2])[-\/](?:0?[1-9]|[12]\d|3[01])[-\/](?:19|20)\d{2}\b/g,
|
|
19419
|
+
validate: (_v, input, idx) => contextMatches(DOB_KEYWORDS, input, idx)
|
|
19420
|
+
},
|
|
19421
|
+
{
|
|
19422
|
+
type: "passport",
|
|
19423
|
+
regex: /\b[A-Z]\d{8}\b/g,
|
|
19424
|
+
validate: (_v, input, idx) => contextMatches(PASS_KEYWORDS, input, idx)
|
|
19425
|
+
},
|
|
19426
|
+
// PII — contact
|
|
19427
|
+
{ type: "email", regex: /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g },
|
|
19428
|
+
{
|
|
19429
|
+
type: "phone_us",
|
|
19430
|
+
// Requires area code starting 2-9 (excludes version numbers, dates, plain 7-digit numbers)
|
|
19431
|
+
regex: /\b(?:\+?1[-.\s]?)?\(?([2-9][0-9]{2})\)?[-.\s]([2-9][0-9]{2})[-.\s]([0-9]{4})\b/g
|
|
19432
|
+
},
|
|
19433
|
+
{
|
|
19434
|
+
type: "phone_e164",
|
|
19435
|
+
// E.164 international (non-US, min 8 digits after +)
|
|
19436
|
+
regex: /\+(?!1[^0-9])(?:[2-9][0-9]{1}|[1][^1])[0-9]{6,12}\b/g
|
|
19437
|
+
},
|
|
19438
|
+
// Network — internal RFC-1918 addresses
|
|
19439
|
+
{
|
|
19440
|
+
type: "internal_ip",
|
|
19441
|
+
regex: /\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b/g
|
|
19442
|
+
},
|
|
19443
|
+
// Generic env assignment (lowest priority — catches remaining secrets)
|
|
19444
|
+
{ type: "env_value", regex: /\b[A-Z][A-Z0-9_]{2,}=[^\s]{8,}/g }
|
|
19445
|
+
];
|
|
19446
|
+
SEVERITY2 = {
|
|
19447
|
+
private_key: "critical",
|
|
19448
|
+
credit_card: "critical",
|
|
19449
|
+
ssn: "critical",
|
|
19450
|
+
iban: "critical",
|
|
19451
|
+
aws_access_key: "high",
|
|
19452
|
+
anthropic_api_key: "high",
|
|
19453
|
+
openai_api_key: "high",
|
|
19454
|
+
google_api_key: "high",
|
|
19455
|
+
github_token: "high",
|
|
19456
|
+
slack_token: "high",
|
|
19457
|
+
stripe_key: "high",
|
|
19458
|
+
sendgrid_key: "high",
|
|
19459
|
+
huggingface_token: "high",
|
|
19460
|
+
jwt: "high",
|
|
19461
|
+
url_credentials: "high",
|
|
19462
|
+
password: "high",
|
|
19463
|
+
credential_pair: "high",
|
|
19464
|
+
passport: "high",
|
|
19465
|
+
dob: "high",
|
|
19466
|
+
email: "medium",
|
|
19467
|
+
phone_us: "medium",
|
|
19468
|
+
phone_e164: "medium",
|
|
19469
|
+
internal_ip: "low",
|
|
19470
|
+
env_value: "low"
|
|
19471
|
+
};
|
|
19472
|
+
CATEGORY = {
|
|
19473
|
+
private_key: "credential",
|
|
19474
|
+
credit_card: "financial",
|
|
19475
|
+
ssn: "identity",
|
|
19476
|
+
iban: "financial",
|
|
19477
|
+
passport: "identity",
|
|
19478
|
+
dob: "identity",
|
|
19479
|
+
aws_access_key: "credential",
|
|
19480
|
+
anthropic_api_key: "credential",
|
|
19481
|
+
openai_api_key: "credential",
|
|
19482
|
+
google_api_key: "credential",
|
|
19483
|
+
github_token: "credential",
|
|
19484
|
+
slack_token: "credential",
|
|
19485
|
+
stripe_key: "credential",
|
|
19486
|
+
sendgrid_key: "credential",
|
|
19487
|
+
huggingface_token: "credential",
|
|
19488
|
+
jwt: "credential",
|
|
19489
|
+
url_credentials: "credential",
|
|
19490
|
+
password: "credential",
|
|
19491
|
+
credential_pair: "credential",
|
|
19492
|
+
email: "contact",
|
|
19493
|
+
phone_us: "contact",
|
|
19494
|
+
phone_e164: "contact",
|
|
19495
|
+
internal_ip: "network",
|
|
19496
|
+
env_value: "credential"
|
|
19497
|
+
};
|
|
19498
|
+
EMAIL_RE = /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g;
|
|
19499
|
+
PAIR_WINDOW = 100;
|
|
19500
|
+
}
|
|
19501
|
+
});
|
|
19502
|
+
|
|
19503
|
+
// src/cli/import.ts
|
|
19504
|
+
var import_exports = {};
|
|
19505
|
+
__export(import_exports, {
|
|
19506
|
+
importGptConversations: () => importGptConversations,
|
|
19507
|
+
runImport: () => runImport
|
|
19508
|
+
});
|
|
19509
|
+
function unixToIso(ts) {
|
|
19510
|
+
if (!ts)
|
|
19511
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace("Z", "+00:00");
|
|
19512
|
+
return new Date(ts * 1e3).toISOString().replace("Z", "+00:00");
|
|
19513
|
+
}
|
|
19514
|
+
function extractText(content) {
|
|
19515
|
+
if (!content)
|
|
19516
|
+
return "";
|
|
19517
|
+
if (!content.parts)
|
|
19518
|
+
return "";
|
|
19519
|
+
return content.parts.filter((p) => typeof p === "string" && p.trim().length > 0).join("\n").trim();
|
|
19520
|
+
}
|
|
19521
|
+
function maskValue(v) {
|
|
19522
|
+
return v.length > 8 ? v.slice(0, 4) + "****" + v.slice(-4) : "****";
|
|
19523
|
+
}
|
|
19524
|
+
function linearize(mapping) {
|
|
19525
|
+
const root = Object.values(mapping).find((n) => n.parent === null || !mapping[n.parent ?? ""]);
|
|
19526
|
+
if (!root)
|
|
19527
|
+
return [];
|
|
19528
|
+
const path = [];
|
|
19529
|
+
let current = root;
|
|
19530
|
+
while (current) {
|
|
19531
|
+
path.push(current);
|
|
19532
|
+
const lastChildId = current.children[current.children.length - 1];
|
|
19533
|
+
current = lastChildId ? mapping[lastChildId] : void 0;
|
|
19534
|
+
}
|
|
19535
|
+
return path;
|
|
19536
|
+
}
|
|
19537
|
+
function messagesFromConversation(conv) {
|
|
19538
|
+
const nodes = linearize(conv.mapping);
|
|
19539
|
+
const result = [];
|
|
19540
|
+
for (const node of nodes) {
|
|
19541
|
+
const msg = node.message;
|
|
19542
|
+
if (!msg)
|
|
19543
|
+
continue;
|
|
19544
|
+
const role = msg.author.role;
|
|
19545
|
+
if (role !== "user" && role !== "assistant")
|
|
19546
|
+
continue;
|
|
19547
|
+
const text2 = extractText(msg.content);
|
|
19548
|
+
if (!text2)
|
|
19549
|
+
continue;
|
|
19550
|
+
result.push({ role, content: text2, ts: unixToIso(msg.create_time) });
|
|
19551
|
+
}
|
|
19552
|
+
return result;
|
|
19553
|
+
}
|
|
19554
|
+
function findFile(dir, name) {
|
|
19555
|
+
for (const entry of (0, import_fs11.readdirSync)(dir)) {
|
|
19556
|
+
const full = (0, import_path10.join)(dir, entry);
|
|
19557
|
+
try {
|
|
19558
|
+
if ((0, import_fs11.statSync)(full).isDirectory()) {
|
|
19559
|
+
const found = findFile(full, name);
|
|
19560
|
+
if (found)
|
|
19561
|
+
return found;
|
|
19562
|
+
} else if (entry === name) {
|
|
19563
|
+
return full;
|
|
19564
|
+
}
|
|
19565
|
+
} catch {
|
|
19566
|
+
}
|
|
19567
|
+
}
|
|
19568
|
+
return null;
|
|
19569
|
+
}
|
|
19570
|
+
async function importGptConversations(db, conversations) {
|
|
19571
|
+
let imported = 0;
|
|
19572
|
+
let skipped = 0;
|
|
19573
|
+
let totalMessages = 0;
|
|
19574
|
+
let totalSecrets = 0;
|
|
19575
|
+
const errors = [];
|
|
19576
|
+
for (const conv of conversations) {
|
|
19577
|
+
const externalRef = `chatgpt:${conv.id}`;
|
|
19578
|
+
const displayTitle = (conv.title ?? "Untitled").slice(0, 60);
|
|
19579
|
+
const [existing] = await db.select({ id: sessions.id }).from(sessions).where(eq(sessions.external_ref, externalRef)).limit(1);
|
|
19580
|
+
if (existing) {
|
|
19581
|
+
skipped++;
|
|
19582
|
+
continue;
|
|
19583
|
+
}
|
|
19584
|
+
const msgs = messagesFromConversation(conv);
|
|
19585
|
+
if (msgs.length === 0) {
|
|
19586
|
+
skipped++;
|
|
19587
|
+
continue;
|
|
19588
|
+
}
|
|
19589
|
+
try {
|
|
19590
|
+
let sessionSecrets = 0;
|
|
19591
|
+
await db.transaction(async (tx) => {
|
|
19592
|
+
let baseTitle = (conv.title ?? "Untitled ChatGPT conversation").slice(0, 500);
|
|
19593
|
+
const [titleConflict] = await tx.select({ id: sessions.id }).from(sessions).where(eq(sessions.title, baseTitle)).limit(1);
|
|
19594
|
+
if (titleConflict) {
|
|
19595
|
+
baseTitle = `${baseTitle} (${conv.id.slice(0, 8)})`.slice(0, 500);
|
|
19596
|
+
}
|
|
19597
|
+
const sessionId = v4_default();
|
|
19598
|
+
let publicKey = null;
|
|
19599
|
+
try {
|
|
19600
|
+
publicKey = generateSessionKeypair(sessionId);
|
|
19601
|
+
} catch {
|
|
19602
|
+
}
|
|
19603
|
+
await tx.insert(sessions).values({
|
|
19604
|
+
id: sessionId,
|
|
19605
|
+
title: baseTitle,
|
|
19606
|
+
ai_tool: "chatgpt",
|
|
19607
|
+
created_at: unixToIso(conv.create_time),
|
|
19608
|
+
updated_at: unixToIso(conv.update_time),
|
|
19609
|
+
external_ref: externalRef,
|
|
19610
|
+
parent_session_id: null,
|
|
19611
|
+
public_key: publicKey
|
|
19612
|
+
});
|
|
19613
|
+
let prevHash = null;
|
|
19614
|
+
for (const m of msgs) {
|
|
19615
|
+
const msgId = v4_default();
|
|
19616
|
+
const hash = computeContentHash(sessionId, m.role, m.content, m.ts, prevHash, "message");
|
|
19617
|
+
await tx.insert(messages).values({
|
|
19618
|
+
id: msgId,
|
|
19619
|
+
session_id: sessionId,
|
|
19620
|
+
role: m.role,
|
|
19621
|
+
content: m.content,
|
|
19622
|
+
created_at: m.ts,
|
|
19623
|
+
prev_hash: prevHash,
|
|
19624
|
+
content_hash: hash,
|
|
19625
|
+
event_type: "message"
|
|
19626
|
+
});
|
|
19627
|
+
if (m.role === "user") {
|
|
19628
|
+
const found = scanForSecrets(m.content);
|
|
19629
|
+
if (found.length > 0) {
|
|
19630
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace("Z", "+00:00");
|
|
19631
|
+
await tx.insert(secrets_detected).values(
|
|
19632
|
+
found.map((s) => ({
|
|
19633
|
+
id: v4_default(),
|
|
19634
|
+
session_id: sessionId,
|
|
19635
|
+
message_id: msgId,
|
|
19636
|
+
type: s.type,
|
|
19637
|
+
masked_value: maskValue(s.value),
|
|
19638
|
+
detected_at: now
|
|
19639
|
+
}))
|
|
19640
|
+
);
|
|
19641
|
+
sessionSecrets += found.length;
|
|
19642
|
+
totalSecrets += found.length;
|
|
19643
|
+
}
|
|
19644
|
+
}
|
|
19645
|
+
prevHash = hash;
|
|
19646
|
+
}
|
|
19647
|
+
});
|
|
19648
|
+
totalMessages += msgs.length;
|
|
19649
|
+
imported++;
|
|
19650
|
+
} catch (err) {
|
|
19651
|
+
errors.push({ title: displayTitle, error: err?.message ?? String(err) });
|
|
19652
|
+
}
|
|
19653
|
+
}
|
|
19654
|
+
return { imported, skipped, totalMessages, totalSecrets, errors };
|
|
19655
|
+
}
|
|
19656
|
+
async function importConversations(filePath) {
|
|
19657
|
+
const db = await initDb();
|
|
19658
|
+
let raw;
|
|
19659
|
+
let tempDir = null;
|
|
19660
|
+
const ext = (0, import_path10.extname)(filePath).toLowerCase();
|
|
19661
|
+
if (ext === ".zip") {
|
|
19662
|
+
tempDir = (0, import_path10.join)((0, import_os10.tmpdir)(), `chron-import-${Date.now()}`);
|
|
19663
|
+
(0, import_fs11.mkdirSync)(tempDir, { recursive: true });
|
|
19664
|
+
try {
|
|
19665
|
+
(0, import_child_process4.execFileSync)("unzip", ["-q", "-o", filePath, "conversations.json", "-d", tempDir], { stdio: "pipe" });
|
|
19666
|
+
} catch {
|
|
19667
|
+
try {
|
|
19668
|
+
(0, import_child_process4.execFileSync)("unzip", ["-q", "-o", filePath, "-d", tempDir], { stdio: "pipe" });
|
|
19669
|
+
} catch {
|
|
19670
|
+
process.stderr.write(`Failed to extract ${filePath}. Make sure 'unzip' is installed.
|
|
19671
|
+
`);
|
|
19672
|
+
(0, import_fs11.rmSync)(tempDir, { recursive: true, force: true });
|
|
19673
|
+
process.exit(1);
|
|
19674
|
+
}
|
|
19675
|
+
}
|
|
19676
|
+
const found = findFile(tempDir, "conversations.json");
|
|
19677
|
+
if (!found) {
|
|
19678
|
+
process.stderr.write(`conversations.json not found inside the ZIP.
|
|
19679
|
+
`);
|
|
19680
|
+
(0, import_fs11.rmSync)(tempDir, { recursive: true, force: true });
|
|
19681
|
+
process.exit(1);
|
|
19682
|
+
}
|
|
19683
|
+
raw = (0, import_fs11.readFileSync)(found, "utf8");
|
|
19684
|
+
} else if (ext === ".json") {
|
|
19685
|
+
raw = (0, import_fs11.readFileSync)(filePath, "utf8");
|
|
19686
|
+
} else {
|
|
19687
|
+
process.stderr.write(`Unsupported file type: ${ext}. Pass a .zip export or a conversations.json file.
|
|
19688
|
+
`);
|
|
19689
|
+
process.exit(1);
|
|
19690
|
+
}
|
|
19691
|
+
let conversations;
|
|
19692
|
+
try {
|
|
19693
|
+
conversations = JSON.parse(raw);
|
|
19694
|
+
if (!Array.isArray(conversations))
|
|
19695
|
+
throw new Error("Expected a JSON array");
|
|
19696
|
+
} catch {
|
|
19697
|
+
process.stderr.write(`Failed to parse conversations JSON.
|
|
19698
|
+
`);
|
|
19699
|
+
if (tempDir)
|
|
19700
|
+
(0, import_fs11.rmSync)(tempDir, { recursive: true, force: true });
|
|
19701
|
+
process.exit(1);
|
|
19702
|
+
}
|
|
19703
|
+
process.stdout.write(`
|
|
19704
|
+
${BOLD9}chron import chatgpt${RESET9} ${DIM8}${filePath}${RESET9}
|
|
19705
|
+
`);
|
|
19706
|
+
process.stdout.write(`${DIM8}${conversations.length} conversation(s) found${RESET9}
|
|
19707
|
+
|
|
19708
|
+
`);
|
|
19709
|
+
const result = await importGptConversations(db, conversations);
|
|
19710
|
+
for (const e of result.errors) {
|
|
19711
|
+
process.stderr.write(` ${YELLOW7}!${RESET9} Failed to import "${e.title}": ${e.error}
|
|
19712
|
+
`);
|
|
19713
|
+
}
|
|
19714
|
+
if (tempDir)
|
|
19715
|
+
(0, import_fs11.rmSync)(tempDir, { recursive: true, force: true });
|
|
19716
|
+
process.stdout.write("\n");
|
|
19717
|
+
process.stdout.write(`${BOLD9}Done.${RESET9} `);
|
|
19718
|
+
process.stdout.write(`${GREEN6}${result.imported} imported${RESET9}`);
|
|
19719
|
+
if (result.skipped > 0)
|
|
19720
|
+
process.stdout.write(` ${DIM8}${result.skipped} skipped (already in DB)${RESET9}`);
|
|
19721
|
+
process.stdout.write(` ${CYAN7}${result.totalMessages} messages total${RESET9}`);
|
|
19722
|
+
if (result.totalSecrets > 0)
|
|
19723
|
+
process.stdout.write(` ${YELLOW7}${result.totalSecrets} secret(s) detected${RESET9}`);
|
|
19724
|
+
if (result.errors.length > 0)
|
|
19725
|
+
process.stdout.write(` ${YELLOW7}${result.errors.length} error(s)${RESET9}`);
|
|
19726
|
+
process.stdout.write("\n\n");
|
|
19727
|
+
process.stdout.write(`${DIM8}Run 'chron history' to browse imported sessions.${RESET9}
|
|
19728
|
+
|
|
19729
|
+
`);
|
|
19730
|
+
}
|
|
19731
|
+
async function runImport(args2) {
|
|
19732
|
+
const [subcommand, filePath] = args2;
|
|
19733
|
+
if (subcommand !== "chatgpt" || !filePath) {
|
|
19734
|
+
process.stderr.write(
|
|
19735
|
+
"Usage: chron import chatgpt <file>\n\n <file> Path to ChatGPT export ZIP (chatgpt-export-*.zip)\n or extracted conversations.json\n\nExample:\n chron import chatgpt ~/Downloads/chatgpt-export.zip\n chron import chatgpt ~/Downloads/conversations.json\n"
|
|
19736
|
+
);
|
|
19737
|
+
process.exit(1);
|
|
19738
|
+
}
|
|
19739
|
+
const resolved = filePath.replace(/^~/, process.env.HOME ?? "");
|
|
19740
|
+
if (!(0, import_fs11.existsSync)(resolved)) {
|
|
19741
|
+
process.stderr.write(`File not found: ${resolved}
|
|
19742
|
+
`);
|
|
19743
|
+
process.exit(1);
|
|
19744
|
+
}
|
|
19745
|
+
await importConversations(resolved);
|
|
19746
|
+
}
|
|
19747
|
+
var import_fs11, import_path10, import_os10, import_child_process4, RESET9, BOLD9, DIM8, GREEN6, YELLOW7, CYAN7;
|
|
19748
|
+
var init_import = __esm({
|
|
19749
|
+
"src/cli/import.ts"() {
|
|
19750
|
+
"use strict";
|
|
19751
|
+
import_fs11 = require("fs");
|
|
19752
|
+
import_path10 = require("path");
|
|
19753
|
+
import_os10 = require("os");
|
|
19754
|
+
import_child_process4 = require("child_process");
|
|
19755
|
+
init_drizzle_orm();
|
|
19756
|
+
init_esm();
|
|
19757
|
+
init_db2();
|
|
19758
|
+
init_schema();
|
|
19759
|
+
init_hash();
|
|
19760
|
+
init_detect();
|
|
19761
|
+
init_signing();
|
|
19762
|
+
RESET9 = "\x1B[0m";
|
|
19763
|
+
BOLD9 = "\x1B[1m";
|
|
19764
|
+
DIM8 = "\x1B[2m";
|
|
19765
|
+
GREEN6 = "\x1B[32m";
|
|
19766
|
+
YELLOW7 = "\x1B[33m";
|
|
19767
|
+
CYAN7 = "\x1B[36m";
|
|
19768
|
+
}
|
|
19769
|
+
});
|
|
19770
|
+
|
|
18799
19771
|
// src/cli/index.ts
|
|
18800
19772
|
var [, , command, ...args] = process.argv;
|
|
18801
19773
|
async function main() {
|
|
@@ -18850,6 +19822,16 @@ async function main() {
|
|
|
18850
19822
|
await runVerify2(args);
|
|
18851
19823
|
break;
|
|
18852
19824
|
}
|
|
19825
|
+
case "doctor": {
|
|
19826
|
+
const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
19827
|
+
await runDoctor2(args);
|
|
19828
|
+
break;
|
|
19829
|
+
}
|
|
19830
|
+
case "import": {
|
|
19831
|
+
const { runImport: runImport2 } = await Promise.resolve().then(() => (init_import(), import_exports));
|
|
19832
|
+
await runImport2(args);
|
|
19833
|
+
break;
|
|
19834
|
+
}
|
|
18853
19835
|
default: {
|
|
18854
19836
|
const name = command ? `Unknown command: ${command}
|
|
18855
19837
|
|
|
@@ -18868,6 +19850,8 @@ Commands:
|
|
|
18868
19850
|
sign Sign a session with its Ed25519 key \u2014 produces a .chron.sig file
|
|
18869
19851
|
verify Verify a session's hash chain and Ed25519 signature
|
|
18870
19852
|
prune Delete sessions older than a retention cutoff
|
|
19853
|
+
doctor Check your Chron setup \u2014 Node version, DB, MCP configs, SIEM
|
|
19854
|
+
import Import conversations from external AI tools
|
|
18871
19855
|
|
|
18872
19856
|
Options (history):
|
|
18873
19857
|
--limit=<n> Max sessions to show (default: 20)
|
|
@@ -18894,6 +19878,12 @@ Options (prune):
|
|
|
18894
19878
|
--older-than=<n>d Cutoff in days (falls back to retention_days in config)
|
|
18895
19879
|
--dry-run Show what would be deleted without deleting
|
|
18896
19880
|
--confirm Required flag to actually delete
|
|
19881
|
+
|
|
19882
|
+
Options (doctor):
|
|
19883
|
+
--json Machine-readable JSON output
|
|
19884
|
+
|
|
19885
|
+
Options (import):
|
|
19886
|
+
chatgpt <file> Import from ChatGPT export (.zip or conversations.json)
|
|
18897
19887
|
`
|
|
18898
19888
|
);
|
|
18899
19889
|
process.exit(command ? 1 : 0);
|