@deeplake/hivemind 0.6.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2097 @@
1
+ #!/usr/bin/env node
2
+
3
+ // dist/src/hooks/codex/pre-tool-use.js
4
+ import { execFileSync } from "node:child_process";
5
+ import { existsSync as existsSync3 } from "node:fs";
6
+ import { join as join6, dirname } from "node:path";
7
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
8
+
9
+ // dist/src/utils/stdin.js
10
+ function readStdin() {
11
+ return new Promise((resolve2, reject) => {
12
+ let data = "";
13
+ process.stdin.setEncoding("utf-8");
14
+ process.stdin.on("data", (chunk) => data += chunk);
15
+ process.stdin.on("end", () => {
16
+ try {
17
+ resolve2(JSON.parse(data));
18
+ } catch (err) {
19
+ reject(new Error(`Failed to parse hook input: ${err}`));
20
+ }
21
+ });
22
+ process.stdin.on("error", reject);
23
+ });
24
+ }
25
+
26
+ // dist/src/config.js
27
+ import { readFileSync, existsSync } from "node:fs";
28
+ import { join } from "node:path";
29
+ import { homedir, userInfo } from "node:os";
30
+ function loadConfig() {
31
+ const home = homedir();
32
+ const credPath = join(home, ".deeplake", "credentials.json");
33
+ let creds = null;
34
+ if (existsSync(credPath)) {
35
+ try {
36
+ creds = JSON.parse(readFileSync(credPath, "utf-8"));
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ const token = process.env.HIVEMIND_TOKEN ?? creds?.token;
42
+ const orgId = process.env.HIVEMIND_ORG_ID ?? creds?.orgId;
43
+ if (!token || !orgId)
44
+ return null;
45
+ return {
46
+ token,
47
+ orgId,
48
+ orgName: creds?.orgName ?? orgId,
49
+ userName: creds?.userName || userInfo().username || "unknown",
50
+ workspaceId: process.env.HIVEMIND_WORKSPACE_ID ?? creds?.workspaceId ?? "default",
51
+ apiUrl: process.env.HIVEMIND_API_URL ?? creds?.apiUrl ?? "https://api.deeplake.ai",
52
+ tableName: process.env.HIVEMIND_TABLE ?? "memory",
53
+ sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
54
+ memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
55
+ };
56
+ }
57
+
58
+ // dist/src/deeplake-api.js
59
+ import { randomUUID } from "node:crypto";
60
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
61
+ import { join as join3 } from "node:path";
62
+ import { tmpdir } from "node:os";
63
+
64
+ // dist/src/utils/debug.js
65
+ import { appendFileSync } from "node:fs";
66
+ import { join as join2 } from "node:path";
67
+ import { homedir as homedir2 } from "node:os";
68
+ var DEBUG = process.env.HIVEMIND_DEBUG === "1";
69
+ var LOG = join2(homedir2(), ".deeplake", "hook-debug.log");
70
+ function log(tag, msg) {
71
+ if (!DEBUG)
72
+ return;
73
+ appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
74
+ `);
75
+ }
76
+
77
+ // dist/src/utils/sql.js
78
+ function sqlStr(value) {
79
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/\0/g, "").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
80
+ }
81
+ function sqlLike(value) {
82
+ return sqlStr(value).replace(/%/g, "\\%").replace(/_/g, "\\_");
83
+ }
84
+
85
+ // dist/src/deeplake-api.js
86
+ var log2 = (msg) => log("sdk", msg);
87
+ function summarizeSql(sql, maxLen = 220) {
88
+ const compact = sql.replace(/\s+/g, " ").trim();
89
+ return compact.length > maxLen ? `${compact.slice(0, maxLen)}...` : compact;
90
+ }
91
+ function traceSql(msg) {
92
+ const traceEnabled = process.env.HIVEMIND_TRACE_SQL === "1" || process.env.HIVEMIND_DEBUG === "1";
93
+ if (!traceEnabled)
94
+ return;
95
+ process.stderr.write(`[deeplake-sql] ${msg}
96
+ `);
97
+ if (process.env.HIVEMIND_DEBUG === "1")
98
+ log2(msg);
99
+ }
100
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
101
+ var MAX_RETRIES = 3;
102
+ var BASE_DELAY_MS = 500;
103
+ var MAX_CONCURRENCY = 5;
104
+ var QUERY_TIMEOUT_MS = Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
105
+ var INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
106
+ function sleep(ms) {
107
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
108
+ }
109
+ function isTimeoutError(error) {
110
+ const name = error instanceof Error ? error.name.toLowerCase() : "";
111
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
112
+ return name.includes("timeout") || name === "aborterror" || message.includes("timeout") || message.includes("timed out");
113
+ }
114
+ function isDuplicateIndexError(error) {
115
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
116
+ return message.includes("duplicate key value violates unique constraint") || message.includes("pg_class_relname_nsp_index") || message.includes("already exists");
117
+ }
118
+ function isSessionInsertQuery(sql) {
119
+ return /^\s*insert\s+into\s+"[^"]+"\s*\(\s*id\s*,\s*path\s*,\s*filename\s*,\s*message\s*,/i.test(sql);
120
+ }
121
+ function isTransientHtml403(text) {
122
+ const body = text.toLowerCase();
123
+ return body.includes("<html") || body.includes("403 forbidden") || body.includes("cloudflare") || body.includes("nginx");
124
+ }
125
+ function getIndexMarkerDir() {
126
+ return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join3(tmpdir(), "hivemind-deeplake-indexes");
127
+ }
128
+ var Semaphore = class {
129
+ max;
130
+ waiting = [];
131
+ active = 0;
132
+ constructor(max) {
133
+ this.max = max;
134
+ }
135
+ async acquire() {
136
+ if (this.active < this.max) {
137
+ this.active++;
138
+ return;
139
+ }
140
+ await new Promise((resolve2) => this.waiting.push(resolve2));
141
+ }
142
+ release() {
143
+ this.active--;
144
+ const next = this.waiting.shift();
145
+ if (next) {
146
+ this.active++;
147
+ next();
148
+ }
149
+ }
150
+ };
151
+ var DeeplakeApi = class {
152
+ token;
153
+ apiUrl;
154
+ orgId;
155
+ workspaceId;
156
+ tableName;
157
+ _pendingRows = [];
158
+ _sem = new Semaphore(MAX_CONCURRENCY);
159
+ _tablesCache = null;
160
+ constructor(token, apiUrl, orgId, workspaceId, tableName) {
161
+ this.token = token;
162
+ this.apiUrl = apiUrl;
163
+ this.orgId = orgId;
164
+ this.workspaceId = workspaceId;
165
+ this.tableName = tableName;
166
+ }
167
+ /** Execute SQL with retry on transient errors and bounded concurrency. */
168
+ async query(sql) {
169
+ const startedAt = Date.now();
170
+ const summary = summarizeSql(sql);
171
+ traceSql(`query start: ${summary}`);
172
+ await this._sem.acquire();
173
+ try {
174
+ const rows = await this._queryWithRetry(sql);
175
+ traceSql(`query ok (${Date.now() - startedAt}ms, rows=${rows.length}): ${summary}`);
176
+ return rows;
177
+ } catch (e) {
178
+ const message = e instanceof Error ? e.message : String(e);
179
+ traceSql(`query fail (${Date.now() - startedAt}ms): ${summary} :: ${message}`);
180
+ throw e;
181
+ } finally {
182
+ this._sem.release();
183
+ }
184
+ }
185
+ async _queryWithRetry(sql) {
186
+ let lastError;
187
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
188
+ let resp;
189
+ try {
190
+ const signal = AbortSignal.timeout(QUERY_TIMEOUT_MS);
191
+ resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables/query`, {
192
+ method: "POST",
193
+ headers: {
194
+ Authorization: `Bearer ${this.token}`,
195
+ "Content-Type": "application/json",
196
+ "X-Activeloop-Org-Id": this.orgId
197
+ },
198
+ signal,
199
+ body: JSON.stringify({ query: sql })
200
+ });
201
+ } catch (e) {
202
+ if (isTimeoutError(e)) {
203
+ lastError = new Error(`Query timeout after ${QUERY_TIMEOUT_MS}ms`);
204
+ throw lastError;
205
+ }
206
+ lastError = e instanceof Error ? e : new Error(String(e));
207
+ if (attempt < MAX_RETRIES) {
208
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
209
+ log2(`query retry ${attempt + 1}/${MAX_RETRIES} (fetch error: ${lastError.message}) in ${delay.toFixed(0)}ms`);
210
+ await sleep(delay);
211
+ continue;
212
+ }
213
+ throw lastError;
214
+ }
215
+ if (resp.ok) {
216
+ const raw = await resp.json();
217
+ if (!raw?.rows || !raw?.columns)
218
+ return [];
219
+ return raw.rows.map((row) => Object.fromEntries(raw.columns.map((col, i) => [col, row[i]])));
220
+ }
221
+ const text = await resp.text().catch(() => "");
222
+ const retryable403 = isSessionInsertQuery(sql) && (resp.status === 401 || resp.status === 403 && (text.length === 0 || isTransientHtml403(text)));
223
+ if (attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
224
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
225
+ log2(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
226
+ await sleep(delay);
227
+ continue;
228
+ }
229
+ throw new Error(`Query failed: ${resp.status}: ${text.slice(0, 200)}`);
230
+ }
231
+ throw lastError ?? new Error("Query failed: max retries exceeded");
232
+ }
233
+ // ── Writes ──────────────────────────────────────────────────────────────────
234
+ /** Queue rows for writing. Call commit() to flush. */
235
+ appendRows(rows) {
236
+ this._pendingRows.push(...rows);
237
+ }
238
+ /** Flush pending rows via SQL. */
239
+ async commit() {
240
+ if (this._pendingRows.length === 0)
241
+ return;
242
+ const rows = this._pendingRows;
243
+ this._pendingRows = [];
244
+ const CONCURRENCY = 10;
245
+ for (let i = 0; i < rows.length; i += CONCURRENCY) {
246
+ const chunk = rows.slice(i, i + CONCURRENCY);
247
+ await Promise.allSettled(chunk.map((r) => this.upsertRowSql(r)));
248
+ }
249
+ log2(`commit: ${rows.length} rows`);
250
+ }
251
+ async upsertRowSql(row) {
252
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
253
+ const cd = row.creationDate ?? ts;
254
+ const lud = row.lastUpdateDate ?? ts;
255
+ const exists = await this.query(`SELECT path FROM "${this.tableName}" WHERE path = '${sqlStr(row.path)}' LIMIT 1`);
256
+ if (exists.length > 0) {
257
+ let setClauses = `summary = E'${sqlStr(row.contentText)}', mime_type = '${sqlStr(row.mimeType)}', size_bytes = ${row.sizeBytes}, last_update_date = '${lud}'`;
258
+ if (row.project !== void 0)
259
+ setClauses += `, project = '${sqlStr(row.project)}'`;
260
+ if (row.description !== void 0)
261
+ setClauses += `, description = '${sqlStr(row.description)}'`;
262
+ await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(row.path)}'`);
263
+ } else {
264
+ const id = randomUUID();
265
+ let cols = "id, path, filename, summary, mime_type, size_bytes, creation_date, last_update_date";
266
+ let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
267
+ if (row.project !== void 0) {
268
+ cols += ", project";
269
+ vals += `, '${sqlStr(row.project)}'`;
270
+ }
271
+ if (row.description !== void 0) {
272
+ cols += ", description";
273
+ vals += `, '${sqlStr(row.description)}'`;
274
+ }
275
+ await this.query(`INSERT INTO "${this.tableName}" (${cols}) VALUES (${vals})`);
276
+ }
277
+ }
278
+ /** Update specific columns on a row by path. */
279
+ async updateColumns(path, columns) {
280
+ const setClauses = Object.entries(columns).map(([col, val]) => typeof val === "number" ? `${col} = ${val}` : `${col} = '${sqlStr(String(val))}'`).join(", ");
281
+ await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(path)}'`);
282
+ }
283
+ // ── Convenience ─────────────────────────────────────────────────────────────
284
+ /** Create a BM25 search index on a column. */
285
+ async createIndex(column) {
286
+ await this.query(`CREATE INDEX IF NOT EXISTS idx_${sqlStr(column)}_bm25 ON "${this.tableName}" USING deeplake_index ("${column}")`);
287
+ }
288
+ buildLookupIndexName(table, suffix) {
289
+ return `idx_${table}_${suffix}`.replace(/[^a-zA-Z0-9_]/g, "_");
290
+ }
291
+ getLookupIndexMarkerPath(table, suffix) {
292
+ const markerKey = [
293
+ this.workspaceId,
294
+ this.orgId,
295
+ table,
296
+ suffix
297
+ ].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
298
+ return join3(getIndexMarkerDir(), `${markerKey}.json`);
299
+ }
300
+ hasFreshLookupIndexMarker(table, suffix) {
301
+ const markerPath = this.getLookupIndexMarkerPath(table, suffix);
302
+ if (!existsSync2(markerPath))
303
+ return false;
304
+ try {
305
+ const raw = JSON.parse(readFileSync2(markerPath, "utf-8"));
306
+ const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
307
+ if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
308
+ return false;
309
+ return true;
310
+ } catch {
311
+ return false;
312
+ }
313
+ }
314
+ markLookupIndexReady(table, suffix) {
315
+ mkdirSync(getIndexMarkerDir(), { recursive: true });
316
+ writeFileSync(this.getLookupIndexMarkerPath(table, suffix), JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
317
+ }
318
+ async ensureLookupIndex(table, suffix, columnsSql) {
319
+ if (this.hasFreshLookupIndexMarker(table, suffix))
320
+ return;
321
+ const indexName = this.buildLookupIndexName(table, suffix);
322
+ try {
323
+ await this.query(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${table}" ${columnsSql}`);
324
+ this.markLookupIndexReady(table, suffix);
325
+ } catch (e) {
326
+ if (isDuplicateIndexError(e)) {
327
+ this.markLookupIndexReady(table, suffix);
328
+ return;
329
+ }
330
+ log2(`index "${indexName}" skipped: ${e.message}`);
331
+ }
332
+ }
333
+ /** List all tables in the workspace (with retry). */
334
+ async listTables(forceRefresh = false) {
335
+ if (!forceRefresh && this._tablesCache)
336
+ return [...this._tablesCache];
337
+ const { tables, cacheable } = await this._fetchTables();
338
+ if (cacheable)
339
+ this._tablesCache = [...tables];
340
+ return tables;
341
+ }
342
+ async _fetchTables() {
343
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
344
+ try {
345
+ const resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables`, {
346
+ headers: {
347
+ Authorization: `Bearer ${this.token}`,
348
+ "X-Activeloop-Org-Id": this.orgId
349
+ }
350
+ });
351
+ if (resp.ok) {
352
+ const data = await resp.json();
353
+ return {
354
+ tables: (data.tables ?? []).map((t) => t.table_name),
355
+ cacheable: true
356
+ };
357
+ }
358
+ if (attempt < MAX_RETRIES && RETRYABLE_CODES.has(resp.status)) {
359
+ await sleep(BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200);
360
+ continue;
361
+ }
362
+ return { tables: [], cacheable: false };
363
+ } catch {
364
+ if (attempt < MAX_RETRIES) {
365
+ await sleep(BASE_DELAY_MS * Math.pow(2, attempt));
366
+ continue;
367
+ }
368
+ return { tables: [], cacheable: false };
369
+ }
370
+ }
371
+ return { tables: [], cacheable: false };
372
+ }
373
+ /** Create the memory table if it doesn't already exist. Migrate columns on existing tables. */
374
+ async ensureTable(name) {
375
+ const tbl = name ?? this.tableName;
376
+ const tables = await this.listTables();
377
+ if (!tables.includes(tbl)) {
378
+ log2(`table "${tbl}" not found, creating`);
379
+ await this.query(`CREATE TABLE IF NOT EXISTS "${tbl}" (id TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', filename TEXT NOT NULL DEFAULT '', summary TEXT NOT NULL DEFAULT '', author TEXT NOT NULL DEFAULT '', mime_type TEXT NOT NULL DEFAULT 'text/plain', size_bytes BIGINT NOT NULL DEFAULT 0, project TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', agent TEXT NOT NULL DEFAULT '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`);
380
+ log2(`table "${tbl}" created`);
381
+ if (!tables.includes(tbl))
382
+ this._tablesCache = [...tables, tbl];
383
+ }
384
+ }
385
+ /** Create the sessions table (uses JSONB for message since every row is a JSON event). */
386
+ async ensureSessionsTable(name) {
387
+ const tables = await this.listTables();
388
+ if (!tables.includes(name)) {
389
+ log2(`table "${name}" not found, creating`);
390
+ await this.query(`CREATE TABLE IF NOT EXISTS "${name}" (id TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', filename TEXT NOT NULL DEFAULT '', message JSONB, author TEXT NOT NULL DEFAULT '', mime_type TEXT NOT NULL DEFAULT 'application/json', size_bytes BIGINT NOT NULL DEFAULT 0, project TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', agent TEXT NOT NULL DEFAULT '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`);
391
+ log2(`table "${name}" created`);
392
+ if (!tables.includes(name))
393
+ this._tablesCache = [...tables, name];
394
+ }
395
+ await this.ensureLookupIndex(name, "path_creation_date", `("path", "creation_date")`);
396
+ }
397
+ };
398
+
399
+ // dist/src/shell/grep-core.js
400
+ var TOOL_INPUT_FIELDS = [
401
+ "command",
402
+ "file_path",
403
+ "path",
404
+ "pattern",
405
+ "prompt",
406
+ "subagent_type",
407
+ "query",
408
+ "url",
409
+ "notebook_path",
410
+ "old_string",
411
+ "new_string",
412
+ "content",
413
+ "skill",
414
+ "args",
415
+ "taskId",
416
+ "status",
417
+ "subject",
418
+ "description",
419
+ "to",
420
+ "message",
421
+ "summary",
422
+ "max_results"
423
+ ];
424
+ var TOOL_RESPONSE_DROP = /* @__PURE__ */ new Set([
425
+ // Note: `stderr` is intentionally NOT in this set. The `stdout` high-signal
426
+ // branch below already de-dupes it for the common case (appends as suffix
427
+ // when non-empty). If a tool response has ONLY `stderr` and no `stdout`
428
+ // (hard-failure on some tools), the generic cleanup preserves it so the
429
+ // error message reaches Claude instead of collapsing to `[ok]`.
430
+ "interrupted",
431
+ "isImage",
432
+ "noOutputExpected",
433
+ "type",
434
+ "structuredPatch",
435
+ "userModified",
436
+ "originalFile",
437
+ "replaceAll",
438
+ "totalDurationMs",
439
+ "totalTokens",
440
+ "totalToolUseCount",
441
+ "usage",
442
+ "toolStats",
443
+ "durationMs",
444
+ "durationSeconds",
445
+ "bytes",
446
+ "code",
447
+ "codeText",
448
+ "agentId",
449
+ "agentType",
450
+ "verificationNudgeNeeded",
451
+ "numLines",
452
+ "numFiles",
453
+ "truncated",
454
+ "statusChange",
455
+ "updatedFields",
456
+ "isAgent",
457
+ "success"
458
+ ]);
459
+ function maybeParseJson(v) {
460
+ if (typeof v !== "string")
461
+ return v;
462
+ const s = v.trim();
463
+ if (s[0] !== "{" && s[0] !== "[")
464
+ return v;
465
+ try {
466
+ return JSON.parse(s);
467
+ } catch {
468
+ return v;
469
+ }
470
+ }
471
+ function snakeCase(k) {
472
+ return k.replace(/([A-Z])/g, "_$1").toLowerCase();
473
+ }
474
+ function camelCase(k) {
475
+ return k.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
476
+ }
477
+ function formatToolInput(raw) {
478
+ const p = maybeParseJson(raw);
479
+ if (typeof p !== "object" || p === null)
480
+ return String(p ?? "");
481
+ const parts = [];
482
+ for (const k of TOOL_INPUT_FIELDS) {
483
+ if (p[k] === void 0)
484
+ continue;
485
+ const v = p[k];
486
+ parts.push(`${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
487
+ }
488
+ for (const k of ["glob", "output_mode", "limit", "offset"]) {
489
+ if (p[k] !== void 0)
490
+ parts.push(`${k}: ${p[k]}`);
491
+ }
492
+ return parts.length ? parts.join("\n") : JSON.stringify(p);
493
+ }
494
+ function formatToolResponse(raw, inp, toolName) {
495
+ const r = maybeParseJson(raw);
496
+ if (typeof r !== "object" || r === null)
497
+ return String(r ?? "");
498
+ if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
499
+ return r.filePath ? `[wrote ${r.filePath}]` : "[ok]";
500
+ }
501
+ if (typeof r.stdout === "string") {
502
+ const stderr = r.stderr;
503
+ return r.stdout + (stderr ? `
504
+ stderr: ${stderr}` : "");
505
+ }
506
+ if (typeof r.content === "string")
507
+ return r.content;
508
+ if (r.file && typeof r.file === "object") {
509
+ const f = r.file;
510
+ if (typeof f.content === "string")
511
+ return `[${f.filePath ?? ""}]
512
+ ${f.content}`;
513
+ if (typeof f.base64 === "string")
514
+ return `[binary ${f.filePath ?? ""}: ${f.base64.length} base64 chars]`;
515
+ }
516
+ if (Array.isArray(r.filenames))
517
+ return r.filenames.join("\n");
518
+ if (Array.isArray(r.matches)) {
519
+ return r.matches.map((m) => typeof m === "string" ? m : JSON.stringify(m)).join("\n");
520
+ }
521
+ if (Array.isArray(r.results)) {
522
+ return r.results.map((x) => typeof x === "string" ? x : x?.title ?? x?.url ?? JSON.stringify(x)).join("\n");
523
+ }
524
+ const inpObj = maybeParseJson(inp);
525
+ const kept = {};
526
+ for (const [k, v] of Object.entries(r)) {
527
+ if (TOOL_RESPONSE_DROP.has(k))
528
+ continue;
529
+ if (v === "" || v === false || v == null)
530
+ continue;
531
+ if (typeof inpObj === "object" && inpObj) {
532
+ const inObj = inpObj;
533
+ if (k in inObj && JSON.stringify(inObj[k]) === JSON.stringify(v))
534
+ continue;
535
+ const snake = snakeCase(k);
536
+ if (snake in inObj && JSON.stringify(inObj[snake]) === JSON.stringify(v))
537
+ continue;
538
+ const camel = camelCase(k);
539
+ if (camel in inObj && JSON.stringify(inObj[camel]) === JSON.stringify(v))
540
+ continue;
541
+ }
542
+ kept[k] = v;
543
+ }
544
+ return Object.keys(kept).length ? JSON.stringify(kept) : "[ok]";
545
+ }
546
+ function formatToolCall(obj) {
547
+ return `[tool:${obj?.tool_name ?? "?"}]
548
+ input: ${formatToolInput(obj?.tool_input)}
549
+ response: ${formatToolResponse(obj?.tool_response, obj?.tool_input, obj?.tool_name)}`;
550
+ }
551
+ function normalizeContent(path, raw) {
552
+ if (!path.includes("/sessions/"))
553
+ return raw;
554
+ if (!raw || raw[0] !== "{")
555
+ return raw;
556
+ let obj;
557
+ try {
558
+ obj = JSON.parse(raw);
559
+ } catch {
560
+ return raw;
561
+ }
562
+ if (Array.isArray(obj.turns)) {
563
+ const header = [];
564
+ if (obj.date_time)
565
+ header.push(`date: ${obj.date_time}`);
566
+ if (obj.speakers) {
567
+ const s = obj.speakers;
568
+ const names = [s.speaker_a, s.speaker_b].filter(Boolean).join(", ");
569
+ if (names)
570
+ header.push(`speakers: ${names}`);
571
+ }
572
+ const lines = obj.turns.map((t) => {
573
+ const sp = String(t?.speaker ?? t?.name ?? "?").trim();
574
+ const tx = String(t?.text ?? t?.content ?? "").replace(/\s+/g, " ").trim();
575
+ const tag = t?.dia_id ? `[${t.dia_id}] ` : "";
576
+ return `${tag}${sp}: ${tx}`;
577
+ });
578
+ const out2 = [...header, ...lines].join("\n");
579
+ return out2.trim() ? out2 : raw;
580
+ }
581
+ const stripRecalled = (t) => {
582
+ const i = t.indexOf("<recalled-memories>");
583
+ if (i === -1)
584
+ return t;
585
+ const j = t.lastIndexOf("</recalled-memories>");
586
+ if (j === -1 || j < i)
587
+ return t;
588
+ const head = t.slice(0, i);
589
+ const tail = t.slice(j + "</recalled-memories>".length);
590
+ return (head + tail).replace(/^\s+/, "").replace(/\n{3,}/g, "\n\n");
591
+ };
592
+ let out = null;
593
+ if (obj.type === "user_message") {
594
+ out = `[user] ${stripRecalled(String(obj.content ?? ""))}`;
595
+ } else if (obj.type === "assistant_message") {
596
+ const agent = obj.agent_type ? ` (agent=${obj.agent_type})` : "";
597
+ out = `[assistant${agent}] ${stripRecalled(String(obj.content ?? ""))}`;
598
+ } else if (obj.type === "tool_call") {
599
+ out = formatToolCall(obj);
600
+ }
601
+ if (out === null)
602
+ return raw;
603
+ const trimmed = out.trim();
604
+ if (!trimmed || trimmed === "[user]" || trimmed === "[assistant]" || /^\[tool:[^\]]*\]\s+input:\s+\{\}\s+response:\s+\{\}$/.test(trimmed))
605
+ return raw;
606
+ return out;
607
+ }
608
+ function buildPathCondition(targetPath) {
609
+ if (!targetPath || targetPath === "/")
610
+ return "";
611
+ const clean = targetPath.replace(/\/+$/, "");
612
+ if (/[*?]/.test(clean)) {
613
+ const likePattern = sqlLike(clean).replace(/\*/g, "%").replace(/\?/g, "_");
614
+ return `path LIKE '${likePattern}' ESCAPE '\\'`;
615
+ }
616
+ const base = clean.split("/").pop() ?? "";
617
+ if (base.includes(".")) {
618
+ return `path = '${sqlStr(clean)}'`;
619
+ }
620
+ return `(path = '${sqlStr(clean)}' OR path LIKE '${sqlLike(clean)}/%' ESCAPE '\\')`;
621
+ }
622
+ async function searchDeeplakeTables(api, memoryTable, sessionsTable, opts) {
623
+ const { pathFilter, contentScanOnly, likeOp, escapedPattern, prefilterPattern, prefilterPatterns, multiWordPatterns } = opts;
624
+ const limit = opts.limit ?? 100;
625
+ const filterPatterns = contentScanOnly ? prefilterPatterns && prefilterPatterns.length > 0 ? prefilterPatterns : prefilterPattern ? [prefilterPattern] : [] : multiWordPatterns && multiWordPatterns.length > 1 ? multiWordPatterns : [escapedPattern];
626
+ const memFilter = buildContentFilter("summary::text", likeOp, filterPatterns);
627
+ const sessFilter = buildContentFilter("message::text", likeOp, filterPatterns);
628
+ const memQuery = `SELECT path, summary::text AS content, 0 AS source_order, '' AS creation_date FROM "${memoryTable}" WHERE 1=1${pathFilter}${memFilter} LIMIT ${limit}`;
629
+ const sessQuery = `SELECT path, message::text AS content, 1 AS source_order, COALESCE(creation_date::text, '') AS creation_date FROM "${sessionsTable}" WHERE 1=1${pathFilter}${sessFilter} LIMIT ${limit}`;
630
+ const rows = await api.query(`SELECT path, content, source_order, creation_date FROM ((${memQuery}) UNION ALL (${sessQuery})) AS combined ORDER BY path, source_order, creation_date`);
631
+ return rows.map((row) => ({
632
+ path: String(row["path"]),
633
+ content: String(row["content"] ?? "")
634
+ }));
635
+ }
636
+ function buildPathFilter(targetPath) {
637
+ const condition = buildPathCondition(targetPath);
638
+ return condition ? ` AND ${condition}` : "";
639
+ }
640
+ function extractRegexLiteralPrefilter(pattern) {
641
+ if (!pattern)
642
+ return null;
643
+ const parts = [];
644
+ let current = "";
645
+ for (let i = 0; i < pattern.length; i++) {
646
+ const ch = pattern[i];
647
+ if (ch === "\\") {
648
+ const next = pattern[i + 1];
649
+ if (!next)
650
+ return null;
651
+ if (/[dDsSwWbBAZzGkKpP]/.test(next))
652
+ return null;
653
+ current += next;
654
+ i++;
655
+ continue;
656
+ }
657
+ if (ch === ".") {
658
+ if (pattern[i + 1] === "*") {
659
+ if (current)
660
+ parts.push(current);
661
+ current = "";
662
+ i++;
663
+ continue;
664
+ }
665
+ return null;
666
+ }
667
+ if ("|()[]{}+?^$".includes(ch) || ch === "*")
668
+ return null;
669
+ current += ch;
670
+ }
671
+ if (current)
672
+ parts.push(current);
673
+ const literal = parts.reduce((best, part) => part.length > best.length ? part : best, "");
674
+ return literal.length >= 2 ? literal : null;
675
+ }
676
+ function extractRegexAlternationPrefilters(pattern) {
677
+ if (!pattern.includes("|"))
678
+ return null;
679
+ const parts = [];
680
+ let current = "";
681
+ let escaped = false;
682
+ for (let i = 0; i < pattern.length; i++) {
683
+ const ch = pattern[i];
684
+ if (escaped) {
685
+ current += `\\${ch}`;
686
+ escaped = false;
687
+ continue;
688
+ }
689
+ if (ch === "\\") {
690
+ escaped = true;
691
+ continue;
692
+ }
693
+ if (ch === "|") {
694
+ if (!current)
695
+ return null;
696
+ parts.push(current);
697
+ current = "";
698
+ continue;
699
+ }
700
+ if ("()[]{}^$".includes(ch))
701
+ return null;
702
+ current += ch;
703
+ }
704
+ if (escaped || !current)
705
+ return null;
706
+ parts.push(current);
707
+ const literals = [...new Set(parts.map((part) => extractRegexLiteralPrefilter(part)).filter((part) => typeof part === "string" && part.length >= 2))];
708
+ return literals.length > 0 ? literals : null;
709
+ }
710
+ function buildGrepSearchOptions(params, targetPath) {
711
+ const hasRegexMeta = !params.fixedString && /[.*+?^${}()|[\]\\]/.test(params.pattern);
712
+ const literalPrefilter = hasRegexMeta ? extractRegexLiteralPrefilter(params.pattern) : null;
713
+ const alternationPrefilters = hasRegexMeta ? extractRegexAlternationPrefilters(params.pattern) : null;
714
+ const multiWordPatterns = !hasRegexMeta ? params.pattern.split(/\s+/).filter((w) => w.length > 2).slice(0, 4) : [];
715
+ return {
716
+ pathFilter: buildPathFilter(targetPath),
717
+ contentScanOnly: hasRegexMeta,
718
+ likeOp: params.ignoreCase ? "ILIKE" : "LIKE",
719
+ escapedPattern: sqlLike(params.pattern),
720
+ prefilterPattern: literalPrefilter ? sqlLike(literalPrefilter) : void 0,
721
+ prefilterPatterns: alternationPrefilters?.map((literal) => sqlLike(literal)),
722
+ multiWordPatterns: multiWordPatterns.length > 1 ? multiWordPatterns.map((w) => sqlLike(w)) : void 0
723
+ };
724
+ }
725
+ function buildContentFilter(column, likeOp, patterns) {
726
+ if (patterns.length === 0)
727
+ return "";
728
+ if (patterns.length === 1)
729
+ return ` AND ${column} ${likeOp} '%${patterns[0]}%'`;
730
+ return ` AND (${patterns.map((pattern) => `${column} ${likeOp} '%${pattern}%'`).join(" OR ")})`;
731
+ }
732
+ function compileGrepRegex(params) {
733
+ let reStr = params.fixedString ? params.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : params.pattern;
734
+ if (params.wordMatch)
735
+ reStr = `\\b${reStr}\\b`;
736
+ try {
737
+ return new RegExp(reStr, params.ignoreCase ? "i" : "");
738
+ } catch {
739
+ return new RegExp(params.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), params.ignoreCase ? "i" : "");
740
+ }
741
+ }
742
+ function refineGrepMatches(rows, params, forceMultiFilePrefix) {
743
+ const re = compileGrepRegex(params);
744
+ const multi = forceMultiFilePrefix ?? rows.length > 1;
745
+ const output = [];
746
+ for (const row of rows) {
747
+ if (!row.content)
748
+ continue;
749
+ const lines = row.content.split("\n");
750
+ const matched = [];
751
+ for (let i = 0; i < lines.length; i++) {
752
+ const hit = re.test(lines[i]);
753
+ if (hit !== !!params.invertMatch) {
754
+ if (params.filesOnly) {
755
+ output.push(row.path);
756
+ break;
757
+ }
758
+ const prefix = multi ? `${row.path}:` : "";
759
+ const ln = params.lineNumber ? `${i + 1}:` : "";
760
+ matched.push(`${prefix}${ln}${lines[i]}`);
761
+ }
762
+ }
763
+ if (!params.filesOnly) {
764
+ if (params.countOnly) {
765
+ output.push(`${multi ? `${row.path}:` : ""}${matched.length}`);
766
+ } else {
767
+ output.push(...matched);
768
+ }
769
+ }
770
+ }
771
+ return output;
772
+ }
773
+ async function grepBothTables(api, memoryTable, sessionsTable, params, targetPath) {
774
+ const rows = await searchDeeplakeTables(api, memoryTable, sessionsTable, buildGrepSearchOptions(params, targetPath));
775
+ const seen = /* @__PURE__ */ new Set();
776
+ const unique = rows.filter((r) => seen.has(r.path) ? false : (seen.add(r.path), true));
777
+ const normalized = unique.map((r) => ({ path: r.path, content: normalizeContent(r.path, r.content) }));
778
+ return refineGrepMatches(normalized, params);
779
+ }
780
+
781
+ // dist/src/utils/output-cap.js
782
+ var CLAUDE_OUTPUT_CAP_BYTES = 8 * 1024;
783
+ function byteLen(str) {
784
+ return Buffer.byteLength(str, "utf8");
785
+ }
786
+ function capOutputForClaude(output, options = {}) {
787
+ const maxBytes = options.maxBytes ?? CLAUDE_OUTPUT_CAP_BYTES;
788
+ if (byteLen(output) <= maxBytes)
789
+ return output;
790
+ const kind = options.kind ?? "output";
791
+ const footerReserve = 220;
792
+ const budget = Math.max(1, maxBytes - footerReserve);
793
+ let running = 0;
794
+ const lines = output.split("\n");
795
+ const keptLines = [];
796
+ for (const line of lines) {
797
+ const lineBytes = byteLen(line) + 1;
798
+ if (running + lineBytes > budget)
799
+ break;
800
+ keptLines.push(line);
801
+ running += lineBytes;
802
+ }
803
+ if (keptLines.length === 0) {
804
+ const buf = Buffer.from(output, "utf8");
805
+ let cutByte = Math.min(budget, buf.length);
806
+ while (cutByte > 0 && (buf[cutByte] & 192) === 128)
807
+ cutByte--;
808
+ const slice = buf.subarray(0, cutByte).toString("utf8");
809
+ const footer2 = `
810
+ ... [${kind} truncated: ${(byteLen(output) / 1024).toFixed(1)} KB total; refine with '| head -N' or a tighter pattern]`;
811
+ return slice + footer2;
812
+ }
813
+ const totalLines = lines.length - (lines[lines.length - 1] === "" ? 1 : 0);
814
+ const elidedLines = Math.max(0, totalLines - keptLines.length);
815
+ const elidedBytes = byteLen(output) - byteLen(keptLines.join("\n"));
816
+ const footer = `
817
+ ... [${kind} truncated: ${elidedLines} more lines (${(elidedBytes / 1024).toFixed(1)} KB) elided \u2014 refine with '| head -N' or a tighter pattern]`;
818
+ return keptLines.join("\n") + footer;
819
+ }
820
+
821
+ // dist/src/hooks/grep-direct.js
822
+ function splitFirstPipelineStage(cmd) {
823
+ const input = cmd.trim();
824
+ let quote = null;
825
+ let escaped = false;
826
+ for (let i = 0; i < input.length; i++) {
827
+ const ch = input[i];
828
+ if (escaped) {
829
+ escaped = false;
830
+ continue;
831
+ }
832
+ if (quote) {
833
+ if (ch === quote) {
834
+ quote = null;
835
+ continue;
836
+ }
837
+ if (ch === "\\" && quote === '"') {
838
+ escaped = true;
839
+ }
840
+ continue;
841
+ }
842
+ if (ch === "\\") {
843
+ escaped = true;
844
+ continue;
845
+ }
846
+ if (ch === "'" || ch === '"') {
847
+ quote = ch;
848
+ continue;
849
+ }
850
+ if (ch === "|")
851
+ return input.slice(0, i).trim();
852
+ }
853
+ return quote ? null : input;
854
+ }
855
+ function tokenizeGrepStage(input) {
856
+ const tokens = [];
857
+ let current = "";
858
+ let quote = null;
859
+ for (let i = 0; i < input.length; i++) {
860
+ const ch = input[i];
861
+ if (quote) {
862
+ if (ch === quote) {
863
+ quote = null;
864
+ } else if (ch === "\\" && quote === '"' && i + 1 < input.length) {
865
+ current += input[++i];
866
+ } else {
867
+ current += ch;
868
+ }
869
+ continue;
870
+ }
871
+ if (ch === "'" || ch === '"') {
872
+ quote = ch;
873
+ continue;
874
+ }
875
+ if (ch === "\\" && i + 1 < input.length) {
876
+ current += input[++i];
877
+ continue;
878
+ }
879
+ if (/\s/.test(ch)) {
880
+ if (current) {
881
+ tokens.push(current);
882
+ current = "";
883
+ }
884
+ continue;
885
+ }
886
+ current += ch;
887
+ }
888
+ if (quote)
889
+ return null;
890
+ if (current)
891
+ tokens.push(current);
892
+ return tokens;
893
+ }
894
+ function parseBashGrep(cmd) {
895
+ const first = splitFirstPipelineStage(cmd);
896
+ if (!first)
897
+ return null;
898
+ if (!/^(grep|egrep|fgrep)\b/.test(first))
899
+ return null;
900
+ const isFixed = first.startsWith("fgrep");
901
+ const tokens = tokenizeGrepStage(first);
902
+ if (!tokens || tokens.length === 0)
903
+ return null;
904
+ let ignoreCase = false, wordMatch = false, filesOnly = false, countOnly = false, lineNumber = false, invertMatch = false, fixedString = isFixed;
905
+ const explicitPatterns = [];
906
+ let ti = 1;
907
+ while (ti < tokens.length) {
908
+ const token = tokens[ti];
909
+ if (token === "--") {
910
+ ti++;
911
+ break;
912
+ }
913
+ if (!token.startsWith("-") || token === "-")
914
+ break;
915
+ if (token.startsWith("--")) {
916
+ const [flag, inlineValue] = token.split("=", 2);
917
+ const handlers = {
918
+ "--ignore-case": () => {
919
+ ignoreCase = true;
920
+ return false;
921
+ },
922
+ "--word-regexp": () => {
923
+ wordMatch = true;
924
+ return false;
925
+ },
926
+ "--files-with-matches": () => {
927
+ filesOnly = true;
928
+ return false;
929
+ },
930
+ "--count": () => {
931
+ countOnly = true;
932
+ return false;
933
+ },
934
+ "--line-number": () => {
935
+ lineNumber = true;
936
+ return false;
937
+ },
938
+ "--invert-match": () => {
939
+ invertMatch = true;
940
+ return false;
941
+ },
942
+ "--fixed-strings": () => {
943
+ fixedString = true;
944
+ return false;
945
+ },
946
+ "--after-context": () => inlineValue === void 0,
947
+ "--before-context": () => inlineValue === void 0,
948
+ "--context": () => inlineValue === void 0,
949
+ "--max-count": () => inlineValue === void 0,
950
+ "--regexp": () => {
951
+ if (inlineValue !== void 0) {
952
+ explicitPatterns.push(inlineValue);
953
+ return false;
954
+ }
955
+ return true;
956
+ }
957
+ };
958
+ const consumeNext = handlers[flag]?.() ?? false;
959
+ if (consumeNext) {
960
+ ti++;
961
+ if (ti >= tokens.length)
962
+ return null;
963
+ if (flag === "--regexp")
964
+ explicitPatterns.push(tokens[ti]);
965
+ }
966
+ ti++;
967
+ continue;
968
+ }
969
+ const shortFlags = token.slice(1);
970
+ for (let i = 0; i < shortFlags.length; i++) {
971
+ const flag = shortFlags[i];
972
+ switch (flag) {
973
+ case "i":
974
+ ignoreCase = true;
975
+ break;
976
+ case "w":
977
+ wordMatch = true;
978
+ break;
979
+ case "l":
980
+ filesOnly = true;
981
+ break;
982
+ case "c":
983
+ countOnly = true;
984
+ break;
985
+ case "n":
986
+ lineNumber = true;
987
+ break;
988
+ case "v":
989
+ invertMatch = true;
990
+ break;
991
+ case "F":
992
+ fixedString = true;
993
+ break;
994
+ case "r":
995
+ case "R":
996
+ case "E":
997
+ break;
998
+ case "A":
999
+ case "B":
1000
+ case "C":
1001
+ case "m":
1002
+ if (i === shortFlags.length - 1) {
1003
+ ti++;
1004
+ if (ti >= tokens.length)
1005
+ return null;
1006
+ }
1007
+ i = shortFlags.length;
1008
+ break;
1009
+ case "e": {
1010
+ const inlineValue = shortFlags.slice(i + 1);
1011
+ if (inlineValue) {
1012
+ explicitPatterns.push(inlineValue);
1013
+ } else {
1014
+ ti++;
1015
+ if (ti >= tokens.length)
1016
+ return null;
1017
+ explicitPatterns.push(tokens[ti]);
1018
+ }
1019
+ i = shortFlags.length;
1020
+ break;
1021
+ }
1022
+ default:
1023
+ break;
1024
+ }
1025
+ }
1026
+ ti++;
1027
+ }
1028
+ const pattern = explicitPatterns.length > 0 ? explicitPatterns[0] : tokens[ti];
1029
+ if (!pattern)
1030
+ return null;
1031
+ let target = explicitPatterns.length > 0 ? tokens[ti] ?? "/" : tokens[ti + 1] ?? "/";
1032
+ if (target === "." || target === "./")
1033
+ target = "/";
1034
+ return {
1035
+ pattern,
1036
+ targetPath: target,
1037
+ ignoreCase,
1038
+ wordMatch,
1039
+ filesOnly,
1040
+ countOnly,
1041
+ lineNumber,
1042
+ invertMatch,
1043
+ fixedString
1044
+ };
1045
+ }
1046
+ async function handleGrepDirect(api, table, sessionsTable, params) {
1047
+ if (!params.pattern)
1048
+ return null;
1049
+ const matchParams = {
1050
+ pattern: params.pattern,
1051
+ ignoreCase: params.ignoreCase,
1052
+ wordMatch: params.wordMatch,
1053
+ filesOnly: params.filesOnly,
1054
+ countOnly: params.countOnly,
1055
+ lineNumber: params.lineNumber,
1056
+ invertMatch: params.invertMatch,
1057
+ fixedString: params.fixedString
1058
+ };
1059
+ const output = await grepBothTables(api, table, sessionsTable, matchParams, params.targetPath);
1060
+ const joined = output.join("\n") || "(no matches)";
1061
+ return capOutputForClaude(joined, { kind: "grep" });
1062
+ }
1063
+
1064
+ // dist/src/hooks/virtual-table-query.js
1065
+ function normalizeSessionPart(path, content) {
1066
+ return normalizeContent(path, content);
1067
+ }
1068
+ function buildVirtualIndexContent(summaryRows, sessionRows = []) {
1069
+ const total = summaryRows.length + sessionRows.length;
1070
+ const lines = [
1071
+ "# Memory Index",
1072
+ "",
1073
+ `${total} entries (${summaryRows.length} summaries, ${sessionRows.length} sessions):`,
1074
+ ""
1075
+ ];
1076
+ if (summaryRows.length > 0) {
1077
+ lines.push("## Summaries", "");
1078
+ for (const row of summaryRows) {
1079
+ const path = row["path"];
1080
+ const project = row["project"] || "";
1081
+ const description = (row["description"] || "").slice(0, 120);
1082
+ const date = (row["creation_date"] || "").slice(0, 10);
1083
+ lines.push(`- [${path}](${path}) ${date} ${project ? `[${project}]` : ""} ${description}`);
1084
+ }
1085
+ lines.push("");
1086
+ }
1087
+ if (sessionRows.length > 0) {
1088
+ lines.push("## Sessions", "");
1089
+ for (const row of sessionRows) {
1090
+ const path = row["path"];
1091
+ const description = (row["description"] || "").slice(0, 120);
1092
+ lines.push(`- [${path}](${path}) ${description}`);
1093
+ }
1094
+ }
1095
+ return lines.join("\n");
1096
+ }
1097
+ function buildUnionQuery(memoryQuery, sessionsQuery) {
1098
+ return `SELECT path, content, size_bytes, creation_date, source_order FROM ((${memoryQuery}) UNION ALL (${sessionsQuery})) AS combined ORDER BY path, source_order, creation_date`;
1099
+ }
1100
+ function buildInList(paths) {
1101
+ return paths.map((path) => `'${sqlStr(path)}'`).join(", ");
1102
+ }
1103
+ function buildDirFilter(dirs) {
1104
+ const cleaned = [...new Set(dirs.map((dir) => dir.replace(/\/+$/, "") || "/"))];
1105
+ if (cleaned.length === 0 || cleaned.includes("/"))
1106
+ return "";
1107
+ const clauses = cleaned.map((dir) => `path LIKE '${sqlLike(dir)}/%' ESCAPE '\\'`);
1108
+ return ` WHERE ${clauses.join(" OR ")}`;
1109
+ }
1110
+ async function queryUnionRows(api, memoryQuery, sessionsQuery) {
1111
+ const unionQuery = buildUnionQuery(memoryQuery, sessionsQuery);
1112
+ try {
1113
+ return await api.query(unionQuery);
1114
+ } catch {
1115
+ const [memoryRows, sessionRows] = await Promise.all([
1116
+ api.query(memoryQuery).catch(() => []),
1117
+ api.query(sessionsQuery).catch(() => [])
1118
+ ]);
1119
+ return [...memoryRows, ...sessionRows];
1120
+ }
1121
+ }
1122
+ async function readVirtualPathContents(api, memoryTable, sessionsTable, virtualPaths) {
1123
+ const uniquePaths = [...new Set(virtualPaths)];
1124
+ const result = new Map(uniquePaths.map((path) => [path, null]));
1125
+ if (uniquePaths.length === 0)
1126
+ return result;
1127
+ const inList = buildInList(uniquePaths);
1128
+ const rows = await queryUnionRows(api, `SELECT path, summary::text AS content, NULL::bigint AS size_bytes, '' AS creation_date, 0 AS source_order FROM "${memoryTable}" WHERE path IN (${inList})`, `SELECT path, message::text AS content, NULL::bigint AS size_bytes, COALESCE(creation_date::text, '') AS creation_date, 1 AS source_order FROM "${sessionsTable}" WHERE path IN (${inList})`);
1129
+ const memoryHits = /* @__PURE__ */ new Map();
1130
+ const sessionHits = /* @__PURE__ */ new Map();
1131
+ for (const row of rows) {
1132
+ const path = row["path"];
1133
+ const content = row["content"];
1134
+ const sourceOrder = Number(row["source_order"] ?? 0);
1135
+ if (typeof path !== "string" || typeof content !== "string")
1136
+ continue;
1137
+ if (sourceOrder === 0) {
1138
+ memoryHits.set(path, content);
1139
+ } else {
1140
+ const current = sessionHits.get(path) ?? [];
1141
+ current.push(normalizeSessionPart(path, content));
1142
+ sessionHits.set(path, current);
1143
+ }
1144
+ }
1145
+ for (const path of uniquePaths) {
1146
+ if (memoryHits.has(path)) {
1147
+ result.set(path, memoryHits.get(path) ?? null);
1148
+ continue;
1149
+ }
1150
+ const sessionParts = sessionHits.get(path) ?? [];
1151
+ if (sessionParts.length > 0) {
1152
+ result.set(path, sessionParts.join("\n"));
1153
+ }
1154
+ }
1155
+ if (result.get("/index.md") === null && uniquePaths.includes("/index.md")) {
1156
+ const [summaryRows, sessionRows] = await Promise.all([
1157
+ api.query(`SELECT path, project, description, creation_date FROM "${memoryTable}" WHERE path LIKE '/summaries/%' ORDER BY creation_date DESC`).catch(() => []),
1158
+ api.query(`SELECT path, description FROM "${sessionsTable}" WHERE path LIKE '/sessions/%' ORDER BY path`).catch(() => [])
1159
+ ]);
1160
+ result.set("/index.md", buildVirtualIndexContent(summaryRows, sessionRows));
1161
+ }
1162
+ return result;
1163
+ }
1164
+ async function listVirtualPathRowsForDirs(api, memoryTable, sessionsTable, dirs) {
1165
+ const uniqueDirs = [...new Set(dirs.map((dir) => dir.replace(/\/+$/, "") || "/"))];
1166
+ const filter = buildDirFilter(uniqueDirs);
1167
+ const rows = await queryUnionRows(api, `SELECT path, NULL::text AS content, size_bytes, '' AS creation_date, 0 AS source_order FROM "${memoryTable}"${filter}`, `SELECT path, NULL::text AS content, size_bytes, '' AS creation_date, 1 AS source_order FROM "${sessionsTable}"${filter}`);
1168
+ const deduped = dedupeRowsByPath(rows.map((row) => ({
1169
+ path: row["path"],
1170
+ size_bytes: row["size_bytes"]
1171
+ })));
1172
+ const byDir = /* @__PURE__ */ new Map();
1173
+ for (const dir of uniqueDirs)
1174
+ byDir.set(dir, []);
1175
+ for (const row of deduped) {
1176
+ const path = row["path"];
1177
+ if (typeof path !== "string")
1178
+ continue;
1179
+ for (const dir of uniqueDirs) {
1180
+ const prefix = dir === "/" ? "/" : `${dir}/`;
1181
+ if (dir === "/" || path.startsWith(prefix)) {
1182
+ byDir.get(dir)?.push(row);
1183
+ }
1184
+ }
1185
+ }
1186
+ return byDir;
1187
+ }
1188
+ async function readVirtualPathContent(api, memoryTable, sessionsTable, virtualPath) {
1189
+ return (await readVirtualPathContents(api, memoryTable, sessionsTable, [virtualPath])).get(virtualPath) ?? null;
1190
+ }
1191
+ async function listVirtualPathRows(api, memoryTable, sessionsTable, dir) {
1192
+ return (await listVirtualPathRowsForDirs(api, memoryTable, sessionsTable, [dir])).get(dir.replace(/\/+$/, "") || "/") ?? [];
1193
+ }
1194
+ async function findVirtualPaths(api, memoryTable, sessionsTable, dir, filenamePattern) {
1195
+ const normalizedDir = dir.replace(/\/+$/, "") || "/";
1196
+ const likePath = `${sqlLike(normalizedDir === "/" ? "" : normalizedDir)}/%`;
1197
+ const rows = await queryUnionRows(api, `SELECT path, NULL::text AS content, NULL::bigint AS size_bytes, '' AS creation_date, 0 AS source_order FROM "${memoryTable}" WHERE path LIKE '${likePath}' ESCAPE '\\' AND filename LIKE '${filenamePattern}' ESCAPE '\\'`, `SELECT path, NULL::text AS content, NULL::bigint AS size_bytes, '' AS creation_date, 1 AS source_order FROM "${sessionsTable}" WHERE path LIKE '${likePath}' ESCAPE '\\' AND filename LIKE '${filenamePattern}' ESCAPE '\\'`);
1198
+ return [...new Set(rows.map((row) => row["path"]).filter((value) => typeof value === "string" && value.length > 0))];
1199
+ }
1200
+ function dedupeRowsByPath(rows) {
1201
+ const seen = /* @__PURE__ */ new Set();
1202
+ const unique = [];
1203
+ for (const row of rows) {
1204
+ const path = typeof row["path"] === "string" ? row["path"] : "";
1205
+ if (!path || seen.has(path))
1206
+ continue;
1207
+ seen.add(path);
1208
+ unique.push(row);
1209
+ }
1210
+ return unique;
1211
+ }
1212
+
1213
+ // dist/src/hooks/bash-command-compiler.js
1214
+ function isQuoted(ch) {
1215
+ return ch === "'" || ch === '"';
1216
+ }
1217
+ function splitTopLevel(input, operators) {
1218
+ const parts = [];
1219
+ let current = "";
1220
+ let quote = null;
1221
+ for (let i = 0; i < input.length; i++) {
1222
+ const ch = input[i];
1223
+ if (quote) {
1224
+ if (ch === quote)
1225
+ quote = null;
1226
+ current += ch;
1227
+ continue;
1228
+ }
1229
+ if (isQuoted(ch)) {
1230
+ quote = ch;
1231
+ current += ch;
1232
+ continue;
1233
+ }
1234
+ const matched = operators.find((op) => input.startsWith(op, i));
1235
+ if (matched) {
1236
+ const trimmed2 = current.trim();
1237
+ if (trimmed2)
1238
+ parts.push(trimmed2);
1239
+ current = "";
1240
+ i += matched.length - 1;
1241
+ continue;
1242
+ }
1243
+ current += ch;
1244
+ }
1245
+ if (quote)
1246
+ return null;
1247
+ const trimmed = current.trim();
1248
+ if (trimmed)
1249
+ parts.push(trimmed);
1250
+ return parts;
1251
+ }
1252
+ function tokenizeShellWords(input) {
1253
+ const tokens = [];
1254
+ let current = "";
1255
+ let quote = null;
1256
+ for (let i = 0; i < input.length; i++) {
1257
+ const ch = input[i];
1258
+ if (quote) {
1259
+ if (ch === quote) {
1260
+ quote = null;
1261
+ } else if (ch === "\\" && quote === '"' && i + 1 < input.length) {
1262
+ current += input[++i];
1263
+ } else {
1264
+ current += ch;
1265
+ }
1266
+ continue;
1267
+ }
1268
+ if (isQuoted(ch)) {
1269
+ quote = ch;
1270
+ continue;
1271
+ }
1272
+ if (/\s/.test(ch)) {
1273
+ if (current) {
1274
+ tokens.push(current);
1275
+ current = "";
1276
+ }
1277
+ continue;
1278
+ }
1279
+ current += ch;
1280
+ }
1281
+ if (quote)
1282
+ return null;
1283
+ if (current)
1284
+ tokens.push(current);
1285
+ return tokens;
1286
+ }
1287
+ function expandBraceToken(token) {
1288
+ const match = token.match(/\{([^{}]+)\}/);
1289
+ if (!match)
1290
+ return [token];
1291
+ const [expr] = match;
1292
+ const prefix = token.slice(0, match.index);
1293
+ const suffix = token.slice((match.index ?? 0) + expr.length);
1294
+ let variants = [];
1295
+ const numericRange = match[1].match(/^(-?\d+)\.\.(-?\d+)$/);
1296
+ if (numericRange) {
1297
+ const start = Number(numericRange[1]);
1298
+ const end = Number(numericRange[2]);
1299
+ const step = start <= end ? 1 : -1;
1300
+ for (let value = start; step > 0 ? value <= end : value >= end; value += step) {
1301
+ variants.push(String(value));
1302
+ }
1303
+ } else {
1304
+ variants = match[1].split(",");
1305
+ }
1306
+ return variants.flatMap((variant) => expandBraceToken(`${prefix}${variant}${suffix}`));
1307
+ }
1308
+ function stripAllowedModifiers(segment) {
1309
+ const ignoreMissing = /\s2>\/dev\/null\s*$/.test(segment);
1310
+ const clean = segment.replace(/\s2>\/dev\/null\s*$/g, "").replace(/\s2>&1\s*/g, " ").trim();
1311
+ return { clean, ignoreMissing };
1312
+ }
1313
+ function hasUnsupportedRedirection(segment) {
1314
+ let quote = null;
1315
+ for (let i = 0; i < segment.length; i++) {
1316
+ const ch = segment[i];
1317
+ if (quote) {
1318
+ if (ch === quote)
1319
+ quote = null;
1320
+ continue;
1321
+ }
1322
+ if (isQuoted(ch)) {
1323
+ quote = ch;
1324
+ continue;
1325
+ }
1326
+ if (ch === ">" || ch === "<")
1327
+ return true;
1328
+ }
1329
+ return false;
1330
+ }
1331
+ function parseHeadTailStage(stage) {
1332
+ const tokens = tokenizeShellWords(stage);
1333
+ if (!tokens || tokens.length === 0)
1334
+ return null;
1335
+ const [cmd, ...rest] = tokens;
1336
+ if (cmd !== "head" && cmd !== "tail")
1337
+ return null;
1338
+ if (rest.length === 0)
1339
+ return { lineLimit: 10, fromEnd: cmd === "tail" };
1340
+ if (rest.length === 1) {
1341
+ const count = Number(rest[0]);
1342
+ if (!Number.isFinite(count)) {
1343
+ return { lineLimit: 10, fromEnd: cmd === "tail" };
1344
+ }
1345
+ return { lineLimit: Math.abs(count), fromEnd: cmd === "tail" };
1346
+ }
1347
+ if (rest.length === 2 && /^-\d+$/.test(rest[0])) {
1348
+ const count = Number(rest[0]);
1349
+ if (!Number.isFinite(count))
1350
+ return null;
1351
+ return { lineLimit: Math.abs(count), fromEnd: cmd === "tail" };
1352
+ }
1353
+ if (rest.length === 2 && rest[0] === "-n") {
1354
+ const count = Number(rest[1]);
1355
+ if (!Number.isFinite(count))
1356
+ return null;
1357
+ return { lineLimit: Math.abs(count), fromEnd: cmd === "tail" };
1358
+ }
1359
+ if (rest.length === 3 && rest[0] === "-n") {
1360
+ const count = Number(rest[1]);
1361
+ if (!Number.isFinite(count))
1362
+ return null;
1363
+ return { lineLimit: Math.abs(count), fromEnd: cmd === "tail" };
1364
+ }
1365
+ return null;
1366
+ }
1367
+ function isValidPipelineHeadTailStage(stage) {
1368
+ const tokens = tokenizeShellWords(stage);
1369
+ if (!tokens || tokens[0] !== "head" && tokens[0] !== "tail")
1370
+ return false;
1371
+ if (tokens.length === 1)
1372
+ return true;
1373
+ if (tokens.length === 2)
1374
+ return /^-\d+$/.test(tokens[1]);
1375
+ if (tokens.length === 3)
1376
+ return tokens[1] === "-n" && /^-?\d+$/.test(tokens[2]);
1377
+ return false;
1378
+ }
1379
+ function parseFindNamePatterns(tokens) {
1380
+ const patterns = [];
1381
+ for (let i = 2; i < tokens.length; i++) {
1382
+ const token = tokens[i];
1383
+ if (token === "-type") {
1384
+ i += 1;
1385
+ continue;
1386
+ }
1387
+ if (token === "-o")
1388
+ continue;
1389
+ if (token === "-name") {
1390
+ const pattern = tokens[i + 1];
1391
+ if (!pattern)
1392
+ return null;
1393
+ patterns.push(pattern);
1394
+ i += 1;
1395
+ continue;
1396
+ }
1397
+ return null;
1398
+ }
1399
+ return patterns.length > 0 ? patterns : null;
1400
+ }
1401
+ function parseCompiledSegment(segment) {
1402
+ const { clean, ignoreMissing } = stripAllowedModifiers(segment);
1403
+ if (hasUnsupportedRedirection(clean))
1404
+ return null;
1405
+ const pipeline = splitTopLevel(clean, ["|"]);
1406
+ if (!pipeline || pipeline.length === 0)
1407
+ return null;
1408
+ const tokens = tokenizeShellWords(pipeline[0]);
1409
+ if (!tokens || tokens.length === 0)
1410
+ return null;
1411
+ if (tokens[0] === "echo" && pipeline.length === 1) {
1412
+ const text = tokens.slice(1).join(" ");
1413
+ return { kind: "echo", text };
1414
+ }
1415
+ if (tokens[0] === "cat") {
1416
+ const paths = tokens.slice(1).flatMap(expandBraceToken);
1417
+ if (paths.length === 0)
1418
+ return null;
1419
+ let lineLimit = 0;
1420
+ let fromEnd = false;
1421
+ let countLines2 = false;
1422
+ if (pipeline.length > 1) {
1423
+ if (pipeline.length !== 2)
1424
+ return null;
1425
+ const pipeStage = pipeline[1].trim();
1426
+ if (/^wc\s+-l\s*$/.test(pipeStage)) {
1427
+ if (paths.length !== 1)
1428
+ return null;
1429
+ countLines2 = true;
1430
+ } else {
1431
+ if (!isValidPipelineHeadTailStage(pipeStage))
1432
+ return null;
1433
+ const headTail = parseHeadTailStage(pipeStage);
1434
+ if (!headTail)
1435
+ return null;
1436
+ lineLimit = headTail.lineLimit;
1437
+ fromEnd = headTail.fromEnd;
1438
+ }
1439
+ }
1440
+ return { kind: "cat", paths, lineLimit, fromEnd, countLines: countLines2, ignoreMissing };
1441
+ }
1442
+ if (tokens[0] === "head" || tokens[0] === "tail") {
1443
+ if (pipeline.length !== 1)
1444
+ return null;
1445
+ const parsed = parseHeadTailStage(clean);
1446
+ if (!parsed)
1447
+ return null;
1448
+ const headTokens = tokenizeShellWords(clean);
1449
+ if (!headTokens)
1450
+ return null;
1451
+ if (headTokens[1] === "-n" && headTokens.length < 4 || /^-\d+$/.test(headTokens[1] ?? "") && headTokens.length < 3 || headTokens.length === 2 && /^-?\d+$/.test(headTokens[1] ?? ""))
1452
+ return null;
1453
+ const path = headTokens[headTokens.length - 1];
1454
+ if (path === "head" || path === "tail" || path === "-n")
1455
+ return null;
1456
+ return {
1457
+ kind: "cat",
1458
+ paths: expandBraceToken(path),
1459
+ lineLimit: parsed.lineLimit,
1460
+ fromEnd: parsed.fromEnd,
1461
+ countLines: false,
1462
+ ignoreMissing
1463
+ };
1464
+ }
1465
+ if (tokens[0] === "wc" && tokens[1] === "-l" && pipeline.length === 1 && tokens[2]) {
1466
+ return {
1467
+ kind: "cat",
1468
+ paths: expandBraceToken(tokens[2]),
1469
+ lineLimit: 0,
1470
+ fromEnd: false,
1471
+ countLines: true,
1472
+ ignoreMissing
1473
+ };
1474
+ }
1475
+ if (tokens[0] === "ls" && pipeline.length === 1) {
1476
+ const dirs = tokens.slice(1).filter((token) => !token.startsWith("-")).flatMap(expandBraceToken);
1477
+ const longFormat = tokens.some((token) => token.startsWith("-") && token.includes("l"));
1478
+ return { kind: "ls", dirs: dirs.length > 0 ? dirs : ["/"], longFormat };
1479
+ }
1480
+ if (tokens[0] === "find") {
1481
+ if (pipeline.length > 3)
1482
+ return null;
1483
+ const dir = tokens[1];
1484
+ if (!dir)
1485
+ return null;
1486
+ const patterns = parseFindNamePatterns(tokens);
1487
+ if (!patterns)
1488
+ return null;
1489
+ const countOnly = pipeline.length === 2 && /^wc\s+-l\s*$/.test(pipeline[1].trim());
1490
+ if (countOnly) {
1491
+ if (patterns.length !== 1)
1492
+ return null;
1493
+ return { kind: "find", dir, pattern: patterns[0], countOnly };
1494
+ }
1495
+ if (pipeline.length >= 2) {
1496
+ const xargsTokens = tokenizeShellWords(pipeline[1].trim());
1497
+ if (!xargsTokens || xargsTokens[0] !== "xargs")
1498
+ return null;
1499
+ const xargsArgs = xargsTokens.slice(1);
1500
+ while (xargsArgs[0] && xargsArgs[0].startsWith("-")) {
1501
+ if (xargsArgs[0] === "-r") {
1502
+ xargsArgs.shift();
1503
+ continue;
1504
+ }
1505
+ return null;
1506
+ }
1507
+ const grepCmd = xargsArgs.join(" ");
1508
+ const grepParams2 = parseBashGrep(grepCmd);
1509
+ if (!grepParams2)
1510
+ return null;
1511
+ let lineLimit = 0;
1512
+ if (pipeline.length === 3) {
1513
+ const headStage = pipeline[2].trim();
1514
+ if (!isValidPipelineHeadTailStage(headStage))
1515
+ return null;
1516
+ const headTail = parseHeadTailStage(headStage);
1517
+ if (!headTail || headTail.fromEnd)
1518
+ return null;
1519
+ lineLimit = headTail.lineLimit;
1520
+ }
1521
+ return { kind: "find_grep", dir, patterns, params: grepParams2, lineLimit };
1522
+ }
1523
+ if (patterns.length !== 1)
1524
+ return null;
1525
+ return { kind: "find", dir, pattern: patterns[0], countOnly };
1526
+ }
1527
+ const grepParams = parseBashGrep(clean);
1528
+ if (grepParams) {
1529
+ let lineLimit = 0;
1530
+ if (pipeline.length > 1) {
1531
+ if (pipeline.length !== 2)
1532
+ return null;
1533
+ const headStage = pipeline[1].trim();
1534
+ if (!isValidPipelineHeadTailStage(headStage))
1535
+ return null;
1536
+ const headTail = parseHeadTailStage(headStage);
1537
+ if (!headTail || headTail.fromEnd)
1538
+ return null;
1539
+ lineLimit = headTail.lineLimit;
1540
+ }
1541
+ return { kind: "grep", params: grepParams, lineLimit };
1542
+ }
1543
+ return null;
1544
+ }
1545
+ function parseCompiledBashCommand(cmd) {
1546
+ if (cmd.includes("||"))
1547
+ return null;
1548
+ const segments = splitTopLevel(cmd, ["&&", ";", "\n"]);
1549
+ if (!segments || segments.length === 0)
1550
+ return null;
1551
+ const parsed = segments.map(parseCompiledSegment);
1552
+ if (parsed.some((segment) => segment === null))
1553
+ return null;
1554
+ return parsed;
1555
+ }
1556
+ function applyLineWindow(content, lineLimit, fromEnd) {
1557
+ if (lineLimit <= 0)
1558
+ return content;
1559
+ const lines = content.split("\n");
1560
+ return (fromEnd ? lines.slice(-lineLimit) : lines.slice(0, lineLimit)).join("\n");
1561
+ }
1562
+ function countLines(content) {
1563
+ return content === "" ? 0 : content.split("\n").length;
1564
+ }
1565
+ function renderDirectoryListing(dir, rows, longFormat) {
1566
+ const entries = /* @__PURE__ */ new Map();
1567
+ const prefix = dir === "/" ? "/" : `${dir}/`;
1568
+ for (const row of rows) {
1569
+ const path = row["path"];
1570
+ if (!path.startsWith(prefix) && dir !== "/")
1571
+ continue;
1572
+ const rest = dir === "/" ? path.slice(1) : path.slice(prefix.length);
1573
+ const slash = rest.indexOf("/");
1574
+ const name = slash === -1 ? rest : rest.slice(0, slash);
1575
+ if (!name)
1576
+ continue;
1577
+ const existing = entries.get(name);
1578
+ if (slash !== -1) {
1579
+ if (!existing)
1580
+ entries.set(name, { isDir: true, size: 0 });
1581
+ } else {
1582
+ entries.set(name, { isDir: false, size: Number(row["size_bytes"] ?? 0) });
1583
+ }
1584
+ }
1585
+ if (entries.size === 0)
1586
+ return `ls: cannot access '${dir}': No such file or directory`;
1587
+ const lines = [];
1588
+ for (const [name, info] of [...entries].sort((a, b) => a[0].localeCompare(b[0]))) {
1589
+ if (longFormat) {
1590
+ const type = info.isDir ? "drwxr-xr-x" : "-rw-r--r--";
1591
+ const size = String(info.isDir ? 0 : info.size).padStart(6);
1592
+ lines.push(`${type} 1 user user ${size} ${name}${info.isDir ? "/" : ""}`);
1593
+ } else {
1594
+ lines.push(name + (info.isDir ? "/" : ""));
1595
+ }
1596
+ }
1597
+ return lines.join("\n");
1598
+ }
1599
+ async function executeCompiledBashCommand(api, memoryTable, sessionsTable, cmd, deps = {}) {
1600
+ const { readVirtualPathContentsFn = readVirtualPathContents, listVirtualPathRowsForDirsFn = listVirtualPathRowsForDirs, findVirtualPathsFn = findVirtualPaths, handleGrepDirectFn = handleGrepDirect } = deps;
1601
+ const plan = parseCompiledBashCommand(cmd);
1602
+ if (!plan)
1603
+ return null;
1604
+ const readPaths = [...new Set(plan.flatMap((segment) => segment.kind === "cat" ? segment.paths : []))];
1605
+ const listDirs = [...new Set(plan.flatMap((segment) => segment.kind === "ls" ? segment.dirs.map((dir) => dir.replace(/\/+$/, "") || "/") : []))];
1606
+ const contentMap = readPaths.length > 0 ? await readVirtualPathContentsFn(api, memoryTable, sessionsTable, readPaths) : /* @__PURE__ */ new Map();
1607
+ const dirRowsMap = listDirs.length > 0 ? await listVirtualPathRowsForDirsFn(api, memoryTable, sessionsTable, listDirs) : /* @__PURE__ */ new Map();
1608
+ const outputs = [];
1609
+ for (const segment of plan) {
1610
+ if (segment.kind === "echo") {
1611
+ outputs.push(segment.text);
1612
+ continue;
1613
+ }
1614
+ if (segment.kind === "cat") {
1615
+ const contents = [];
1616
+ for (const path of segment.paths) {
1617
+ const content = contentMap.get(path) ?? null;
1618
+ if (content === null) {
1619
+ if (segment.ignoreMissing)
1620
+ continue;
1621
+ return null;
1622
+ }
1623
+ contents.push(content);
1624
+ }
1625
+ const combined = contents.join("");
1626
+ if (segment.countLines) {
1627
+ outputs.push(`${countLines(combined)} ${segment.paths[0]}`);
1628
+ } else {
1629
+ outputs.push(applyLineWindow(combined, segment.lineLimit, segment.fromEnd));
1630
+ }
1631
+ continue;
1632
+ }
1633
+ if (segment.kind === "ls") {
1634
+ for (const dir of segment.dirs) {
1635
+ outputs.push(renderDirectoryListing(dir.replace(/\/+$/, "") || "/", dirRowsMap.get(dir.replace(/\/+$/, "") || "/") ?? [], segment.longFormat));
1636
+ }
1637
+ continue;
1638
+ }
1639
+ if (segment.kind === "find") {
1640
+ const filenamePattern = sqlLike(segment.pattern).replace(/\*/g, "%").replace(/\?/g, "_");
1641
+ const paths = await findVirtualPathsFn(api, memoryTable, sessionsTable, segment.dir.replace(/\/+$/, "") || "/", filenamePattern);
1642
+ outputs.push(segment.countOnly ? String(paths.length) : paths.join("\n") || "(no matches)");
1643
+ continue;
1644
+ }
1645
+ if (segment.kind === "find_grep") {
1646
+ const dir = segment.dir.replace(/\/+$/, "") || "/";
1647
+ const candidateBatches = await Promise.all(segment.patterns.map((pattern) => findVirtualPathsFn(api, memoryTable, sessionsTable, dir, sqlLike(pattern).replace(/\*/g, "%").replace(/\?/g, "_"))));
1648
+ const candidatePaths = [...new Set(candidateBatches.flat())];
1649
+ if (candidatePaths.length === 0) {
1650
+ outputs.push("(no matches)");
1651
+ continue;
1652
+ }
1653
+ const candidateContents = await readVirtualPathContentsFn(api, memoryTable, sessionsTable, candidatePaths);
1654
+ const matched = refineGrepMatches(candidatePaths.flatMap((path) => {
1655
+ const content = candidateContents.get(path);
1656
+ if (content === null || content === void 0)
1657
+ return [];
1658
+ return [{ path, content: normalizeContent(path, content) }];
1659
+ }), segment.params);
1660
+ const limited = segment.lineLimit > 0 ? matched.slice(0, segment.lineLimit) : matched;
1661
+ outputs.push(limited.join("\n") || "(no matches)");
1662
+ continue;
1663
+ }
1664
+ if (segment.kind === "grep") {
1665
+ const result = await handleGrepDirectFn(api, memoryTable, sessionsTable, segment.params);
1666
+ if (result === null)
1667
+ return null;
1668
+ if (segment.lineLimit > 0) {
1669
+ outputs.push(result.split("\n").slice(0, segment.lineLimit).join("\n"));
1670
+ } else {
1671
+ outputs.push(result);
1672
+ }
1673
+ continue;
1674
+ }
1675
+ }
1676
+ return capOutputForClaude(outputs.join("\n"), { kind: "bash" });
1677
+ }
1678
+
1679
+ // dist/src/hooks/query-cache.js
1680
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
1681
+ import { join as join4 } from "node:path";
1682
+ import { homedir as homedir3 } from "node:os";
1683
+ var log3 = (msg) => log("query-cache", msg);
1684
+ var DEFAULT_CACHE_ROOT = join4(homedir3(), ".deeplake", "query-cache");
1685
+ var INDEX_CACHE_FILE = "index.md";
1686
+ function getSessionQueryCacheDir(sessionId, deps = {}) {
1687
+ const { cacheRoot = DEFAULT_CACHE_ROOT } = deps;
1688
+ return join4(cacheRoot, sessionId);
1689
+ }
1690
+ function readCachedIndexContent(sessionId, deps = {}) {
1691
+ const { logFn = log3 } = deps;
1692
+ try {
1693
+ return readFileSync3(join4(getSessionQueryCacheDir(sessionId, deps), INDEX_CACHE_FILE), "utf-8");
1694
+ } catch (e) {
1695
+ if (e?.code === "ENOENT")
1696
+ return null;
1697
+ logFn(`read failed for session=${sessionId}: ${e.message}`);
1698
+ return null;
1699
+ }
1700
+ }
1701
+ function writeCachedIndexContent(sessionId, content, deps = {}) {
1702
+ const { logFn = log3 } = deps;
1703
+ try {
1704
+ const dir = getSessionQueryCacheDir(sessionId, deps);
1705
+ mkdirSync2(dir, { recursive: true });
1706
+ writeFileSync2(join4(dir, INDEX_CACHE_FILE), content, "utf-8");
1707
+ } catch (e) {
1708
+ logFn(`write failed for session=${sessionId}: ${e.message}`);
1709
+ }
1710
+ }
1711
+
1712
+ // dist/src/utils/direct-run.js
1713
+ import { resolve } from "node:path";
1714
+ import { fileURLToPath } from "node:url";
1715
+ function isDirectRun(metaUrl) {
1716
+ const entry = process.argv[1];
1717
+ if (!entry)
1718
+ return false;
1719
+ try {
1720
+ return resolve(fileURLToPath(metaUrl)) === resolve(entry);
1721
+ } catch {
1722
+ return false;
1723
+ }
1724
+ }
1725
+
1726
+ // dist/src/hooks/memory-path-utils.js
1727
+ import { homedir as homedir4 } from "node:os";
1728
+ import { join as join5 } from "node:path";
1729
+ var MEMORY_PATH = join5(homedir4(), ".deeplake", "memory");
1730
+ var TILDE_PATH = "~/.deeplake/memory";
1731
+ var HOME_VAR_PATH = "$HOME/.deeplake/memory";
1732
+ var SAFE_BUILTINS = /* @__PURE__ */ new Set([
1733
+ "cat",
1734
+ "ls",
1735
+ "cp",
1736
+ "mv",
1737
+ "rm",
1738
+ "rmdir",
1739
+ "mkdir",
1740
+ "touch",
1741
+ "ln",
1742
+ "chmod",
1743
+ "stat",
1744
+ "readlink",
1745
+ "du",
1746
+ "tree",
1747
+ "file",
1748
+ "grep",
1749
+ "egrep",
1750
+ "fgrep",
1751
+ "rg",
1752
+ "sed",
1753
+ "awk",
1754
+ "cut",
1755
+ "tr",
1756
+ "sort",
1757
+ "uniq",
1758
+ "wc",
1759
+ "head",
1760
+ "tail",
1761
+ "tac",
1762
+ "rev",
1763
+ "nl",
1764
+ "fold",
1765
+ "expand",
1766
+ "unexpand",
1767
+ "paste",
1768
+ "join",
1769
+ "comm",
1770
+ "column",
1771
+ "diff",
1772
+ "strings",
1773
+ "split",
1774
+ "find",
1775
+ "xargs",
1776
+ "which",
1777
+ "jq",
1778
+ "yq",
1779
+ "xan",
1780
+ "base64",
1781
+ "od",
1782
+ "tar",
1783
+ "gzip",
1784
+ "gunzip",
1785
+ "zcat",
1786
+ "md5sum",
1787
+ "sha1sum",
1788
+ "sha256sum",
1789
+ "echo",
1790
+ "printf",
1791
+ "tee",
1792
+ "pwd",
1793
+ "cd",
1794
+ "basename",
1795
+ "dirname",
1796
+ "env",
1797
+ "printenv",
1798
+ "hostname",
1799
+ "whoami",
1800
+ "date",
1801
+ "seq",
1802
+ "expr",
1803
+ "sleep",
1804
+ "timeout",
1805
+ "time",
1806
+ "true",
1807
+ "false",
1808
+ "test",
1809
+ "alias",
1810
+ "unalias",
1811
+ "history",
1812
+ "help",
1813
+ "clear",
1814
+ "for",
1815
+ "while",
1816
+ "do",
1817
+ "done",
1818
+ "if",
1819
+ "then",
1820
+ "else",
1821
+ "fi",
1822
+ "case",
1823
+ "esac"
1824
+ ]);
1825
+ function isSafe(cmd) {
1826
+ if (/\$\(|`|<\(/.test(cmd))
1827
+ return false;
1828
+ const stripped = cmd.replace(/'[^']*'/g, "''").replace(/"[^"]*"/g, '""');
1829
+ const stages = stripped.split(/\||;|&&|\|\||\n/);
1830
+ for (const stage of stages) {
1831
+ const firstToken = stage.trim().split(/\s+/)[0] ?? "";
1832
+ if (firstToken && !SAFE_BUILTINS.has(firstToken))
1833
+ return false;
1834
+ }
1835
+ return true;
1836
+ }
1837
+ function touchesMemory(p) {
1838
+ return p.includes(MEMORY_PATH) || p.includes(TILDE_PATH) || p.includes(HOME_VAR_PATH);
1839
+ }
1840
+ function rewritePaths(cmd) {
1841
+ return cmd.replace(new RegExp(MEMORY_PATH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "/?", "g"), "/").replace(/~\/.deeplake\/memory\/?/g, "/").replace(/\$HOME\/.deeplake\/memory\/?/g, "/").replace(/"\$HOME\/.deeplake\/memory\/?"/g, '"/"');
1842
+ }
1843
+
1844
+ // dist/src/hooks/codex/pre-tool-use.js
1845
+ var log4 = (msg) => log("codex-pre", msg);
1846
+ var __bundleDir = dirname(fileURLToPath2(import.meta.url));
1847
+ var SHELL_BUNDLE = existsSync3(join6(__bundleDir, "shell", "deeplake-shell.js")) ? join6(__bundleDir, "shell", "deeplake-shell.js") : join6(__bundleDir, "..", "shell", "deeplake-shell.js");
1848
+ function buildUnsupportedGuidance() {
1849
+ return "This command is not supported for ~/.deeplake/memory/ operations. Only bash builtins are available: cat, ls, grep, echo, jq, head, tail, sed, awk, wc, sort, find, etc. Do NOT use python, python3, node, curl, or other interpreters. Rewrite your command using only bash tools and retry.";
1850
+ }
1851
+ function runVirtualShell(cmd, shellBundle = SHELL_BUNDLE, logFn = log4) {
1852
+ try {
1853
+ return execFileSync("node", [shellBundle, "-c", cmd], {
1854
+ encoding: "utf-8",
1855
+ timeout: 1e4,
1856
+ env: { ...process.env },
1857
+ stdio: ["pipe", "pipe", "pipe"]
1858
+ }).trim();
1859
+ } catch (e) {
1860
+ logFn(`virtual shell failed: ${e.message}`);
1861
+ return "";
1862
+ }
1863
+ }
1864
+ function buildIndexContent(rows) {
1865
+ const lines = ["# Memory Index", "", `${rows.length} sessions:`, ""];
1866
+ for (const row of rows) {
1867
+ const path = row["path"];
1868
+ const project = row["project"] || "";
1869
+ const description = (row["description"] || "").slice(0, 120);
1870
+ const date = (row["creation_date"] || "").slice(0, 10);
1871
+ lines.push(`- [${path}](${path}) ${date} ${project ? `[${project}]` : ""} ${description}`);
1872
+ }
1873
+ return lines.join("\n");
1874
+ }
1875
+ async function processCodexPreToolUse(input, deps = {}) {
1876
+ const { config = loadConfig(), createApi = (table, activeConfig) => new DeeplakeApi(activeConfig.token, activeConfig.apiUrl, activeConfig.orgId, activeConfig.workspaceId, table), executeCompiledBashCommandFn = executeCompiledBashCommand, readVirtualPathContentsFn = readVirtualPathContents, readVirtualPathContentFn = readVirtualPathContent, listVirtualPathRowsFn = listVirtualPathRows, findVirtualPathsFn = findVirtualPaths, handleGrepDirectFn = handleGrepDirect, readCachedIndexContentFn = readCachedIndexContent, writeCachedIndexContentFn = writeCachedIndexContent, runVirtualShellFn = runVirtualShell, shellBundle = SHELL_BUNDLE, logFn = log4 } = deps;
1877
+ const cmd = input.tool_input?.command ?? "";
1878
+ logFn(`hook fired: cmd=${cmd}`);
1879
+ if (!touchesMemory(cmd))
1880
+ return { action: "pass" };
1881
+ const rewritten = rewritePaths(cmd);
1882
+ if (!isSafe(rewritten)) {
1883
+ const guidance = buildUnsupportedGuidance();
1884
+ logFn(`unsupported command, returning guidance: ${rewritten}`);
1885
+ return {
1886
+ action: "guide",
1887
+ output: guidance,
1888
+ rewrittenCommand: rewritten
1889
+ };
1890
+ }
1891
+ if (config) {
1892
+ const table = process.env["HIVEMIND_TABLE"] ?? "memory";
1893
+ const sessionsTable = process.env["HIVEMIND_SESSIONS_TABLE"] ?? "sessions";
1894
+ const api = createApi(table, config);
1895
+ const readVirtualPathContentsWithCache = async (cachePaths) => {
1896
+ const uniquePaths = [...new Set(cachePaths)];
1897
+ const result2 = new Map(uniquePaths.map((path) => [path, null]));
1898
+ const cachedIndex = uniquePaths.includes("/index.md") ? readCachedIndexContentFn(input.session_id) : null;
1899
+ const remainingPaths = cachedIndex === null ? uniquePaths : uniquePaths.filter((path) => path !== "/index.md");
1900
+ if (cachedIndex !== null) {
1901
+ result2.set("/index.md", cachedIndex);
1902
+ }
1903
+ if (remainingPaths.length > 0) {
1904
+ const fetched = await readVirtualPathContentsFn(api, table, sessionsTable, remainingPaths);
1905
+ for (const [path, content] of fetched)
1906
+ result2.set(path, content);
1907
+ }
1908
+ const fetchedIndex = result2.get("/index.md");
1909
+ if (typeof fetchedIndex === "string") {
1910
+ writeCachedIndexContentFn(input.session_id, fetchedIndex);
1911
+ }
1912
+ return result2;
1913
+ };
1914
+ try {
1915
+ const compiled = await executeCompiledBashCommandFn(api, table, sessionsTable, rewritten, {
1916
+ readVirtualPathContentsFn: async (_api, _memoryTable, _sessionsTable, cachePaths) => readVirtualPathContentsWithCache(cachePaths)
1917
+ });
1918
+ if (compiled !== null) {
1919
+ return { action: "block", output: compiled, rewrittenCommand: rewritten };
1920
+ }
1921
+ let virtualPath = null;
1922
+ let lineLimit = 0;
1923
+ let fromEnd = false;
1924
+ const catCmd = rewritten.replace(/\s+2>\S+/g, "").trim();
1925
+ const catPipeHead = catCmd.match(/^cat\s+(\S+?)\s*(?:\|[^|]*)*\|\s*head\s+(?:-n?\s*)?(-?\d+)\s*$/);
1926
+ if (catPipeHead) {
1927
+ virtualPath = catPipeHead[1];
1928
+ lineLimit = Math.abs(parseInt(catPipeHead[2], 10));
1929
+ }
1930
+ if (!virtualPath) {
1931
+ const catMatch = catCmd.match(/^cat\s+(\S+)\s*$/);
1932
+ if (catMatch)
1933
+ virtualPath = catMatch[1];
1934
+ }
1935
+ if (!virtualPath) {
1936
+ const headMatch = rewritten.match(/^head\s+(?:-n\s*)?(-?\d+)\s+(\S+)\s*$/) ?? rewritten.match(/^head\s+(\S+)\s*$/);
1937
+ if (headMatch) {
1938
+ if (headMatch[2]) {
1939
+ virtualPath = headMatch[2];
1940
+ lineLimit = Math.abs(parseInt(headMatch[1], 10));
1941
+ } else {
1942
+ virtualPath = headMatch[1];
1943
+ lineLimit = 10;
1944
+ }
1945
+ }
1946
+ }
1947
+ if (!virtualPath) {
1948
+ const tailMatch = rewritten.match(/^tail\s+(?:-n\s*)?(-?\d+)\s+(\S+)\s*$/) ?? rewritten.match(/^tail\s+(\S+)\s*$/);
1949
+ if (tailMatch) {
1950
+ fromEnd = true;
1951
+ if (tailMatch[2]) {
1952
+ virtualPath = tailMatch[2];
1953
+ lineLimit = Math.abs(parseInt(tailMatch[1], 10));
1954
+ } else {
1955
+ virtualPath = tailMatch[1];
1956
+ lineLimit = 10;
1957
+ }
1958
+ }
1959
+ }
1960
+ if (!virtualPath) {
1961
+ const wcMatch = rewritten.match(/^wc\s+-l\s+(\S+)\s*$/);
1962
+ if (wcMatch) {
1963
+ virtualPath = wcMatch[1];
1964
+ lineLimit = -1;
1965
+ }
1966
+ }
1967
+ if (virtualPath && !virtualPath.endsWith("/")) {
1968
+ logFn(`direct read: ${virtualPath}`);
1969
+ let content = virtualPath === "/index.md" ? readCachedIndexContentFn(input.session_id) : null;
1970
+ if (content === null) {
1971
+ content = await readVirtualPathContentFn(api, table, sessionsTable, virtualPath);
1972
+ }
1973
+ if (content === null && virtualPath === "/index.md") {
1974
+ const idxRows = await api.query(`SELECT path, project, description, creation_date FROM "${table}" WHERE path LIKE '/summaries/%' ORDER BY creation_date DESC`);
1975
+ content = buildIndexContent(idxRows);
1976
+ }
1977
+ if (content !== null) {
1978
+ if (virtualPath === "/index.md") {
1979
+ writeCachedIndexContentFn(input.session_id, content);
1980
+ }
1981
+ if (lineLimit === -1) {
1982
+ return { action: "block", output: `${content.split("\n").length} ${virtualPath}`, rewrittenCommand: rewritten };
1983
+ }
1984
+ if (lineLimit > 0) {
1985
+ const lines = content.split("\n");
1986
+ content = fromEnd ? lines.slice(-lineLimit).join("\n") : lines.slice(0, lineLimit).join("\n");
1987
+ }
1988
+ return { action: "block", output: content, rewrittenCommand: rewritten };
1989
+ }
1990
+ }
1991
+ const lsMatch = rewritten.match(/^ls\s+(?:-[a-zA-Z]+\s+)*(\S+)?\s*$/);
1992
+ if (lsMatch) {
1993
+ const dir = (lsMatch[1] ?? "/").replace(/\/+$/, "") || "/";
1994
+ const isLong = /\s-[a-zA-Z]*l/.test(rewritten);
1995
+ logFn(`direct ls: ${dir}`);
1996
+ const rows = await listVirtualPathRowsFn(api, table, sessionsTable, dir);
1997
+ const entries = /* @__PURE__ */ new Map();
1998
+ const prefix = dir === "/" ? "/" : `${dir}/`;
1999
+ for (const row of rows) {
2000
+ const path = row["path"];
2001
+ if (!path.startsWith(prefix) && dir !== "/")
2002
+ continue;
2003
+ const rest = dir === "/" ? path.slice(1) : path.slice(prefix.length);
2004
+ const slash = rest.indexOf("/");
2005
+ const name = slash === -1 ? rest : rest.slice(0, slash);
2006
+ if (!name)
2007
+ continue;
2008
+ const existing = entries.get(name);
2009
+ if (slash !== -1) {
2010
+ if (!existing)
2011
+ entries.set(name, { isDir: true, size: 0 });
2012
+ } else {
2013
+ entries.set(name, { isDir: false, size: row["size_bytes"] ?? 0 });
2014
+ }
2015
+ }
2016
+ if (entries.size > 0) {
2017
+ const lines = [];
2018
+ for (const [name, info] of [...entries].sort((a, b) => a[0].localeCompare(b[0]))) {
2019
+ if (isLong) {
2020
+ const type = info.isDir ? "drwxr-xr-x" : "-rw-r--r--";
2021
+ const size = info.isDir ? "0" : String(info.size).padStart(6);
2022
+ lines.push(`${type} 1 user user ${size} ${name}${info.isDir ? "/" : ""}`);
2023
+ } else {
2024
+ lines.push(name + (info.isDir ? "/" : ""));
2025
+ }
2026
+ }
2027
+ return { action: "block", output: lines.join("\n"), rewrittenCommand: rewritten };
2028
+ }
2029
+ return {
2030
+ action: "block",
2031
+ output: `ls: cannot access '${dir}': No such file or directory`,
2032
+ rewrittenCommand: rewritten
2033
+ };
2034
+ }
2035
+ const findMatch = rewritten.match(/^find\s+(\S+)\s+(?:-type\s+\S+\s+)?-name\s+'([^']+)'/);
2036
+ if (findMatch) {
2037
+ const dir = findMatch[1].replace(/\/+$/, "") || "/";
2038
+ const namePattern = sqlLike(findMatch[2]).replace(/\*/g, "%").replace(/\?/g, "_");
2039
+ logFn(`direct find: ${dir} -name '${findMatch[2]}'`);
2040
+ const paths = await findVirtualPathsFn(api, table, sessionsTable, dir, namePattern);
2041
+ let result2 = paths.join("\n") || "";
2042
+ if (/\|\s*wc\s+-l\s*$/.test(rewritten))
2043
+ result2 = String(paths.length);
2044
+ return {
2045
+ action: "block",
2046
+ output: result2 || "(no matches)",
2047
+ rewrittenCommand: rewritten
2048
+ };
2049
+ }
2050
+ const grepParams = parseBashGrep(rewritten);
2051
+ if (grepParams) {
2052
+ logFn(`direct grep: pattern=${grepParams.pattern} path=${grepParams.targetPath}`);
2053
+ const result2 = await handleGrepDirectFn(api, table, sessionsTable, grepParams);
2054
+ if (result2 !== null) {
2055
+ return { action: "block", output: result2, rewrittenCommand: rewritten };
2056
+ }
2057
+ }
2058
+ } catch (e) {
2059
+ logFn(`direct query failed, falling back to shell: ${e.message}`);
2060
+ }
2061
+ }
2062
+ logFn(`intercepted \u2192 running via virtual shell: ${rewritten}`);
2063
+ const result = runVirtualShellFn(rewritten, shellBundle, logFn);
2064
+ return {
2065
+ action: "block",
2066
+ output: result || "[Deeplake Memory] Command returned empty or the file does not exist in cloud storage.",
2067
+ rewrittenCommand: rewritten
2068
+ };
2069
+ }
2070
+ async function main() {
2071
+ const input = await readStdin();
2072
+ const decision = await processCodexPreToolUse(input);
2073
+ if (decision.action === "pass")
2074
+ return;
2075
+ if (decision.action === "guide") {
2076
+ if (decision.output)
2077
+ process.stdout.write(decision.output);
2078
+ process.exit(0);
2079
+ }
2080
+ if (decision.output)
2081
+ process.stderr.write(decision.output);
2082
+ process.exit(2);
2083
+ }
2084
+ if (isDirectRun(import.meta.url)) {
2085
+ main().catch((e) => {
2086
+ log4(`fatal: ${e.message}`);
2087
+ process.exit(0);
2088
+ });
2089
+ }
2090
+ export {
2091
+ buildUnsupportedGuidance,
2092
+ isSafe,
2093
+ processCodexPreToolUse,
2094
+ rewritePaths,
2095
+ runVirtualShell,
2096
+ touchesMemory
2097
+ };