@deeplake/hivemind 0.6.47 → 0.7.4

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 (41) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +158 -51
  4. package/bundle/cli.js +4103 -282
  5. package/codex/bundle/capture.js +510 -90
  6. package/codex/bundle/commands/auth-login.js +219 -72
  7. package/codex/bundle/embeddings/embed-daemon.js +243 -0
  8. package/codex/bundle/pre-tool-use.js +713 -108
  9. package/codex/bundle/session-start-setup.js +209 -58
  10. package/codex/bundle/session-start.js +40 -11
  11. package/codex/bundle/shell/deeplake-shell.js +679 -112
  12. package/codex/bundle/stop.js +477 -59
  13. package/codex/bundle/wiki-worker.js +312 -11
  14. package/cursor/bundle/capture.js +768 -57
  15. package/cursor/bundle/commands/auth-login.js +219 -72
  16. package/cursor/bundle/embeddings/embed-daemon.js +243 -0
  17. package/cursor/bundle/pre-tool-use.js +1684 -0
  18. package/cursor/bundle/session-end.js +223 -2
  19. package/cursor/bundle/session-start.js +209 -57
  20. package/cursor/bundle/shell/deeplake-shell.js +679 -112
  21. package/cursor/bundle/wiki-worker.js +571 -0
  22. package/hermes/bundle/capture.js +1194 -0
  23. package/hermes/bundle/commands/auth-login.js +1009 -0
  24. package/hermes/bundle/embeddings/embed-daemon.js +243 -0
  25. package/hermes/bundle/package.json +1 -0
  26. package/hermes/bundle/pre-tool-use.js +1681 -0
  27. package/hermes/bundle/session-end.js +265 -0
  28. package/hermes/bundle/session-start.js +655 -0
  29. package/hermes/bundle/shell/deeplake-shell.js +69905 -0
  30. package/hermes/bundle/wiki-worker.js +572 -0
  31. package/mcp/bundle/server.js +289 -69
  32. package/openclaw/dist/chunks/auth-creds-AEKS6D3P.js +14 -0
  33. package/openclaw/dist/chunks/chunk-SRCBBT4H.js +37 -0
  34. package/openclaw/dist/chunks/config-G23NI5TV.js +33 -0
  35. package/openclaw/dist/chunks/index-marker-store-PGT5CW6T.js +33 -0
  36. package/openclaw/dist/chunks/setup-config-C35UK4LP.js +114 -0
  37. package/openclaw/dist/index.js +752 -702
  38. package/openclaw/openclaw.plugin.json +1 -1
  39. package/openclaw/package.json +1 -1
  40. package/package.json +7 -3
  41. package/pi/extension-source/hivemind.ts +807 -0
