jinzd-ai-cli 0.4.233 → 0.4.234
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{batch-WWNPYQ6G.js → batch-T3FRX4B6.js} +2 -2
- package/dist/{chat-index-O56HDGFI.js → chat-index-7HXBWQFH.js} +2 -2
- package/dist/{chat-index-WSI7ULRQ.js → chat-index-WYXYD7YP.js} +1 -1
- package/dist/{chunk-4KM53GLG.js → chunk-2QQKS56X.js} +1 -1
- package/dist/{chunk-ACTO5C6K.js → chunk-3KTSJ2EL.js} +1 -1
- package/dist/{chunk-NXYXFRPA.js → chunk-5DTEEWYC.js} +3 -3
- package/dist/{chunk-NVUCDUXE.js → chunk-5MYPIQ3Y.js} +11 -5
- package/dist/{chunk-7KS3RGZ7.js → chunk-5VPRZMZB.js} +2 -2
- package/dist/{chunk-FQSAK75P.js → chunk-6MJMHHRK.js} +3 -3
- package/dist/{chunk-M4FW2NPU.js → chunk-AFT6VFMI.js} +1 -1
- package/dist/{chunk-HX67JAK6.js → chunk-E5RLXMSU.js} +5 -5
- package/dist/{chunk-4OAV7QMB.js → chunk-ETJ5X6V7.js} +1 -1
- package/dist/{chunk-EZGZOK6E.js → chunk-II6RZRTF.js} +1 -1
- package/dist/{chunk-ZWU7FWHO.js → chunk-JMP3LJC2.js} +38 -91
- package/dist/chunk-KCEO2XJ4.js +420 -0
- package/dist/{chunk-5PKNKJYP.js → chunk-L5PNAQ6O.js} +1 -1
- package/dist/{chunk-TJK7AU65.js → chunk-TAXIG7HH.js} +1 -1
- package/dist/{chunk-LF3OUJHK.js → chunk-UO3XWZFE.js} +302 -4
- package/dist/{ci-V5V3JSJC.js → ci-GJ53GPLA.js} +4 -4
- package/dist/{ci-format-XS7LKBO5.js → ci-format-AXE4QCI6.js} +2 -2
- package/dist/{constants-UROQJXLY.js → constants-DC4EXFSB.js} +1 -1
- package/dist/{doctor-cli-BY3FIV2B.js → doctor-cli-HKREND6Q.js} +4 -4
- package/dist/electron-server.js +43 -96
- package/dist/{hub-L2QCRI5M.js → hub-LTTKRGZC.js} +2 -2
- package/dist/index.js +22 -22
- package/dist/{persist-I3JI27OK.js → persist-AMH5SQ4W.js} +3 -3
- package/dist/{pr-IJRUPR5C.js → pr-4VODJVRV.js} +4 -4
- package/dist/{run-tests-FLCVJ6TK.js → run-tests-G5BNTMWQ.js} +2 -2
- package/dist/{run-tests-NNXBS4EL.js → run-tests-QR2PYR2P.js} +1 -1
- package/dist/{server-W2X3C6GJ.js → server-64WZVZZN.js} +15 -15
- package/dist/{server-E6LZB72O.js → server-EO6VQHKL.js} +6 -6
- package/dist/{task-orchestrator-W55VKYTC.js → task-orchestrator-4INCZMLT.js} +6 -6
- package/dist/{usage-MZCDTJWB.js → usage-T3HFMNNV.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-B5TYJO7V.js +0 -123
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
atomicWriteFileSync
|
|
4
|
+
} from "./chunk-IW3Q7AE5.js";
|
|
5
|
+
|
|
6
|
+
// src/security/redactor.ts
|
|
7
|
+
var DEFAULT_PATTERNS = [
|
|
8
|
+
// password: xxx / password = xxx / password="xxx"
|
|
9
|
+
// Covers YAML / JSON / shell-ish / env-file forms.
|
|
10
|
+
{ kind: "password", regex: /\b(password|passwd|pwd)\s*[:=]\s*["']?([^\s"',;{}]{4,200})["']?/gi },
|
|
11
|
+
// PGPASSWORD=xxx (explicit bash env-var form, separate rule because no quotes usually)
|
|
12
|
+
{ kind: "pgpassword-env", regex: /\b(PGPASSWORD)=([^\s"']{4,200})/g },
|
|
13
|
+
// JDBC/PG/MySQL/Mongo connection strings with inline credentials
|
|
14
|
+
// postgresql://user:pass@host/db → redact pass
|
|
15
|
+
{ kind: "db-uri-password", regex: /(\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:\s]+:)([^@\s]+)(@)/gi },
|
|
16
|
+
// Anthropic API keys
|
|
17
|
+
{ kind: "anthropic-key", regex: /(sk-ant-[a-zA-Z0-9_-]{90,})/g },
|
|
18
|
+
// L6 (v0.4.108): Zhipu / GLM API keys — `<24+ hex/base64-ish>.<32+>`
|
|
19
|
+
// Two segments separated by a dot, each safely identifiable by length
|
|
20
|
+
// and char class. Conservative on the lower bound so we don't eat
|
|
21
|
+
// version strings like `1.0.0` or filenames.
|
|
22
|
+
{ kind: "zhipu-key", regex: /\b([a-zA-Z0-9]{24,}\.[a-zA-Z0-9]{32,})\b/g },
|
|
23
|
+
// OpenAI / generic sk- keys — requires length ≥32 to avoid eating short identifiers
|
|
24
|
+
{ kind: "openai-key", regex: /(sk-(?:proj-)?[a-zA-Z0-9_-]{32,})/g },
|
|
25
|
+
// GitHub personal access tokens
|
|
26
|
+
{ kind: "github-pat", regex: /\b(ghp_[a-zA-Z0-9]{36})\b/g },
|
|
27
|
+
{ kind: "github-oauth", regex: /\b(gho_[a-zA-Z0-9]{36})\b/g },
|
|
28
|
+
{ kind: "github-install", regex: /\b(ghs_[a-zA-Z0-9]{36})\b/g },
|
|
29
|
+
// Slack tokens
|
|
30
|
+
{ kind: "slack-bot", regex: /\b(xoxb-\d+-\d+-[a-zA-Z0-9]+)\b/g },
|
|
31
|
+
{ kind: "slack-user", regex: /\b(xoxp-\d+-\d+-\d+-[a-zA-Z0-9]+)\b/g },
|
|
32
|
+
// AWS access key IDs (AKIA...) and secret access keys are context-dependent;
|
|
33
|
+
// we only catch the ID because secret key alone is indistinguishable from random base64.
|
|
34
|
+
{ kind: "aws-access-key-id", regex: /\b(AKIA[0-9A-Z]{16})\b/g },
|
|
35
|
+
// Google API keys
|
|
36
|
+
{ kind: "google-api-key", regex: /\b(AIza[0-9A-Za-z_-]{35})\b/g },
|
|
37
|
+
// Generic "api_key": "..." / "apiKey": "..." / api-key=xxx
|
|
38
|
+
{ kind: "api-key", regex: /\b(api[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{16,200})["']?/gi },
|
|
39
|
+
// Generic token: xxx (only when value looks token-shaped; avoids eating human prose)
|
|
40
|
+
{ kind: "token", regex: /\b(token|access[_-]?token|bearer[_-]?token)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{20,300})["']?/gi },
|
|
41
|
+
// Bearer <token> in Authorization headers
|
|
42
|
+
{ kind: "bearer", regex: /\b(Authorization:\s*Bearer\s+)([a-zA-Z0-9_\-.=]{20,500})/g },
|
|
43
|
+
// Private key PEM blocks — catch the header+footer together
|
|
44
|
+
// P2-PERF-02: PEM bodies use strict Base64 + =\n — narrowed char class avoids backtracking
|
|
45
|
+
{ kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[ \t\n\r]*[A-Za-z0-9+/=\n\r-]*[ \t\n\r]*-----END [A-Z ]*PRIVATE KEY-----/g }
|
|
46
|
+
];
|
|
47
|
+
var MAX_CUSTOM = 32;
|
|
48
|
+
var MAX_PATTERN_LEN = 500;
|
|
49
|
+
var SUSPICIOUS_REDOS = /\([^)]*[+*][^)]*\)\s*[+*{]/;
|
|
50
|
+
var MAX_REDACT_INPUT = 512e3;
|
|
51
|
+
function render(placeholder, kind) {
|
|
52
|
+
return placeholder.replace("{kind}", kind);
|
|
53
|
+
}
|
|
54
|
+
function redactString(input, options) {
|
|
55
|
+
if (!options.enabled || !input) return { redacted: input, hits: [] };
|
|
56
|
+
if (input.length > MAX_REDACT_INPUT) return { redacted: input, hits: [] };
|
|
57
|
+
const placeholder = options.placeholder ?? "[REDACTED:{kind}]";
|
|
58
|
+
const customSrcs = (options.customRegexes ?? []).slice(0, MAX_CUSTOM);
|
|
59
|
+
const patterns = [
|
|
60
|
+
...options.patterns ?? DEFAULT_PATTERNS,
|
|
61
|
+
...customSrcs.flatMap((src, i) => {
|
|
62
|
+
if (typeof src !== "string" || src.length === 0 || src.length > MAX_PATTERN_LEN) return [];
|
|
63
|
+
try {
|
|
64
|
+
const flags = src.match(/^\/.*\/([gimsuy]*)$/)?.[1] ?? "";
|
|
65
|
+
const body = src.replace(/^\/(.*)\/[gimsuy]*$/, "$1");
|
|
66
|
+
if (SUSPICIOUS_REDOS.test(body)) return [];
|
|
67
|
+
const regex = new RegExp(body, flags.includes("g") ? flags : flags + "g");
|
|
68
|
+
return [{ kind: `custom-${i}`, regex }];
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
];
|
|
74
|
+
let redacted = input;
|
|
75
|
+
const hits = [];
|
|
76
|
+
for (const { kind, regex } of patterns) {
|
|
77
|
+
const rx = new RegExp(regex.source, regex.flags);
|
|
78
|
+
const captureCount = new RegExp(rx.source + "|").exec("").length - 1;
|
|
79
|
+
redacted = redacted.replace(rx, (...args) => {
|
|
80
|
+
const match = args[0];
|
|
81
|
+
const g1 = captureCount >= 1 ? args[1] : void 0;
|
|
82
|
+
const g2 = captureCount >= 2 ? args[2] : void 0;
|
|
83
|
+
const offset = args[1 + captureCount];
|
|
84
|
+
if (captureCount >= 2 && typeof g2 === "string") {
|
|
85
|
+
hits.push({ kind, start: offset + (g1?.length ?? 0), length: g2.length, secret: g2 });
|
|
86
|
+
return `${g1}${render(placeholder, kind)}`;
|
|
87
|
+
}
|
|
88
|
+
hits.push({ kind, start: offset, length: match.length, secret: g1 ?? match });
|
|
89
|
+
return render(placeholder, kind);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return { redacted, hits };
|
|
93
|
+
}
|
|
94
|
+
function redactJson(value, options) {
|
|
95
|
+
if (!options.enabled) return { value, hits: [] };
|
|
96
|
+
const allHits = [];
|
|
97
|
+
function walk(v) {
|
|
98
|
+
if (typeof v === "string") {
|
|
99
|
+
const r = redactString(v, options);
|
|
100
|
+
allHits.push(...r.hits);
|
|
101
|
+
return r.redacted;
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
104
|
+
if (v && typeof v === "object") {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const [k, vv] of Object.entries(v)) {
|
|
107
|
+
out[k] = walk(vv);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
return v;
|
|
112
|
+
}
|
|
113
|
+
const redacted = walk(value);
|
|
114
|
+
return { value: redacted, hits: allHits };
|
|
115
|
+
}
|
|
116
|
+
function scanString(input, options) {
|
|
117
|
+
const { hits } = redactString(input, { ...options, enabled: true });
|
|
118
|
+
return hits;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/session/session-index.ts
|
|
122
|
+
import { existsSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
123
|
+
import { join } from "path";
|
|
124
|
+
|
|
125
|
+
// src/session/session-meta.ts
|
|
126
|
+
import { readFileSync, openSync, readSync, closeSync } from "fs";
|
|
127
|
+
function safeDate(value) {
|
|
128
|
+
const d = new Date(value);
|
|
129
|
+
return isNaN(d.getTime()) ? /* @__PURE__ */ new Date(0) : d;
|
|
130
|
+
}
|
|
131
|
+
function extractJsonField(header, field) {
|
|
132
|
+
const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`, "i");
|
|
133
|
+
const m = header.match(re);
|
|
134
|
+
return m ? m[1] : void 0;
|
|
135
|
+
}
|
|
136
|
+
function extractJsonNumberField(header, field) {
|
|
137
|
+
const m = header.match(new RegExp(`"${field}"\\s*:\\s*(\\d+)`));
|
|
138
|
+
return m ? Number(m[1]) : void 0;
|
|
139
|
+
}
|
|
140
|
+
function readSessionMeta(filePath) {
|
|
141
|
+
const HEADER_SIZE = 1024;
|
|
142
|
+
let header;
|
|
143
|
+
try {
|
|
144
|
+
const fd = openSync(filePath, "r");
|
|
145
|
+
const buf = Buffer.alloc(HEADER_SIZE);
|
|
146
|
+
const bytesRead = readSync(fd, buf, 0, HEADER_SIZE, 0);
|
|
147
|
+
closeSync(fd);
|
|
148
|
+
header = buf.toString("utf-8", 0, bytesRead);
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const id = extractJsonField(header, "id");
|
|
153
|
+
const provider = extractJsonField(header, "provider");
|
|
154
|
+
const model = extractJsonField(header, "model");
|
|
155
|
+
const created = extractJsonField(header, "created");
|
|
156
|
+
const updated = extractJsonField(header, "updated");
|
|
157
|
+
const title = extractJsonField(header, "title");
|
|
158
|
+
if (id && provider && model) {
|
|
159
|
+
let messageCount = extractJsonNumberField(header, "messageCount");
|
|
160
|
+
if (messageCount === void 0) {
|
|
161
|
+
messageCount = 0;
|
|
162
|
+
try {
|
|
163
|
+
const full = readFileSync(filePath, "utf-8");
|
|
164
|
+
const matches = full.match(/"role"\s*:/g);
|
|
165
|
+
messageCount = matches ? matches.length : 0;
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
let branchCount;
|
|
170
|
+
let activeBranch;
|
|
171
|
+
try {
|
|
172
|
+
const brMatch = header.match(/"branches"\s*:\s*\[/);
|
|
173
|
+
if (brMatch) {
|
|
174
|
+
const parentMatches = header.match(/"parentBranchId"/g);
|
|
175
|
+
branchCount = parentMatches ? parentMatches.length + 1 : 1;
|
|
176
|
+
}
|
|
177
|
+
activeBranch = extractJsonField(header, "activeBranchId");
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
id,
|
|
182
|
+
provider,
|
|
183
|
+
model,
|
|
184
|
+
messageCount,
|
|
185
|
+
created: safeDate(created),
|
|
186
|
+
updated: safeDate(updated),
|
|
187
|
+
title: title || void 0,
|
|
188
|
+
branchCount,
|
|
189
|
+
activeBranch
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
193
|
+
return {
|
|
194
|
+
id: data.id,
|
|
195
|
+
provider: data.provider,
|
|
196
|
+
model: data.model,
|
|
197
|
+
messageCount: data.messages?.length ?? 0,
|
|
198
|
+
created: safeDate(data.created),
|
|
199
|
+
updated: safeDate(data.updated),
|
|
200
|
+
title: data.title,
|
|
201
|
+
branchCount: data.branches?.length ?? 1,
|
|
202
|
+
activeBranch: data.activeBranchId
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/session/session-index.ts
|
|
207
|
+
var INDEX_FILE = "sessions.index.json";
|
|
208
|
+
function indexFilePath(historyDir) {
|
|
209
|
+
return join(historyDir, INDEX_FILE);
|
|
210
|
+
}
|
|
211
|
+
function loadIndex(historyDir) {
|
|
212
|
+
const fp = indexFilePath(historyDir);
|
|
213
|
+
if (!existsSync(fp)) return null;
|
|
214
|
+
try {
|
|
215
|
+
const data = JSON.parse(readFileSync2(fp, "utf-8"));
|
|
216
|
+
if (data?.version === 1 && data.sessions && data.invertedIndex) {
|
|
217
|
+
return data;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function saveIndex(historyDir, idx) {
|
|
225
|
+
idx.built = (/* @__PURE__ */ new Date()).toISOString();
|
|
226
|
+
const fp = indexFilePath(historyDir);
|
|
227
|
+
atomicWriteFileSync(fp, JSON.stringify(idx));
|
|
228
|
+
}
|
|
229
|
+
function rebuildIndex(historyDir) {
|
|
230
|
+
if (!existsSync(historyDir)) {
|
|
231
|
+
const empty = { version: 1, built: (/* @__PURE__ */ new Date()).toISOString(), sessions: {}, invertedIndex: {} };
|
|
232
|
+
return empty;
|
|
233
|
+
}
|
|
234
|
+
const files = readdirSync(historyDir).filter((f) => f.endsWith(".json"));
|
|
235
|
+
const sessions = {};
|
|
236
|
+
const invertedIndex = /* @__PURE__ */ new Map();
|
|
237
|
+
for (const file of files) {
|
|
238
|
+
const filePath = join(historyDir, file);
|
|
239
|
+
const meta = readSessionMeta(filePath);
|
|
240
|
+
if (!meta) continue;
|
|
241
|
+
let fileSize = 0;
|
|
242
|
+
try {
|
|
243
|
+
fileSize = statSync(filePath).size;
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
let mtimeMs = 0;
|
|
247
|
+
try {
|
|
248
|
+
mtimeMs = statSync(filePath).mtimeMs;
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
const entry = {
|
|
252
|
+
id: meta.id,
|
|
253
|
+
provider: meta.provider,
|
|
254
|
+
model: meta.model,
|
|
255
|
+
title: meta.title,
|
|
256
|
+
messageCount: meta.messageCount,
|
|
257
|
+
fileSize,
|
|
258
|
+
created: meta.created.toISOString(),
|
|
259
|
+
updated: meta.updated.toISOString(),
|
|
260
|
+
branchCount: meta.branchCount,
|
|
261
|
+
activeBranch: meta.activeBranch,
|
|
262
|
+
_mtimeMs: mtimeMs
|
|
263
|
+
};
|
|
264
|
+
sessions[meta.id] = entry;
|
|
265
|
+
addTokensToIndex(invertedIndex, meta.title ?? "", meta.id);
|
|
266
|
+
}
|
|
267
|
+
const serializedIndex = {};
|
|
268
|
+
for (const [token, ids] of invertedIndex) {
|
|
269
|
+
serializedIndex[token] = [...ids];
|
|
270
|
+
}
|
|
271
|
+
const idx = {
|
|
272
|
+
version: 1,
|
|
273
|
+
built: (/* @__PURE__ */ new Date()).toISOString(),
|
|
274
|
+
sessions,
|
|
275
|
+
invertedIndex: serializedIndex
|
|
276
|
+
};
|
|
277
|
+
saveIndex(historyDir, idx);
|
|
278
|
+
return idx;
|
|
279
|
+
}
|
|
280
|
+
function updateSessionInIndex(historyDir, meta, filePath) {
|
|
281
|
+
let idx = loadIndex(historyDir);
|
|
282
|
+
if (!idx) {
|
|
283
|
+
rebuildIndex(historyDir);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
let fileSize = 0;
|
|
287
|
+
try {
|
|
288
|
+
fileSize = statSync(filePath).size;
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
let mtimeMs = 0;
|
|
292
|
+
try {
|
|
293
|
+
mtimeMs = statSync(filePath).mtimeMs;
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
const id = meta.id;
|
|
297
|
+
for (const [token, ids] of Object.entries(idx.invertedIndex)) {
|
|
298
|
+
const filtered = ids.filter((sid) => sid !== id);
|
|
299
|
+
if (filtered.length === 0) {
|
|
300
|
+
delete idx.invertedIndex[token];
|
|
301
|
+
} else {
|
|
302
|
+
idx.invertedIndex[token] = filtered;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
idx.sessions[id] = {
|
|
306
|
+
id,
|
|
307
|
+
provider: meta.provider,
|
|
308
|
+
model: meta.model,
|
|
309
|
+
title: meta.title,
|
|
310
|
+
messageCount: meta.messageCount,
|
|
311
|
+
fileSize,
|
|
312
|
+
created: meta.created.toISOString(),
|
|
313
|
+
updated: meta.updated.toISOString(),
|
|
314
|
+
_mtimeMs: mtimeMs
|
|
315
|
+
};
|
|
316
|
+
const tokens = tokenize(meta.title ?? "");
|
|
317
|
+
for (const token of tokens) {
|
|
318
|
+
const existing = idx.invertedIndex[token] ?? [];
|
|
319
|
+
if (!existing.includes(id)) {
|
|
320
|
+
existing.push(id);
|
|
321
|
+
idx.invertedIndex[token] = existing;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
saveIndex(historyDir, idx);
|
|
325
|
+
}
|
|
326
|
+
function removeSessionFromIndex(historyDir, id) {
|
|
327
|
+
const idx = loadIndex(historyDir);
|
|
328
|
+
if (!idx) return;
|
|
329
|
+
delete idx.sessions[id];
|
|
330
|
+
for (const [token, ids] of Object.entries(idx.invertedIndex)) {
|
|
331
|
+
const filtered = ids.filter((sid) => sid !== id);
|
|
332
|
+
if (filtered.length === 0) {
|
|
333
|
+
delete idx.invertedIndex[token];
|
|
334
|
+
} else {
|
|
335
|
+
idx.invertedIndex[token] = filtered;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
saveIndex(historyDir, idx);
|
|
339
|
+
}
|
|
340
|
+
function listFromIndex(historyDir, opts) {
|
|
341
|
+
let idx = loadIndex(historyDir);
|
|
342
|
+
if (!idx) {
|
|
343
|
+
idx = rebuildIndex(historyDir);
|
|
344
|
+
}
|
|
345
|
+
return Object.values(idx.sessions).map((entry) => ({
|
|
346
|
+
id: entry.id,
|
|
347
|
+
provider: entry.provider,
|
|
348
|
+
model: entry.model,
|
|
349
|
+
title: entry.title,
|
|
350
|
+
messageCount: entry.messageCount,
|
|
351
|
+
created: new Date(entry.created),
|
|
352
|
+
updated: new Date(entry.updated),
|
|
353
|
+
fileSize: opts?.includeFileSize ? entry.fileSize : void 0,
|
|
354
|
+
branchCount: entry.branchCount,
|
|
355
|
+
activeBranch: entry.activeBranch
|
|
356
|
+
})).sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
357
|
+
}
|
|
358
|
+
function searchCandidates(historyDir, query) {
|
|
359
|
+
const idx = loadIndex(historyDir);
|
|
360
|
+
if (!idx) return null;
|
|
361
|
+
const queryTokens = tokenize(query.toLowerCase());
|
|
362
|
+
if (queryTokens.length === 0) return null;
|
|
363
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
364
|
+
for (const token of queryTokens) {
|
|
365
|
+
const matchingIds = idx.invertedIndex[token];
|
|
366
|
+
if (!matchingIds) continue;
|
|
367
|
+
for (const sid of matchingIds) {
|
|
368
|
+
scoreMap.set(sid, (scoreMap.get(sid) ?? 0) + 1);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const candidates = [...scoreMap.entries()].filter(([, score]) => score > 0).sort((a, b) => {
|
|
372
|
+
const scoreDiff = b[1] - a[1];
|
|
373
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
374
|
+
const aMeta = idx.sessions[a[0]];
|
|
375
|
+
const bMeta = idx.sessions[b[0]];
|
|
376
|
+
return (bMeta?.updated ?? "").localeCompare(aMeta?.updated ?? "");
|
|
377
|
+
}).map(([sid]) => sid);
|
|
378
|
+
return candidates.length > 0 ? candidates : null;
|
|
379
|
+
}
|
|
380
|
+
function getIndexMtimeMap(historyDir) {
|
|
381
|
+
const idx = loadIndex(historyDir);
|
|
382
|
+
if (!idx) return null;
|
|
383
|
+
const map = {};
|
|
384
|
+
for (const entry of Object.values(idx.sessions)) {
|
|
385
|
+
map[entry.id] = entry._mtimeMs;
|
|
386
|
+
}
|
|
387
|
+
return map;
|
|
388
|
+
}
|
|
389
|
+
function addTokensToIndex(invertedIndex, text, sessionId) {
|
|
390
|
+
for (const token of tokenize(text)) {
|
|
391
|
+
const s = invertedIndex.get(token) ?? /* @__PURE__ */ new Set();
|
|
392
|
+
s.add(sessionId);
|
|
393
|
+
invertedIndex.set(token, s);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function tokenize(text) {
|
|
397
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
398
|
+
const cjkOnly = text.replace(/[^\u4e00-\u9fff\u3400-\u4dbf]/g, "");
|
|
399
|
+
for (let i = 0; i < cjkOnly.length - 1; i++) {
|
|
400
|
+
tokens.add(cjkOnly.slice(i, i + 2));
|
|
401
|
+
}
|
|
402
|
+
const english = text.replace(/[\u4e00-\u9fff\u3400-\u4dbf]/g, " ").toLowerCase();
|
|
403
|
+
const words = english.split(/[^a-z0-9]+/).filter((w) => w.length >= 2);
|
|
404
|
+
for (const w of words) tokens.add(w);
|
|
405
|
+
return [...tokens];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export {
|
|
409
|
+
DEFAULT_PATTERNS,
|
|
410
|
+
redactString,
|
|
411
|
+
redactJson,
|
|
412
|
+
scanString,
|
|
413
|
+
safeDate,
|
|
414
|
+
readSessionMeta,
|
|
415
|
+
updateSessionInIndex,
|
|
416
|
+
removeSessionFromIndex,
|
|
417
|
+
listFromIndex,
|
|
418
|
+
searchCandidates,
|
|
419
|
+
getIndexMtimeMap
|
|
420
|
+
};
|