jinzd-ai-cli 0.4.88 → 0.4.90

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.
Files changed (32) hide show
  1. package/dist/{batch-7XCYSPJU.js → batch-3MJ56YAA.js} +2 -2
  2. package/dist/chat-index-QKFH7ZP6.js +17 -0
  3. package/dist/chat-index-W2UZ34ZI.js +18 -0
  4. package/dist/{chunk-PFYAAX2S.js → chunk-2DXY7UGF.js} +16 -63
  5. package/dist/chunk-5S3PIG5O.js +453 -0
  6. package/dist/{chunk-QT2KNL3V.js → chunk-AB2LA33A.js} +1 -1
  7. package/dist/chunk-ANYYM4CF.js +460 -0
  8. package/dist/{chunk-P6EQZKKG.js → chunk-BJXGZFE6.js} +1 -1
  9. package/dist/{chunk-L3MBIO36.js → chunk-DJGP7AR6.js} +5 -106
  10. package/dist/{chunk-V3NMERIB.js → chunk-EEEAFWNK.js} +1 -1
  11. package/dist/{chunk-YDHIU24C.js → chunk-G65IDWVP.js} +76 -3
  12. package/dist/chunk-JV5N65KN.js +50 -0
  13. package/dist/chunk-KHYD3WXE.js +52 -0
  14. package/dist/{chunk-CQQQFNND.js → chunk-KJLJPUY2.js} +6 -4
  15. package/dist/{chunk-GTKJUEBS.js → chunk-MO7MWNWC.js} +6 -4
  16. package/dist/{chunk-XMA222FQ.js → chunk-PASCDYMH.js} +17 -63
  17. package/dist/{chunk-VGXNE37B.js → chunk-WPQ4D6T3.js} +1 -1
  18. package/dist/electron-server.js +187 -104
  19. package/dist/{hub-IR4INXSU.js → hub-B7NJSCWF.js} +1 -1
  20. package/dist/index.js +158 -19
  21. package/dist/{run-tests-FQHDUYOG.js → run-tests-2DYVHTIH.js} +2 -2
  22. package/dist/{run-tests-JVWIGY7P.js → run-tests-37FEBJTR.js} +1 -1
  23. package/dist/{semantic-MYAXLDCZ.js → semantic-3KJPAUW6.js} +3 -2
  24. package/dist/{semantic-ICJ536BG.js → semantic-YDRPPVWK.js} +3 -2
  25. package/dist/{server-UWKRV5DK.js → server-FCTPLKGO.js} +121 -13
  26. package/dist/{server-HTVVWKFN.js → server-S6JYNMMF.js} +7 -5
  27. package/dist/{task-orchestrator-6MI6LD7T.js → task-orchestrator-K6HDX4YE.js} +7 -5
  28. package/dist/{vector-store-UR7IARXB.js → vector-store-NDUFLNGN.js} +2 -1
  29. package/dist/{vector-store-YTVHACBV.js → vector-store-QARQ2P6D.js} +2 -1
  30. package/dist/web/client/app.js +201 -0
  31. package/dist/web/client/index.html +24 -0
  32. package/package.json +1 -1