@@ -0,0 +1,1009 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // dist/src/index-marker-store.js
13
+ var index_marker_store_exports = {};
14
+ __export(index_marker_store_exports, {
15
+ buildIndexMarkerPath: () => buildIndexMarkerPath,
16
+ getIndexMarkerDir: () => getIndexMarkerDir,
17
+ hasFreshIndexMarker: () => hasFreshIndexMarker,
18
+ writeIndexMarker: () => writeIndexMarker
19
+ });
20
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
21
+ import { join as join4 } from "node:path";
22
+ import { tmpdir } from "node:os";
23
+ function getIndexMarkerDir() {
24
+ return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join4(tmpdir(), "hivemind-deeplake-indexes");
25
+ }
26
+ function buildIndexMarkerPath(workspaceId, orgId, table, suffix) {
27
+ const markerKey = [workspaceId, orgId, table, suffix].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
28
+ return join4(getIndexMarkerDir(), `${markerKey}.json`);
29
+ }
30
+ function hasFreshIndexMarker(markerPath) {
31
+ if (!existsSync2(markerPath))
32
+ return false;
33
+ try {
34
+ const raw = JSON.parse(readFileSync3(markerPath, "utf-8"));
35
+ const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
36
+ if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
37
+ return false;
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+ function writeIndexMarker(markerPath) {
44
+ mkdirSync2(getIndexMarkerDir(), { recursive: true });
45
+ writeFileSync2(markerPath, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
46
+ }
47
+ var INDEX_MARKER_TTL_MS;
48
+ var init_index_marker_store = __esm({
49
+ "dist/src/index-marker-store.js"() {
50
+ "use strict";
51
+ INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
52
+ }
53
+ });
54
+
55
+ // dist/src/commands/auth.js
56
+ import { execSync } from "node:child_process";
57
+
58
+ // dist/src/utils/client-header.js
59
+ var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
60
+ function deeplakeClientValue() {
61
+ return "hivemind";
62
+ }
63
+ function deeplakeClientHeader() {
64
+ return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
65
+ }
66
+
67
+ // dist/src/commands/auth-creds.js
68
+ import { readFileSync, writeFileSync, mkdirSync, unlinkSync } from "node:fs";
69
+ import { join } from "node:path";
70
+ import { homedir } from "node:os";
71
+ function configDir() {
72
+ return join(homedir(), ".deeplake");
73
+ }
74
+ function credsPath() {
75
+ return join(configDir(), "credentials.json");
76
+ }
77
+ function loadCredentials() {
78
+ try {
79
+ return JSON.parse(readFileSync(credsPath(), "utf-8"));
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+ function saveCredentials(creds) {
85
+ mkdirSync(configDir(), { recursive: true, mode: 448 });
86
+ writeFileSync(credsPath(), JSON.stringify({ ...creds, savedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), { mode: 384 });
87
+ }
88
+ function deleteCredentials() {
89
+ try {
90
+ unlinkSync(credsPath());
91
+ return true;
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
96
+
97
+ // dist/src/commands/auth.js
98
+ var DEFAULT_API_URL = "https://api.deeplake.ai";
99
+ async function apiGet(path, token, apiUrl, orgId) {
100
+ const headers = {
101
+ Authorization: `Bearer ${token}`,
102
+ "Content-Type": "application/json",
103
+ ...deeplakeClientHeader()
104
+ };
105
+ if (orgId)
106
+ headers["X-Activeloop-Org-Id"] = orgId;
107
+ const resp = await fetch(`${apiUrl}${path}`, { headers });
108
+ if (!resp.ok)
109
+ throw new Error(`API ${resp.status}: ${await resp.text().catch(() => "")}`);
110
+ return resp.json();
111
+ }
112
+ async function apiPost(path, body, token, apiUrl, orgId) {
113
+ const headers = {
114
+ Authorization: `Bearer ${token}`,
115
+ "Content-Type": "application/json",
116
+ ...deeplakeClientHeader()
117
+ };
118
+ if (orgId)
119
+ headers["X-Activeloop-Org-Id"] = orgId;
120
+ const resp = await fetch(`${apiUrl}${path}`, { method: "POST", headers, body: JSON.stringify(body) });
121
+ if (!resp.ok)
122
+ throw new Error(`API ${resp.status}: ${await resp.text().catch(() => "")}`);
123
+ return resp.json();
124
+ }
125
+ async function apiDelete(path, token, apiUrl, orgId) {
126
+ const headers = {
127
+ Authorization: `Bearer ${token}`,
128
+ "Content-Type": "application/json",
129
+ ...deeplakeClientHeader()
130
+ };
131
+ if (orgId)
132
+ headers["X-Activeloop-Org-Id"] = orgId;
133
+ const resp = await fetch(`${apiUrl}${path}`, { method: "DELETE", headers });
134
+ if (!resp.ok)
135
+ throw new Error(`API ${resp.status}: ${await resp.text().catch(() => "")}`);
136
+ }
137
+ async function requestDeviceCode(apiUrl = DEFAULT_API_URL) {
138
+ const resp = await fetch(`${apiUrl}/auth/device/code`, {
139
+ method: "POST",
140
+ headers: { "Content-Type": "application/json", ...deeplakeClientHeader() }
141
+ });
142
+ if (!resp.ok)
143
+ throw new Error(`Device flow unavailable: HTTP ${resp.status}`);
144
+ return resp.json();
145
+ }
146
+ async function pollForToken(deviceCode, apiUrl = DEFAULT_API_URL) {
147
+ const resp = await fetch(`${apiUrl}/auth/device/token`, {
148
+ method: "POST",
149
+ headers: { "Content-Type": "application/json", ...deeplakeClientHeader() },
150
+ body: JSON.stringify({ device_code: deviceCode })
151
+ });
152
+ if (resp.ok)
153
+ return resp.json();
154
+ if (resp.status === 400) {
155
+ const err = await resp.json().catch(() => null);
156
+ if (err?.error === "authorization_pending" || err?.error === "slow_down")
157
+ return null;
158
+ if (err?.error === "expired_token")
159
+ throw new Error("Device code expired. Try again.");
160
+ if (err?.error === "access_denied")
161
+ throw new Error("Authorization denied.");
162
+ }
163
+ throw new Error(`Token polling failed: HTTP ${resp.status}`);
164
+ }
165
+ function openBrowser(url) {
166
+ try {
167
+ const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}" 2>/dev/null`;
168
+ execSync(cmd, { stdio: "ignore", timeout: 5e3 });
169
+ return true;
170
+ } catch {
171
+ return false;
172
+ }
173
+ }
174
+ async function deviceFlowLogin(apiUrl = DEFAULT_API_URL) {
175
+ const code = await requestDeviceCode(apiUrl);
176
+ const opened = openBrowser(code.verification_uri_complete);
177
+ const msg = [
178
+ "\nDeeplake Authentication",
179
+ "\u2500".repeat(40),
180
+ `
181
+ Open this URL: ${code.verification_uri_complete}`,
182
+ `Or visit ${code.verification_uri} and enter code: ${code.user_code}`,
183
+ opened ? "\nBrowser opened. Waiting for sign in..." : "\nWaiting for sign in..."
184
+ ].join("\n");
185
+ process.stderr.write(msg + "\n");
186
+ const interval = Math.max(code.interval || 5, 5) * 1e3;
187
+ const deadline = Date.now() + code.expires_in * 1e3;
188
+ while (Date.now() < deadline) {
189
+ await new Promise((r) => setTimeout(r, interval));
190
+ const result = await pollForToken(code.device_code, apiUrl);
191
+ if (result) {
192
+ process.stderr.write("\nAuthentication successful!\n");
193
+ return { token: result.access_token, expiresIn: result.expires_in };
194
+ }
195
+ }
196
+ throw new Error("Device code expired.");
197
+ }
198
+ async function listOrgs(token, apiUrl = DEFAULT_API_URL) {
199
+ const data = await apiGet("/organizations", token, apiUrl);
200
+ return Array.isArray(data) ? data : [];
201
+ }
202
+ async function switchOrg(orgId, orgName) {
203
+ const creds = loadCredentials();
204
+ if (!creds)
205
+ throw new Error("Not logged in. Run deeplake login first.");
206
+ saveCredentials({ ...creds, orgId, orgName });
207
+ }
208
+ async function listWorkspaces(token, apiUrl = DEFAULT_API_URL, orgId) {
209
+ const raw = await apiGet("/workspaces", token, apiUrl, orgId);
210
+ const data = raw.data ?? raw;
211
+ return Array.isArray(data) ? data : [];
212
+ }
213
+ async function switchWorkspace(workspaceId) {
214
+ const creds = loadCredentials();
215
+ if (!creds)
216
+ throw new Error("Not logged in. Run deeplake login first.");
217
+ saveCredentials({ ...creds, workspaceId });
218
+ }
219
+ async function inviteMember(username, accessMode, token, orgId, apiUrl = DEFAULT_API_URL) {
220
+ await apiPost(`/organizations/${orgId}/members/invite`, { username, access_mode: accessMode }, token, apiUrl, orgId);
221
+ }
222
+ async function listMembers(token, orgId, apiUrl = DEFAULT_API_URL) {
223
+ const data = await apiGet(`/organizations/${orgId}/members`, token, apiUrl, orgId);
224
+ return data.members ?? [];
225
+ }
226
+ async function removeMember(userId, token, orgId, apiUrl = DEFAULT_API_URL) {
227
+ await apiDelete(`/organizations/${orgId}/members/${userId}`, token, apiUrl, orgId);
228
+ }
229
+ async function login(apiUrl = DEFAULT_API_URL) {
230
+ const { token: authToken } = await deviceFlowLogin(apiUrl);
231
+ const user = await apiGet("/me", authToken, apiUrl);
232
+ const userName = user.name || (user.email ? user.email.split("@")[0] : "unknown");
233
+ process.stderr.write(`
234
+ Logged in as: ${userName}
235
+ `);
236
+ const orgs = await listOrgs(authToken, apiUrl);
237
+ let orgId;
238
+ let orgName;
239
+ if (orgs.length === 1) {
240
+ orgId = orgs[0].id;
241
+ orgName = orgs[0].name;
242
+ process.stderr.write(`Organization: ${orgName}
243
+ `);
244
+ } else {
245
+ process.stderr.write("\nOrganizations:\n");
246
+ orgs.forEach((org, i) => process.stderr.write(` ${i + 1}. ${org.name}
247
+ `));
248
+ orgId = orgs[0].id;
249
+ orgName = orgs[0].name;
250
+ process.stderr.write(`
251
+ Using: ${orgName}
252
+ `);
253
+ }
254
+ const tokenName = `deeplake-plugin-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
255
+ const tokenData = await apiPost("/users/me/tokens", {
256
+ name: tokenName,
257
+ duration: 365 * 24 * 3600,
258
+ organization_id: orgId
259
+ }, authToken, apiUrl);
260
+ const apiToken = tokenData.token.token;
261
+ const creds = {
262
+ token: apiToken,
263
+ orgId,
264
+ orgName,
265
+ userName,
266
+ workspaceId: "default",
267
+ apiUrl,
268
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
269
+ };
270
+ saveCredentials(creds);
271
+ return creds;
272
+ }
273
+
274
+ // dist/src/config.js
275
+ import { readFileSync as readFileSync2, existsSync } from "node:fs";
276
+ import { join as join2 } from "node:path";
277
+ import { homedir as homedir2, userInfo } from "node:os";
278
+ function loadConfig() {
279
+ const home = homedir2();
280
+ const credPath = join2(home, ".deeplake", "credentials.json");
281
+ let creds = null;
282
+ if (existsSync(credPath)) {
283
+ try {
284
+ creds = JSON.parse(readFileSync2(credPath, "utf-8"));
285
+ } catch {
286
+ return null;
287
+ }
288
+ }
289
+ const token = process.env.HIVEMIND_TOKEN ?? creds?.token;
290
+ const orgId = process.env.HIVEMIND_ORG_ID ?? creds?.orgId;
291
+ if (!token || !orgId)
292
+ return null;
293
+ return {
294
+ token,
295
+ orgId,
296
+ orgName: creds?.orgName ?? orgId,
297
+ userName: creds?.userName || userInfo().username || "unknown",
298
+ workspaceId: process.env.HIVEMIND_WORKSPACE_ID ?? creds?.workspaceId ?? "default",
299
+ apiUrl: process.env.HIVEMIND_API_URL ?? creds?.apiUrl ?? "https://api.deeplake.ai",
300
+ tableName: process.env.HIVEMIND_TABLE ?? "memory",
301
+ sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
302
+ memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join2(home, ".deeplake", "memory")
303
+ };
304
+ }
305
+
306
+ // dist/src/deeplake-api.js
307
+ import { randomUUID } from "node:crypto";
308
+
309
+ // dist/src/utils/debug.js
310
+ import { appendFileSync } from "node:fs";
311
+ import { join as join3 } from "node:path";
312
+ import { homedir as homedir3 } from "node:os";
313
+ var DEBUG = process.env.HIVEMIND_DEBUG === "1";
314
+ var LOG = join3(homedir3(), ".deeplake", "hook-debug.log");
315
+ function log(tag, msg) {
316
+ if (!DEBUG)
317
+ return;
318
+ appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
319
+ `);
320
+ }
321
+
322
+ // dist/src/utils/sql.js
323
+ function sqlStr(value) {
324
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/\0/g, "").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
325
+ }
326
+
327
+ // dist/src/embeddings/columns.js
328
+ var SUMMARY_EMBEDDING_COL = "summary_embedding";
329
+ var MESSAGE_EMBEDDING_COL = "message_embedding";
330
+
331
+ // dist/src/deeplake-api.js
332
+ var indexMarkerStorePromise = null;
333
+ function getIndexMarkerStore() {
334
+ if (!indexMarkerStorePromise)
335
+ indexMarkerStorePromise = Promise.resolve().then(() => (init_index_marker_store(), index_marker_store_exports));
336
+ return indexMarkerStorePromise;
337
+ }
338
+ var log2 = (msg) => log("sdk", msg);
339
+ function summarizeSql(sql, maxLen = 220) {
340
+ const compact = sql.replace(/\s+/g, " ").trim();
341
+ return compact.length > maxLen ? `${compact.slice(0, maxLen)}...` : compact;
342
+ }
343
+ function traceSql(msg) {
344
+ const traceEnabled = process.env.HIVEMIND_TRACE_SQL === "1" || process.env.HIVEMIND_DEBUG === "1";
345
+ if (!traceEnabled)
346
+ return;
347
+ process.stderr.write(`[deeplake-sql] ${msg}
348
+ `);
349
+ if (process.env.HIVEMIND_DEBUG === "1")
350
+ log2(msg);
351
+ }
352
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
353
+ var MAX_RETRIES = 3;
354
+ var BASE_DELAY_MS = 500;
355
+ var MAX_CONCURRENCY = 5;
356
+ var QUERY_TIMEOUT_MS = Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
357
+ function sleep(ms) {
358
+ return new Promise((resolve) => setTimeout(resolve, ms));
359
+ }
360
+ function isTimeoutError(error) {
361
+ const name = error instanceof Error ? error.name.toLowerCase() : "";
362
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
363
+ return name.includes("timeout") || name === "aborterror" || message.includes("timeout") || message.includes("timed out");
364
+ }
365
+ function isDuplicateIndexError(error) {
366
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
367
+ return message.includes("duplicate key value violates unique constraint") || message.includes("pg_class_relname_nsp_index") || message.includes("already exists");
368
+ }
369
+ function isSessionInsertQuery(sql) {
370
+ return /^\s*insert\s+into\s+"[^"]+"\s*\(\s*id\s*,\s*path\s*,\s*filename\s*,\s*message\s*,/i.test(sql);
371
+ }
372
+ function isTransientHtml403(text) {
373
+ const body = text.toLowerCase();
374
+ return body.includes("<html") || body.includes("403 forbidden") || body.includes("cloudflare") || body.includes("nginx");
375
+ }
376
+ var Semaphore = class {
377
+ max;
378
+ waiting = [];
379
+ active = 0;
380
+ constructor(max) {
381
+ this.max = max;
382
+ }
383
+ async acquire() {
384
+ if (this.active < this.max) {
385
+ this.active++;
386
+ return;
387
+ }
388
+ await new Promise((resolve) => this.waiting.push(resolve));
389
+ }
390
+ release() {
391
+ this.active--;
392
+ const next = this.waiting.shift();
393
+ if (next) {
394
+ this.active++;
395
+ next();
396
+ }
397
+ }
398
+ };
399
+ var DeeplakeApi = class {
400
+ token;
401
+ apiUrl;
402
+ orgId;
403
+ workspaceId;
404
+ tableName;
405
+ _pendingRows = [];
406
+ _sem = new Semaphore(MAX_CONCURRENCY);
407
+ _tablesCache = null;
408
+ constructor(token, apiUrl, orgId, workspaceId, tableName) {
409
+ this.token = token;
410
+ this.apiUrl = apiUrl;
411
+ this.orgId = orgId;
412
+ this.workspaceId = workspaceId;
413
+ this.tableName = tableName;
414
+ }
415
+ /** Execute SQL with retry on transient errors and bounded concurrency. */
416
+ async query(sql) {
417
+ const startedAt = Date.now();
418
+ const summary = summarizeSql(sql);
419
+ traceSql(`query start: ${summary}`);
420
+ await this._sem.acquire();
421
+ try {
422
+ const rows = await this._queryWithRetry(sql);
423
+ traceSql(`query ok (${Date.now() - startedAt}ms, rows=${rows.length}): ${summary}`);
424
+ return rows;
425
+ } catch (e) {
426
+ const message = e instanceof Error ? e.message : String(e);
427
+ traceSql(`query fail (${Date.now() - startedAt}ms): ${summary} :: ${message}`);
428
+ throw e;
429
+ } finally {
430
+ this._sem.release();
431
+ }
432
+ }
433
+ async _queryWithRetry(sql) {
434
+ let lastError;
435
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
436
+ let resp;
437
+ try {
438
+ const signal = AbortSignal.timeout(QUERY_TIMEOUT_MS);
439
+ resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables/query`, {
440
+ method: "POST",
441
+ headers: {
442
+ Authorization: `Bearer ${this.token}`,
443
+ "Content-Type": "application/json",
444
+ "X-Activeloop-Org-Id": this.orgId,
445
+ ...deeplakeClientHeader()
446
+ },
447
+ signal,
448
+ body: JSON.stringify({ query: sql })
449
+ });
450
+ } catch (e) {
451
+ if (isTimeoutError(e)) {
452
+ lastError = new Error(`Query timeout after ${QUERY_TIMEOUT_MS}ms`);
453
+ throw lastError;
454
+ }
455
+ lastError = e instanceof Error ? e : new Error(String(e));
456
+ if (attempt < MAX_RETRIES) {
457
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
458
+ log2(`query retry ${attempt + 1}/${MAX_RETRIES} (fetch error: ${lastError.message}) in ${delay.toFixed(0)}ms`);
459
+ await sleep(delay);
460
+ continue;
461
+ }
462
+ throw lastError;
463
+ }
464
+ if (resp.ok) {
465
+ const raw = await resp.json();
466
+ if (!raw?.rows || !raw?.columns)
467
+ return [];
468
+ return raw.rows.map((row) => Object.fromEntries(raw.columns.map((col, i) => [col, row[i]])));
469
+ }
470
+ const text = await resp.text().catch(() => "");
471
+ const retryable403 = isSessionInsertQuery(sql) && (resp.status === 401 || resp.status === 403 && (text.length === 0 || isTransientHtml403(text)));
472
+ const alreadyExists = resp.status === 500 && isDuplicateIndexError(text);
473
+ if (!alreadyExists && attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
474
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
475
+ log2(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
476
+ await sleep(delay);
477
+ continue;
478
+ }
479
+ throw new Error(`Query failed: ${resp.status}: ${text.slice(0, 200)}`);
480
+ }
481
+ throw lastError ?? new Error("Query failed: max retries exceeded");
482
+ }
483
+ // ── Writes ──────────────────────────────────────────────────────────────────
484
+ /** Queue rows for writing. Call commit() to flush. */
485
+ appendRows(rows) {
486
+ this._pendingRows.push(...rows);
487
+ }
488
+ /** Flush pending rows via SQL. */
489
+ async commit() {
490
+ if (this._pendingRows.length === 0)
491
+ return;
492
+ const rows = this._pendingRows;
493
+ this._pendingRows = [];
494
+ const CONCURRENCY = 10;
495
+ for (let i = 0; i < rows.length; i += CONCURRENCY) {
496
+ const chunk = rows.slice(i, i + CONCURRENCY);
497
+ await Promise.allSettled(chunk.map((r) => this.upsertRowSql(r)));
498
+ }
499
+ log2(`commit: ${rows.length} rows`);
500
+ }
501
+ async upsertRowSql(row) {
502
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
503
+ const cd = row.creationDate ?? ts;
504
+ const lud = row.lastUpdateDate ?? ts;
505
+ const exists = await this.query(`SELECT path FROM "${this.tableName}" WHERE path = '${sqlStr(row.path)}' LIMIT 1`);
506
+ if (exists.length > 0) {
507
+ let setClauses = `summary = E'${sqlStr(row.contentText)}', ${SUMMARY_EMBEDDING_COL} = NULL, mime_type = '${sqlStr(row.mimeType)}', size_bytes = ${row.sizeBytes}, last_update_date = '${lud}'`;
508
+ if (row.project !== void 0)
509
+ setClauses += `, project = '${sqlStr(row.project)}'`;
510
+ if (row.description !== void 0)
511
+ setClauses += `, description = '${sqlStr(row.description)}'`;
512
+ await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(row.path)}'`);
513
+ } else {
514
+ const id = randomUUID();
515
+ let cols = `id, path, filename, summary, ${SUMMARY_EMBEDDING_COL}, mime_type, size_bytes, creation_date, last_update_date`;
516
+ let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', NULL, '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
517
+ if (row.project !== void 0) {
518
+ cols += ", project";
519
+ vals += `, '${sqlStr(row.project)}'`;
520
+ }
521
+ if (row.description !== void 0) {
522
+ cols += ", description";
523
+ vals += `, '${sqlStr(row.description)}'`;
524
+ }
525
+ await this.query(`INSERT INTO "${this.tableName}" (${cols}) VALUES (${vals})`);
526
+ }
527
+ }
528
+ /** Update specific columns on a row by path. */
529
+ async updateColumns(path, columns) {
530
+ const setClauses = Object.entries(columns).map(([col, val]) => typeof val === "number" ? `${col} = ${val}` : `${col} = '${sqlStr(String(val))}'`).join(", ");
531
+ await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(path)}'`);
532
+ }
533
+ // ── Convenience ─────────────────────────────────────────────────────────────
534
+ /** Create a BM25 search index on a column. */
535
+ async createIndex(column) {
536
+ await this.query(`CREATE INDEX IF NOT EXISTS idx_${sqlStr(column)}_bm25 ON "${this.tableName}" USING deeplake_index ("${column}")`);
537
+ }
538
+ buildLookupIndexName(table, suffix) {
539
+ return `idx_${table}_${suffix}`.replace(/[^a-zA-Z0-9_]/g, "_");
540
+ }
541
+ async ensureLookupIndex(table, suffix, columnsSql) {
542
+ const markers = await getIndexMarkerStore();
543
+ const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, suffix);
544
+ if (markers.hasFreshIndexMarker(markerPath))
545
+ return;
546
+ const indexName = this.buildLookupIndexName(table, suffix);
547
+ try {
548
+ await this.query(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${table}" ${columnsSql}`);
549
+ markers.writeIndexMarker(markerPath);
550
+ } catch (e) {
551
+ if (isDuplicateIndexError(e)) {
552
+ markers.writeIndexMarker(markerPath);
553
+ return;
554
+ }
555
+ log2(`index "${indexName}" skipped: ${e.message}`);
556
+ }
557
+ }
558
+ /**
559
+ * Ensure a vector column exists on the given table.
560
+ *
561
+ * The previous implementation always issued `ALTER TABLE ADD COLUMN IF NOT
562
+ * EXISTS …` on every SessionStart. On a long-running workspace that's
563
+ * already migrated, every call returns 500 "Column already exists" — noisy
564
+ * in the log and a wasted round-trip. Worse, the very first call after the
565
+ * column is genuinely added triggers Deeplake's post-ALTER `vector::at`
566
+ * window (~30s) during which subsequent INSERTs fail; minimising the
567
+ * number of ALTER calls minimises exposure to that window.
568
+ *
569
+ * New flow:
570
+ * 1. Check the local marker file (mirrors ensureLookupIndex). If fresh,
571
+ * return — zero network calls.
572
+ * 2. SELECT 1 FROM information_schema.columns WHERE table_name = T AND
573
+ * column_name = C. Read-only, idempotent, can't tickle the post-ALTER
574
+ * bug. If the column is present → mark + return.
575
+ * 3. Only if step 2 says the column is missing, fall back to ALTER ADD
576
+ * COLUMN IF NOT EXISTS. Mark on success, also mark if Deeplake reports
577
+ * "already exists" (race: another client added it between our SELECT
578
+ * and ALTER).
579
+ *
580
+ * Marker uses the same dir / TTL as ensureLookupIndex so both schema
581
+ * caches share an opt-out (HIVEMIND_INDEX_MARKER_DIR) and a TTL knob.
582
+ */
583
+ async ensureEmbeddingColumn(table, column) {
584
+ await this.ensureColumn(table, column, "FLOAT4[]");
585
+ }
586
+ /**
587
+ * Generic marker-gated column migration. Same SELECT-then-ALTER flow as
588
+ * ensureEmbeddingColumn, parameterized by SQL type so it can patch up any
589
+ * column that was added to the schema after the table was originally
590
+ * created. Used today for `summary_embedding`, `message_embedding`, and
591
+ * the `agent` column (added 2026-04-11) — the latter has no fallback if
592
+ * a user upgraded over a pre-2026-04-11 table, so every INSERT fails
593
+ * with `column "agent" does not exist`.
594
+ */
595
+ async ensureColumn(table, column, sqlType) {
596
+ const markers = await getIndexMarkerStore();
597
+ const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, `col_${column}`);
598
+ if (markers.hasFreshIndexMarker(markerPath))
599
+ return;
600
+ const colCheck = `SELECT 1 FROM information_schema.columns WHERE table_name = '${sqlStr(table)}' AND column_name = '${sqlStr(column)}' AND table_schema = '${sqlStr(this.workspaceId)}' LIMIT 1`;
601
+ const rows = await this.query(colCheck);
602
+ if (rows.length > 0) {
603
+ markers.writeIndexMarker(markerPath);
604
+ return;
605
+ }
606
+ try {
607
+ await this.query(`ALTER TABLE "${table}" ADD COLUMN ${column} ${sqlType}`);
608
+ } catch (e) {
609
+ const msg = e instanceof Error ? e.message : String(e);
610
+ if (!/already exists/i.test(msg))
611
+ throw e;
612
+ const recheck = await this.query(colCheck);
613
+ if (recheck.length === 0)
614
+ throw e;
615
+ }
616
+ markers.writeIndexMarker(markerPath);
617
+ }
618
+ /** List all tables in the workspace (with retry). */
619
+ async listTables(forceRefresh = false) {
620
+ if (!forceRefresh && this._tablesCache)
621
+ return [...this._tablesCache];
622
+ const { tables, cacheable } = await this._fetchTables();
623
+ if (cacheable)
624
+ this._tablesCache = [...tables];
625
+ return tables;
626
+ }
627
+ async _fetchTables() {
628
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
629
+ try {
630
+ const resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables`, {
631
+ headers: {
632
+ Authorization: `Bearer ${this.token}`,
633
+ "X-Activeloop-Org-Id": this.orgId,
634
+ ...deeplakeClientHeader()
635
+ }
636
+ });
637
+ if (resp.ok) {
638
+ const data = await resp.json();
639
+ return {
640
+ tables: (data.tables ?? []).map((t) => t.table_name),
641
+ cacheable: true
642
+ };
643
+ }
644
+ if (attempt < MAX_RETRIES && RETRYABLE_CODES.has(resp.status)) {
645
+ await sleep(BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200);
646
+ continue;
647
+ }
648
+ return { tables: [], cacheable: false };
649
+ } catch {
650
+ if (attempt < MAX_RETRIES) {
651
+ await sleep(BASE_DELAY_MS * Math.pow(2, attempt));
652
+ continue;
653
+ }
654
+ return { tables: [], cacheable: false };
655
+ }
656
+ }
657
+ return { tables: [], cacheable: false };
658
+ }
659
+ /**
660
+ * Run a `CREATE TABLE` with an extra outer retry budget. The base
661
+ * `query()` already retries 3 times on fetch errors (~3.5s total), but a
662
+ * failed CREATE is permanent corruption — every subsequent SELECT against
663
+ * the missing table fails. Wrapping in an outer loop with longer backoff
664
+ * (2s, 5s, then 10s) gives us ~17s of reach across transient network
665
+ * blips before giving up. Failures still propagate; getApi() resets its
666
+ * cache on init failure (openclaw plugin) so the next call retries the
667
+ * whole init flow.
668
+ */
669
+ async createTableWithRetry(sql, label) {
670
+ const OUTER_BACKOFFS_MS = [2e3, 5e3, 1e4];
671
+ let lastErr = null;
672
+ for (let attempt = 0; attempt <= OUTER_BACKOFFS_MS.length; attempt++) {
673
+ try {
674
+ await this.query(sql);
675
+ return;
676
+ } catch (err) {
677
+ lastErr = err;
678
+ const msg = err instanceof Error ? err.message : String(err);
679
+ log2(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
680
+ if (attempt < OUTER_BACKOFFS_MS.length) {
681
+ await sleep(OUTER_BACKOFFS_MS[attempt]);
682
+ }
683
+ }
684
+ }
685
+ throw lastErr;
686
+ }
687
+ /** Create the memory table if it doesn't already exist. Migrate columns on existing tables. */
688
+ async ensureTable(name) {
689
+ const tbl = name ?? this.tableName;
690
+ const tables = await this.listTables();
691
+ if (!tables.includes(tbl)) {
692
+ log2(`table "${tbl}" not found, creating`);
693
+ await this.createTableWithRetry(`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 '', summary_embedding FLOAT4[], 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`, tbl);
694
+ log2(`table "${tbl}" created`);
695
+ if (!tables.includes(tbl))
696
+ this._tablesCache = [...tables, tbl];
697
+ }
698
+ await this.ensureEmbeddingColumn(tbl, SUMMARY_EMBEDDING_COL);
699
+ await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
700
+ }
701
+ /** Create the sessions table (uses JSONB for message since every row is a JSON event). */
702
+ async ensureSessionsTable(name) {
703
+ const tables = await this.listTables();
704
+ if (!tables.includes(name)) {
705
+ log2(`table "${name}" not found, creating`);
706
+ await this.createTableWithRetry(`CREATE TABLE IF NOT EXISTS "${name}" (id TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', filename TEXT NOT NULL DEFAULT '', message JSONB, message_embedding FLOAT4[], 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`, name);
707
+ log2(`table "${name}" created`);
708
+ if (!tables.includes(name))
709
+ this._tablesCache = [...tables, name];
710
+ }
711
+ await this.ensureEmbeddingColumn(name, MESSAGE_EMBEDDING_COL);
712
+ await this.ensureColumn(name, "agent", "TEXT NOT NULL DEFAULT ''");
713
+ await this.ensureLookupIndex(name, "path_creation_date", `("path", "creation_date")`);
714
+ }
715
+ };
716
+
717
+ // dist/src/commands/session-prune.js
718
+ import { createInterface } from "node:readline";
719
+ function parseArgs(argv) {
720
+ let before;
721
+ let sessionId;
722
+ let all = false;
723
+ let yes = false;
724
+ for (let i = 0; i < argv.length; i++) {
725
+ const arg = argv[i];
726
+ if (arg === "--before" && argv[i + 1]) {
727
+ before = argv[++i];
728
+ } else if (arg === "--session-id" && argv[i + 1]) {
729
+ sessionId = argv[++i];
730
+ } else if (arg === "--all") {
731
+ all = true;
732
+ } else if (arg === "--yes" || arg === "-y") {
733
+ yes = true;
734
+ }
735
+ }
736
+ return { before, sessionId, all, yes };
737
+ }
738
+ function confirm(message) {
739
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
740
+ return new Promise((resolve) => {
741
+ rl.question(`${message} [y/N] `, (answer) => {
742
+ rl.close();
743
+ resolve(answer.trim().toLowerCase() === "y");
744
+ });
745
+ });
746
+ }
747
+ function extractSessionId(path) {
748
+ const m = path.match(/\/sessions\/[^/]+\/[^/]+_([^.]+)\.jsonl$/);
749
+ return m ? m[1] : path.split("/").pop()?.replace(/\.jsonl$/, "") ?? path;
750
+ }
751
+ async function listSessions(api, sessionsTable, author) {
752
+ const rows = await api.query(`SELECT path, COUNT(*) as cnt, MIN(creation_date) as first_event, MAX(creation_date) as last_event, MAX(project) as project FROM "${sessionsTable}" WHERE author = '${sqlStr(author)}' GROUP BY path ORDER BY first_event DESC`);
753
+ return rows.map((r) => ({
754
+ path: String(r.path),
755
+ rowCount: Number(r.cnt),
756
+ firstEvent: String(r.first_event),
757
+ lastEvent: String(r.last_event),
758
+ project: String(r.project ?? "")
759
+ }));
760
+ }
761
+ async function deleteSessions(config, sessionPaths) {
762
+ if (sessionPaths.length === 0)
763
+ return { sessionsDeleted: 0, summariesDeleted: 0 };
764
+ const sessionsApi = new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.sessionsTableName);
765
+ const memoryApi = new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.tableName);
766
+ let sessionsDeleted = 0;
767
+ let summariesDeleted = 0;
768
+ for (const sessionPath of sessionPaths) {
769
+ await sessionsApi.query(`DELETE FROM "${config.sessionsTableName}" WHERE path = '${sqlStr(sessionPath)}'`);
770
+ sessionsDeleted++;
771
+ const sessionId = extractSessionId(sessionPath);
772
+ const summaryPath = `/summaries/${config.userName}/${sessionId}.md`;
773
+ const existing = await memoryApi.query(`SELECT path FROM "${config.tableName}" WHERE path = '${sqlStr(summaryPath)}' LIMIT 1`);
774
+ if (existing.length > 0) {
775
+ await memoryApi.query(`DELETE FROM "${config.tableName}" WHERE path = '${sqlStr(summaryPath)}'`);
776
+ summariesDeleted++;
777
+ }
778
+ }
779
+ return { sessionsDeleted, summariesDeleted };
780
+ }
781
+ async function sessionPrune(argv) {
782
+ const config = loadConfig();
783
+ if (!config) {
784
+ console.error("Not logged in. Run: deeplake login");
785
+ process.exit(1);
786
+ }
787
+ const { before, sessionId, all, yes } = parseArgs(argv);
788
+ const author = config.userName;
789
+ const sessionsApi = new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.sessionsTableName);
790
+ const sessions = await listSessions(sessionsApi, config.sessionsTableName, author);
791
+ if (sessions.length === 0) {
792
+ console.log(`No sessions found for author "${author}".`);
793
+ return;
794
+ }
795
+ let targets;
796
+ if (sessionId) {
797
+ targets = sessions.filter((s) => extractSessionId(s.path) === sessionId);
798
+ if (targets.length === 0) {
799
+ console.error(`Session not found: ${sessionId}`);
800
+ console.error(`
801
+ Your sessions:`);
802
+ for (const s of sessions.slice(0, 10)) {
803
+ console.error(` ${extractSessionId(s.path)} ${s.firstEvent.slice(0, 10)} ${s.project}`);
804
+ }
805
+ process.exit(1);
806
+ }
807
+ } else if (before) {
808
+ const cutoff = new Date(before);
809
+ if (isNaN(cutoff.getTime())) {
810
+ console.error(`Invalid date: ${before}`);
811
+ process.exit(1);
812
+ }
813
+ targets = sessions.filter((s) => new Date(s.lastEvent) < cutoff);
814
+ } else if (all) {
815
+ targets = sessions;
816
+ } else {
817
+ console.log(`Sessions for "${author}" (${sessions.length} total):
818
+ `);
819
+ console.log(" Session ID".padEnd(42) + "Date".padEnd(14) + "Events".padEnd(10) + "Project");
820
+ console.log(" " + "\u2500".repeat(80));
821
+ for (const s of sessions) {
822
+ const id = extractSessionId(s.path);
823
+ const date = s.firstEvent.slice(0, 10);
824
+ console.log(` ${id.padEnd(40)}${date.padEnd(14)}${String(s.rowCount).padEnd(10)}${s.project}`);
825
+ }
826
+ console.log(`
827
+ To delete, use: --all, --before <date>, or --session-id <id>`);
828
+ return;
829
+ }
830
+ if (targets.length === 0) {
831
+ console.log("No sessions match the given criteria.");
832
+ return;
833
+ }
834
+ console.log(`Will delete ${targets.length} session(s) for "${author}":
835
+ `);
836
+ for (const s of targets) {
837
+ const id = extractSessionId(s.path);
838
+ console.log(` ${id} ${s.firstEvent.slice(0, 10)} ${s.rowCount} events ${s.project}`);
839
+ }
840
+ console.log();
841
+ if (!yes) {
842
+ const ok = await confirm("Proceed with deletion?");
843
+ if (!ok) {
844
+ console.log("Aborted.");
845
+ return;
846
+ }
847
+ }
848
+ const { sessionsDeleted, summariesDeleted } = await deleteSessions(config, targets.map((t) => t.path));
849
+ console.log(`Deleted ${sessionsDeleted} session(s) and ${summariesDeleted} summary file(s).`);
850
+ }
851
+
852
+ // dist/src/commands/auth-login.js
853
+ async function runAuthCommand(args) {
854
+ const cmd = args[0] ?? "whoami";
855
+ const creds = loadCredentials();
856
+ const apiUrl = creds?.apiUrl ?? "https://api.deeplake.ai";
857
+ switch (cmd) {
858
+ case "login": {
859
+ await login(apiUrl);
860
+ break;
861
+ }
862
+ case "whoami": {
863
+ if (!creds) {
864
+ console.log("Not logged in. Run: node auth-login.js login");
865
+ break;
866
+ }
867
+ console.log(`User org: ${creds.orgName ?? creds.orgId}`);
868
+ console.log(`Workspace: ${creds.workspaceId ?? "default"}`);
869
+ console.log(`API: ${creds.apiUrl ?? "https://api.deeplake.ai"}`);
870
+ break;
871
+ }
872
+ case "org": {
873
+ if (!creds) {
874
+ console.log("Not logged in.");
875
+ process.exit(1);
876
+ }
877
+ const sub = args[1];
878
+ if (sub === "list") {
879
+ const orgs = await listOrgs(creds.token, apiUrl);
880
+ orgs.forEach((o) => console.log(`${o.id} ${o.name}`));
881
+ } else if (sub === "switch") {
882
+ const target = args[2];
883
+ if (!target) {
884
+ console.log("Usage: org switch <org-name-or-id>");
885
+ process.exit(1);
886
+ }
887
+ const orgs = await listOrgs(creds.token, apiUrl);
888
+ const match = orgs.find((o) => o.id === target || o.name.toLowerCase() === target.toLowerCase());
889
+ if (!match) {
890
+ console.log(`Org not found: ${target}`);
891
+ process.exit(1);
892
+ }
893
+ await switchOrg(match.id, match.name);
894
+ console.log(`Switched to org: ${match.name}`);
895
+ } else {
896
+ console.log("Usage: org list | org switch <name-or-id>");
897
+ }
898
+ break;
899
+ }
900
+ case "workspaces": {
901
+ if (!creds) {
902
+ console.log("Not logged in.");
903
+ process.exit(1);
904
+ }
905
+ const ws = await listWorkspaces(creds.token, apiUrl, creds.orgId);
906
+ ws.forEach((w) => console.log(`${w.id} ${w.name}`));
907
+ break;
908
+ }
909
+ case "workspace": {
910
+ if (!creds) {
911
+ console.log("Not logged in.");
912
+ process.exit(1);
913
+ }
914
+ const wsId = args[1];
915
+ if (!wsId) {
916
+ console.log("Usage: workspace <id>");
917
+ process.exit(1);
918
+ }
919
+ await switchWorkspace(wsId);
920
+ console.log(`Switched to workspace: ${wsId}`);
921
+ break;
922
+ }
923
+ case "invite": {
924
+ if (!creds) {
925
+ console.log("Not logged in.");
926
+ process.exit(1);
927
+ }
928
+ const email = args[1];
929
+ const mode = args[2]?.toUpperCase() ?? "WRITE";
930
+ if (!email) {
931
+ console.log("Usage: invite <email> [ADMIN|WRITE|READ]");
932
+ process.exit(1);
933
+ }
934
+ await inviteMember(email, mode, creds.token, creds.orgId, apiUrl);
935
+ console.log(`Invited ${email} with ${mode} access`);
936
+ break;
937
+ }
938
+ case "members": {
939
+ if (!creds) {
940
+ console.log("Not logged in.");
941
+ process.exit(1);
942
+ }
943
+ const members = await listMembers(creds.token, creds.orgId, apiUrl);
944
+ members.forEach((m) => console.log(`${m.role.padEnd(8)} ${m.email ?? m.name}`));
945
+ break;
946
+ }
947
+ case "remove": {
948
+ if (!creds) {
949
+ console.log("Not logged in.");
950
+ process.exit(1);
951
+ }
952
+ const userId = args[1];
953
+ if (!userId) {
954
+ console.log("Usage: remove <user-id>");
955
+ process.exit(1);
956
+ }
957
+ await removeMember(userId, creds.token, creds.orgId, apiUrl);
958
+ console.log(`Removed user ${userId}`);
959
+ break;
960
+ }
961
+ case "sessions": {
962
+ const sub = args[1];
963
+ if (sub === "prune") {
964
+ await sessionPrune(args.slice(2));
965
+ } else {
966
+ console.log("Usage: sessions prune [--all | --before <date> | --session-id <id>] [--yes]");
967
+ }
968
+ break;
969
+ }
970
+ case "autoupdate": {
971
+ if (!creds) {
972
+ console.log("Not logged in.");
973
+ process.exit(1);
974
+ }
975
+ const val = args[1]?.toLowerCase();
976
+ if (val === "on" || val === "true") {
977
+ saveCredentials({ ...creds, autoupdate: true });
978
+ console.log("Autoupdate enabled. Plugin will update automatically on session start.");
979
+ } else if (val === "off" || val === "false") {
980
+ saveCredentials({ ...creds, autoupdate: false });
981
+ console.log("Autoupdate disabled. You'll see a notice when updates are available.");
982
+ } else {
983
+ const current = creds.autoupdate !== false ? "on" : "off";
984
+ console.log(`Autoupdate is currently: ${current}`);
985
+ console.log("Usage: autoupdate [on|off]");
986
+ }
987
+ break;
988
+ }
989
+ case "logout": {
990
+ if (deleteCredentials()) {
991
+ console.log("Logged out. Credentials removed.");
992
+ } else {
993
+ console.log("Not logged in.");
994
+ }
995
+ break;
996
+ }
997
+ default:
998
+ console.log("Commands: login, logout, whoami, org list, org switch, workspaces, workspace, sessions prune, invite, members, remove, autoupdate");
999
+ }
1000
+ }
1001
+ if (process.argv[1] && process.argv[1].endsWith("auth-login.js")) {
1002
+ runAuthCommand(process.argv.slice(2)).catch((e) => {
1003
+ console.error(e.message);
1004
+ process.exit(1);
1005
+ });
1006
+ }
1007
+ export {
1008
+ runAuthCommand
1009
+ };