@@ -0,0 +1,460 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ EMBEDDING_DIM,
4
+ embed,
5
+ embedOne
6
+ } from "./chunk-KHYD3WXE.js";
7
+
8
+ // src/memory/chat-index.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import os from "os";
12
+ import crypto from "crypto";
13
+
14
+ // src/security/redactor.ts
15
+ var DEFAULT_PATTERNS = [
16
+ // password: xxx / password = xxx / password="xxx"
17
+ // Covers YAML / JSON / shell-ish / env-file forms.
18
+ { kind: "password", regex: /\b(password|passwd|pwd)\s*[:=]\s*["']?([^\s"',;{}]{4,200})["']?/gi },
19
+ // PGPASSWORD=xxx (explicit bash env-var form, separate rule because no quotes usually)
20
+ { kind: "pgpassword-env", regex: /\b(PGPASSWORD)=([^\s"']{4,200})/g },
21
+ // JDBC/PG/MySQL/Mongo connection strings with inline credentials
22
+ // postgresql://user:pass@host/db → redact pass
23
+ { kind: "db-uri-password", regex: /(\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:\s]+:)([^@\s]+)(@)/gi },
24
+ // Anthropic API keys
25
+ { kind: "anthropic-key", regex: /(sk-ant-[a-zA-Z0-9_-]{90,})/g },
26
+ // OpenAI / generic sk- keys — requires length ≥32 to avoid eating short identifiers
27
+ { kind: "openai-key", regex: /(sk-(?:proj-)?[a-zA-Z0-9_-]{32,})/g },
28
+ // GitHub personal access tokens
29
+ { kind: "github-pat", regex: /\b(ghp_[a-zA-Z0-9]{36})\b/g },
30
+ { kind: "github-oauth", regex: /\b(gho_[a-zA-Z0-9]{36})\b/g },
31
+ { kind: "github-install", regex: /\b(ghs_[a-zA-Z0-9]{36})\b/g },
32
+ // Slack tokens
33
+ { kind: "slack-bot", regex: /\b(xoxb-\d+-\d+-[a-zA-Z0-9]+)\b/g },
34
+ { kind: "slack-user", regex: /\b(xoxp-\d+-\d+-\d+-[a-zA-Z0-9]+)\b/g },
35
+ // AWS access key IDs (AKIA...) and secret access keys are context-dependent;
36
+ // we only catch the ID because secret key alone is indistinguishable from random base64.
37
+ { kind: "aws-access-key-id", regex: /\b(AKIA[0-9A-Z]{16})\b/g },
38
+ // Google API keys
39
+ { kind: "google-api-key", regex: /\b(AIza[0-9A-Za-z_-]{35})\b/g },
40
+ // Generic "api_key": "..." / "apiKey": "..." / api-key=xxx
41
+ { kind: "api-key", regex: /\b(api[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{16,200})["']?/gi },
42
+ // Generic token: xxx (only when value looks token-shaped; avoids eating human prose)
43
+ { kind: "token", regex: /\b(token|access[_-]?token|bearer[_-]?token)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{20,300})["']?/gi },
44
+ // Bearer <token> in Authorization headers
45
+ { kind: "bearer", regex: /\b(Authorization:\s*Bearer\s+)([a-zA-Z0-9_\-.=]{20,500})/g },
46
+ // Private key PEM blocks — catch the header+footer together
47
+ { kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g }
48
+ ];
49
+ function render(placeholder, kind) {
50
+ return placeholder.replace("{kind}", kind);
51
+ }
52
+ function redactString(input, options) {
53
+ if (!options.enabled || !input) return { redacted: input, hits: [] };
54
+ const placeholder = options.placeholder ?? "[REDACTED:{kind}]";
55
+ const patterns = [
56
+ ...options.patterns ?? DEFAULT_PATTERNS,
57
+ ...(options.customRegexes ?? []).flatMap((src, i) => {
58
+ try {
59
+ const flags = src.match(/^\/.*\/([gimsuy]*)$/)?.[1] ?? "";
60
+ const body = src.replace(/^\/(.*)\/[gimsuy]*$/, "$1");
61
+ const regex = new RegExp(body, flags.includes("g") ? flags : flags + "g");
62
+ return [{ kind: `custom-${i}`, regex }];
63
+ } catch {
64
+ return [];
65
+ }
66
+ })
67
+ ];
68
+ let redacted = input;
69
+ const hits = [];
70
+ for (const { kind, regex } of patterns) {
71
+ const rx = new RegExp(regex.source, regex.flags);
72
+ redacted = redacted.replace(rx, (...args) => {
73
+ const match = args[0];
74
+ const probe = new RegExp(rx.source).exec(match);
75
+ const captureCount = probe ? probe.length - 1 : 0;
76
+ const g1 = captureCount >= 1 ? args[1] : void 0;
77
+ const g2 = captureCount >= 2 ? args[2] : void 0;
78
+ const offset = args[1 + captureCount];
79
+ if (captureCount >= 2 && typeof g2 === "string") {
80
+ hits.push({ kind, start: offset + (g1?.length ?? 0), length: g2.length, secret: g2 });
81
+ return `${g1}${render(placeholder, kind)}`;
82
+ }
83
+ hits.push({ kind, start: offset, length: match.length, secret: g1 ?? match });
84
+ return render(placeholder, kind);
85
+ });
86
+ }
87
+ return { redacted, hits };
88
+ }
89
+ function redactJson(value, options) {
90
+ if (!options.enabled) return { value, hits: [] };
91
+ const allHits = [];
92
+ function walk(v) {
93
+ if (typeof v === "string") {
94
+ const r = redactString(v, options);
95
+ allHits.push(...r.hits);
96
+ return r.redacted;
97
+ }
98
+ if (Array.isArray(v)) return v.map(walk);
99
+ if (v && typeof v === "object") {
100
+ const out = {};
101
+ for (const [k, vv] of Object.entries(v)) {
102
+ out[k] = walk(vv);
103
+ }
104
+ return out;
105
+ }
106
+ return v;
107
+ }
108
+ const redacted = walk(value);
109
+ return { value: redacted, hits: allHits };
110
+ }
111
+ function scanString(input, options) {
112
+ const { hits } = redactString(input, { ...options, enabled: true });
113
+ return hits;
114
+ }
115
+
116
+ // src/memory/chat-index.ts
117
+ var MEMORY_DIR_NAME = "memory-index";
118
+ var CHUNKS_FILE = "chunks.json";
119
+ var VECTORS_FILE = "vectors.vec";
120
+ var VEC_MAGIC = 1094929750;
121
+ var VEC_VERSION = 1;
122
+ var VEC_HEADER_BYTES = 16;
123
+ function memoryIndexDir() {
124
+ return path.join(os.homedir(), ".aicli", MEMORY_DIR_NAME);
125
+ }
126
+ function chunksPath() {
127
+ return path.join(memoryIndexDir(), CHUNKS_FILE);
128
+ }
129
+ function vectorsPath() {
130
+ return path.join(memoryIndexDir(), VECTORS_FILE);
131
+ }
132
+ function historyDir() {
133
+ return path.join(os.homedir(), ".aicli", "history");
134
+ }
135
+ var MAX_CHUNK_CHARS = 1200;
136
+ var MIN_CHUNK_CHARS = 40;
137
+ function extractMessageText(msg) {
138
+ if (typeof msg.content === "string") return msg.content;
139
+ if (Array.isArray(msg.content)) {
140
+ return msg.content.filter((p) => p && p.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n");
141
+ }
142
+ return "";
143
+ }
144
+ function chunkSession(session) {
145
+ const chunks = [];
146
+ let pending = null;
147
+ const flush = () => {
148
+ if (!pending) return;
149
+ const rawText = pending.parts.join("\n").trim();
150
+ if (rawText.length < MIN_CHUNK_CHARS) {
151
+ pending = null;
152
+ return;
153
+ }
154
+ const { redacted } = redactString(rawText, { enabled: true });
155
+ const id = crypto.createHash("sha1").update(`${session.id}|${pending.start}|${pending.end}|${redacted.length}`).digest("hex").slice(0, 16);
156
+ chunks.push({
157
+ id,
158
+ sessionId: session.id,
159
+ sessionTitle: session.title,
160
+ provider: session.provider,
161
+ model: session.model,
162
+ startMessageIdx: pending.start,
163
+ endMessageIdx: pending.end,
164
+ text: redacted,
165
+ timestamp: pending.latestTs,
166
+ roles: pending.roles
167
+ });
168
+ pending = null;
169
+ };
170
+ for (let i = 0; i < session.messages.length; i++) {
171
+ const m = session.messages[i];
172
+ if (m.role !== "user" && m.role !== "assistant" && m.role !== "system") continue;
173
+ const text = extractMessageText(m).trim();
174
+ if (!text) continue;
175
+ const ts = m.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
176
+ const prefix = m.role === "user" ? "[USER] " : m.role === "assistant" ? "[AI] " : "[SYS] ";
177
+ const part = `${prefix}${text}`;
178
+ if (!pending) {
179
+ pending = { start: i, end: i, parts: [part], roles: [m.role], latestTs: ts };
180
+ continue;
181
+ }
182
+ const projected = pending.parts.reduce((n, p) => n + p.length + 1, 0) + part.length;
183
+ if (projected > MAX_CHUNK_CHARS) {
184
+ flush();
185
+ pending = { start: i, end: i, parts: [part], roles: [m.role], latestTs: ts };
186
+ } else {
187
+ pending.parts.push(part);
188
+ pending.end = i;
189
+ pending.roles.push(m.role);
190
+ pending.latestTs = ts;
191
+ }
192
+ }
193
+ flush();
194
+ return chunks;
195
+ }
196
+ function writeVectorsFile(chunks, vectors) {
197
+ if (chunks.length * EMBEDDING_DIM !== vectors.length) {
198
+ throw new Error(
199
+ `writeVectorsFile: length mismatch \u2014 ${chunks.length} chunks vs ${vectors.length / EMBEDDING_DIM} vectors`
200
+ );
201
+ }
202
+ const dir = memoryIndexDir();
203
+ fs.mkdirSync(dir, { recursive: true });
204
+ const totalBytes = VEC_HEADER_BYTES + vectors.byteLength;
205
+ const buf = Buffer.alloc(totalBytes);
206
+ buf.writeUInt32LE(VEC_MAGIC, 0);
207
+ buf.writeUInt32LE(VEC_VERSION, 4);
208
+ buf.writeUInt32LE(chunks.length, 8);
209
+ buf.writeUInt32LE(EMBEDDING_DIM, 12);
210
+ Buffer.from(vectors.buffer, vectors.byteOffset, vectors.byteLength).copy(buf, VEC_HEADER_BYTES);
211
+ const target = vectorsPath();
212
+ const tmp = `${target}.tmp`;
213
+ fs.writeFileSync(tmp, buf);
214
+ fs.renameSync(tmp, target);
215
+ }
216
+ function readVectorsFile(expectedCount) {
217
+ const p = vectorsPath();
218
+ if (!fs.existsSync(p)) return null;
219
+ let buf;
220
+ try {
221
+ buf = fs.readFileSync(p);
222
+ } catch {
223
+ return null;
224
+ }
225
+ if (buf.length < VEC_HEADER_BYTES) return null;
226
+ const magic = buf.readUInt32LE(0);
227
+ const version = buf.readUInt32LE(4);
228
+ const count = buf.readUInt32LE(8);
229
+ const dim = buf.readUInt32LE(12);
230
+ if (magic !== VEC_MAGIC || version !== VEC_VERSION || dim !== EMBEDDING_DIM) return null;
231
+ if (count !== expectedCount) return null;
232
+ const expected = VEC_HEADER_BYTES + count * dim * 4;
233
+ if (buf.length !== expected) return null;
234
+ return new Float32Array(
235
+ buf.buffer.slice(buf.byteOffset + VEC_HEADER_BYTES, buf.byteOffset + expected)
236
+ );
237
+ }
238
+ function writeIndexFile(idx) {
239
+ const dir = memoryIndexDir();
240
+ fs.mkdirSync(dir, { recursive: true });
241
+ const target = chunksPath();
242
+ const tmp = `${target}.tmp`;
243
+ fs.writeFileSync(tmp, JSON.stringify(idx, null, 2), "utf-8");
244
+ fs.renameSync(tmp, target);
245
+ }
246
+ function readIndexFile() {
247
+ const p = chunksPath();
248
+ if (!fs.existsSync(p)) return null;
249
+ try {
250
+ const raw = fs.readFileSync(p, "utf-8");
251
+ const data = JSON.parse(raw);
252
+ if (data.version !== 1) return null;
253
+ return data;
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
258
+ function loadChatIndex() {
259
+ const idx = readIndexFile();
260
+ if (!idx) return null;
261
+ const vectors = readVectorsFile(idx.chunks.length);
262
+ if (!vectors) return null;
263
+ return { idx, vectors };
264
+ }
265
+ function clearChatIndex() {
266
+ try {
267
+ if (fs.existsSync(chunksPath())) fs.unlinkSync(chunksPath());
268
+ } catch {
269
+ }
270
+ try {
271
+ if (fs.existsSync(vectorsPath())) fs.unlinkSync(vectorsPath());
272
+ } catch {
273
+ }
274
+ }
275
+ function listSessionFiles() {
276
+ const dir = historyDir();
277
+ if (!fs.existsSync(dir)) return [];
278
+ const out = [];
279
+ for (const name of fs.readdirSync(dir)) {
280
+ if (!name.endsWith(".json")) continue;
281
+ const id = name.replace(/\.json$/, "");
282
+ const p = path.join(dir, name);
283
+ try {
284
+ const st = fs.statSync(p);
285
+ out.push({ id, path: p, mtime: st.mtimeMs });
286
+ } catch {
287
+ }
288
+ }
289
+ return out;
290
+ }
291
+ function readSession(p) {
292
+ try {
293
+ const data = JSON.parse(fs.readFileSync(p, "utf-8"));
294
+ if (!data.id || !Array.isArray(data.messages)) return null;
295
+ return data;
296
+ } catch {
297
+ return null;
298
+ }
299
+ }
300
+ async function buildChatIndex(options = {}) {
301
+ const t0 = Date.now();
302
+ const onProgress = options.onProgress ?? (() => {
303
+ });
304
+ onProgress({ stage: "scanning" });
305
+ const files = listSessionFiles();
306
+ const existing = options.full ? null : loadChatIndex();
307
+ const prevMtimes = existing?.idx.sessionMtimes ?? {};
308
+ const prevChunksBySession = /* @__PURE__ */ new Map();
309
+ const prevVectorsByChunkId = /* @__PURE__ */ new Map();
310
+ if (existing) {
311
+ for (let i = 0; i < existing.idx.chunks.length; i++) {
312
+ const c = existing.idx.chunks[i];
313
+ const arr = prevChunksBySession.get(c.sessionId) ?? [];
314
+ arr.push(c);
315
+ prevChunksBySession.set(c.sessionId, arr);
316
+ prevVectorsByChunkId.set(
317
+ c.id,
318
+ existing.vectors.slice(i * EMBEDDING_DIM, (i + 1) * EMBEDDING_DIM)
319
+ );
320
+ }
321
+ }
322
+ onProgress({ stage: "chunking" });
323
+ const stats = {
324
+ sessionsScanned: files.length,
325
+ sessionsIndexed: 0,
326
+ sessionsSkipped: 0,
327
+ chunksTotal: 0,
328
+ chunksAdded: 0,
329
+ chunksRemoved: 0,
330
+ durationMs: 0
331
+ };
332
+ const newMtimes = {};
333
+ const finalChunks = [];
334
+ const finalVectors = [];
335
+ const toEmbed = [];
336
+ for (const f of files) {
337
+ newMtimes[f.id] = f.mtime;
338
+ const prevMtime = prevMtimes[f.id];
339
+ if (prevMtime === f.mtime && prevChunksBySession.has(f.id)) {
340
+ stats.sessionsSkipped++;
341
+ const cached = prevChunksBySession.get(f.id);
342
+ for (const c of cached) {
343
+ const v = prevVectorsByChunkId.get(c.id);
344
+ if (!v) continue;
345
+ finalChunks.push(c);
346
+ finalVectors.push(v);
347
+ }
348
+ continue;
349
+ }
350
+ const sess = readSession(f.path);
351
+ if (!sess) continue;
352
+ stats.sessionsIndexed++;
353
+ const chunks = chunkSession(sess);
354
+ for (const c of chunks) {
355
+ finalChunks.push(c);
356
+ toEmbed.push(c);
357
+ stats.chunksAdded++;
358
+ }
359
+ }
360
+ if (existing) {
361
+ for (const prevId of Object.keys(prevMtimes)) {
362
+ if (!(prevId in newMtimes)) {
363
+ const removed = prevChunksBySession.get(prevId) ?? [];
364
+ stats.chunksRemoved += removed.length;
365
+ }
366
+ }
367
+ }
368
+ stats.chunksTotal = finalChunks.length;
369
+ const BATCH = 16;
370
+ onProgress({ stage: "embedding", processed: 0, total: toEmbed.length });
371
+ const newVectorsByChunkId = /* @__PURE__ */ new Map();
372
+ for (let i = 0; i < toEmbed.length; i += BATCH) {
373
+ const batch = toEmbed.slice(i, i + BATCH);
374
+ const vecs = await embed(batch.map((c) => c.text));
375
+ for (let j = 0; j < batch.length; j++) {
376
+ newVectorsByChunkId.set(batch[j].id, vecs[j]);
377
+ }
378
+ onProgress({ stage: "embedding", processed: Math.min(i + BATCH, toEmbed.length), total: toEmbed.length });
379
+ }
380
+ const flat = new Float32Array(finalChunks.length * EMBEDDING_DIM);
381
+ for (let i = 0; i < finalChunks.length; i++) {
382
+ const c = finalChunks[i];
383
+ const v = newVectorsByChunkId.get(c.id) ?? prevVectorsByChunkId.get(c.id);
384
+ if (!v || v.length !== EMBEDDING_DIM) {
385
+ continue;
386
+ }
387
+ flat.set(v, i * EMBEDDING_DIM);
388
+ }
389
+ onProgress({ stage: "saving" });
390
+ const idx = {
391
+ version: 1,
392
+ built: (/* @__PURE__ */ new Date()).toISOString(),
393
+ model: "Xenova/paraphrase-multilingual-MiniLM-L12-v2",
394
+ sessionMtimes: newMtimes,
395
+ chunks: finalChunks
396
+ };
397
+ writeIndexFile(idx);
398
+ writeVectorsFile(finalChunks, flat);
399
+ stats.durationMs = Date.now() - t0;
400
+ onProgress({ stage: "done" });
401
+ return stats;
402
+ }
403
+ async function searchChatMemory(query, options = {}) {
404
+ const topK = options.topK ?? 5;
405
+ const minScore = options.minScore ?? 0.25;
406
+ const loaded = loadChatIndex();
407
+ if (!loaded || loaded.idx.chunks.length === 0) return [];
408
+ const { idx, vectors } = loaded;
409
+ const { redacted } = redactString(query, { enabled: true });
410
+ const qvec = await embedOne(redacted);
411
+ const candidates = [];
412
+ for (let i = 0; i < idx.chunks.length; i++) {
413
+ const c = idx.chunks[i];
414
+ if (options.sessionId && c.sessionId !== options.sessionId) continue;
415
+ if (options.excludeSessionId && c.sessionId === options.excludeSessionId) continue;
416
+ let score = 0;
417
+ const base = i * EMBEDDING_DIM;
418
+ for (let d = 0; d < EMBEDDING_DIM; d++) {
419
+ score += vectors[base + d] * qvec[d];
420
+ }
421
+ if (score < minScore) continue;
422
+ candidates.push({ chunk: c, score });
423
+ }
424
+ candidates.sort((a, b) => b.score - a.score);
425
+ return candidates.slice(0, topK);
426
+ }
427
+ function getChatIndexStatus() {
428
+ const status = {
429
+ exists: false,
430
+ chunks: 0,
431
+ sessions: 0,
432
+ vecFileSizeBytes: 0,
433
+ chunksFileSizeBytes: 0
434
+ };
435
+ try {
436
+ if (fs.existsSync(vectorsPath())) status.vecFileSizeBytes = fs.statSync(vectorsPath()).size;
437
+ if (fs.existsSync(chunksPath())) status.chunksFileSizeBytes = fs.statSync(chunksPath()).size;
438
+ } catch {
439
+ }
440
+ const idx = readIndexFile();
441
+ if (!idx) return status;
442
+ status.exists = true;
443
+ status.chunks = idx.chunks.length;
444
+ status.sessions = Object.keys(idx.sessionMtimes).length;
445
+ status.built = idx.built;
446
+ status.model = idx.model;
447
+ return status;
448
+ }
449
+
450
+ export {
451
+ DEFAULT_PATTERNS,
452
+ redactJson,
453
+ scanString,
454
+ chunkSession,
455
+ loadChatIndex,
456
+ clearChatIndex,
457
+ buildChatIndex,
458
+ searchChatMemory,
459
+ getChatIndexStatus
460
+ };
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.88";
9
+ var VERSION = "0.4.90";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -2,13 +2,16 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-YDHIU24C.js";
5
+ } from "./chunk-G65IDWVP.js";
6
6
  import {
7
7
  AuthError,
8
8
  ProviderError,
9
9
  ProviderNotFoundError,
10
10
  RateLimitError
11
11
  } from "./chunk-2ZD3YTVM.js";
12
+ import {
13
+ redactJson
14
+ } from "./chunk-ANYYM4CF.js";
12
15
  import {
13
16
  APP_NAME,
14
17
  CONFIG_DIR_NAME,
@@ -18,7 +21,7 @@ import {
18
21
  MCP_PROTOCOL_VERSION,
19
22
  MCP_TOOL_PREFIX,
20
23
  VERSION
21
- } from "./chunk-VGXNE37B.js";
24
+ } from "./chunk-WPQ4D6T3.js";
22
25
 
23
26
  // src/providers/claude.ts
24
27
  import Anthropic from "@anthropic-ai/sdk";
@@ -2655,108 +2658,6 @@ var Session = class _Session {
2655
2658
  }
2656
2659
  };
2657
2660
 
2658
- // src/security/redactor.ts
2659
- var DEFAULT_PATTERNS = [
2660
- // password: xxx / password = xxx / password="xxx"
2661
- // Covers YAML / JSON / shell-ish / env-file forms.
2662
- { kind: "password", regex: /\b(password|passwd|pwd)\s*[:=]\s*["']?([^\s"',;{}]{4,200})["']?/gi },
2663
- // PGPASSWORD=xxx (explicit bash env-var form, separate rule because no quotes usually)
2664
- { kind: "pgpassword-env", regex: /\b(PGPASSWORD)=([^\s"']{4,200})/g },
2665
- // JDBC/PG/MySQL/Mongo connection strings with inline credentials
2666
- // postgresql://user:pass@host/db → redact pass
2667
- { kind: "db-uri-password", regex: /(\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:\s]+:)([^@\s]+)(@)/gi },
2668
- // Anthropic API keys
2669
- { kind: "anthropic-key", regex: /(sk-ant-[a-zA-Z0-9_-]{90,})/g },
2670
- // OpenAI / generic sk- keys — requires length ≥32 to avoid eating short identifiers
2671
- { kind: "openai-key", regex: /(sk-(?:proj-)?[a-zA-Z0-9_-]{32,})/g },
2672
- // GitHub personal access tokens
2673
- { kind: "github-pat", regex: /\b(ghp_[a-zA-Z0-9]{36})\b/g },
2674
- { kind: "github-oauth", regex: /\b(gho_[a-zA-Z0-9]{36})\b/g },
2675
- { kind: "github-install", regex: /\b(ghs_[a-zA-Z0-9]{36})\b/g },
2676
- // Slack tokens
2677
- { kind: "slack-bot", regex: /\b(xoxb-\d+-\d+-[a-zA-Z0-9]+)\b/g },
2678
- { kind: "slack-user", regex: /\b(xoxp-\d+-\d+-\d+-[a-zA-Z0-9]+)\b/g },
2679
- // AWS access key IDs (AKIA...) and secret access keys are context-dependent;
2680
- // we only catch the ID because secret key alone is indistinguishable from random base64.
2681
- { kind: "aws-access-key-id", regex: /\b(AKIA[0-9A-Z]{16})\b/g },
2682
- // Google API keys
2683
- { kind: "google-api-key", regex: /\b(AIza[0-9A-Za-z_-]{35})\b/g },
2684
- // Generic "api_key": "..." / "apiKey": "..." / api-key=xxx
2685
- { kind: "api-key", regex: /\b(api[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{16,200})["']?/gi },
2686
- // Generic token: xxx (only when value looks token-shaped; avoids eating human prose)
2687
- { kind: "token", regex: /\b(token|access[_-]?token|bearer[_-]?token)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{20,300})["']?/gi },
2688
- // Bearer <token> in Authorization headers
2689
- { kind: "bearer", regex: /\b(Authorization:\s*Bearer\s+)([a-zA-Z0-9_\-.=]{20,500})/g },
2690
- // Private key PEM blocks — catch the header+footer together
2691
- { kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g }
2692
- ];
2693
- function render(placeholder, kind) {
2694
- return placeholder.replace("{kind}", kind);
2695
- }
2696
- function redactString(input, options) {
2697
- if (!options.enabled || !input) return { redacted: input, hits: [] };
2698
- const placeholder = options.placeholder ?? "[REDACTED:{kind}]";
2699
- const patterns = [
2700
- ...options.patterns ?? DEFAULT_PATTERNS,
2701
- ...(options.customRegexes ?? []).flatMap((src, i) => {
2702
- try {
2703
- const flags = src.match(/^\/.*\/([gimsuy]*)$/)?.[1] ?? "";
2704
- const body = src.replace(/^\/(.*)\/[gimsuy]*$/, "$1");
2705
- const regex = new RegExp(body, flags.includes("g") ? flags : flags + "g");
2706
- return [{ kind: `custom-${i}`, regex }];
2707
- } catch {
2708
- return [];
2709
- }
2710
- })
2711
- ];
2712
- let redacted = input;
2713
- const hits = [];
2714
- for (const { kind, regex } of patterns) {
2715
- const rx = new RegExp(regex.source, regex.flags);
2716
- redacted = redacted.replace(rx, (...args) => {
2717
- const match = args[0];
2718
- const probe = new RegExp(rx.source).exec(match);
2719
- const captureCount = probe ? probe.length - 1 : 0;
2720
- const g1 = captureCount >= 1 ? args[1] : void 0;
2721
- const g2 = captureCount >= 2 ? args[2] : void 0;
2722
- const offset = args[1 + captureCount];
2723
- if (captureCount >= 2 && typeof g2 === "string") {
2724
- hits.push({ kind, start: offset + (g1?.length ?? 0), length: g2.length, secret: g2 });
2725
- return `${g1}${render(placeholder, kind)}`;
2726
- }
2727
- hits.push({ kind, start: offset, length: match.length, secret: g1 ?? match });
2728
- return render(placeholder, kind);
2729
- });
2730
- }
2731
- return { redacted, hits };
2732
- }
2733
- function redactJson(value, options) {
2734
- if (!options.enabled) return { value, hits: [] };
2735
- const allHits = [];
2736
- function walk(v) {
2737
- if (typeof v === "string") {
2738
- const r = redactString(v, options);
2739
- allHits.push(...r.hits);
2740
- return r.redacted;
2741
- }
2742
- if (Array.isArray(v)) return v.map(walk);
2743
- if (v && typeof v === "object") {
2744
- const out = {};
2745
- for (const [k, vv] of Object.entries(v)) {
2746
- out[k] = walk(vv);
2747
- }
2748
- return out;
2749
- }
2750
- return v;
2751
- }
2752
- const redacted = walk(value);
2753
- return { value: redacted, hits: allHits };
2754
- }
2755
- function scanString(input, options) {
2756
- const { hits } = redactString(input, { ...options, enabled: true });
2757
- return hits;
2758
- }
2759
-
2760
2661
  // src/session/session-manager.ts
2761
2662
  function safeDate(value) {
2762
2663
  const d = new Date(value);
@@ -4099,8 +4000,6 @@ export {
4099
4000
  buildPhantomCorrectionMessage,
4100
4001
  ProviderRegistry,
4101
4002
  getContentText,
4102
- DEFAULT_PATTERNS,
4103
- scanString,
4104
4003
  SessionManager,
4105
4004
  getGitRoot,
4106
4005
  getGitContext,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-VGXNE37B.js";
4
+ } from "./chunk-WPQ4D6T3.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync } from "child_process";