@memrosetta/cli 0.4.8 → 0.5.0

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,542 @@
1
+ import {
2
+ deterministicOpId,
3
+ openCliSyncContext
4
+ } from "./chunk-2MO65JLK.js";
5
+ import {
6
+ hasFlag,
7
+ optionalOption,
8
+ requireOption
9
+ } from "./chunk-SYPVELIW.js";
10
+ import {
11
+ output,
12
+ outputError
13
+ } from "./chunk-ET6TNQOJ.js";
14
+ import {
15
+ getConfig,
16
+ getDefaultDbPath,
17
+ writeConfig
18
+ } from "./chunk-SEPYQK3J.js";
19
+
20
+ // src/commands/sync.ts
21
+ import { randomUUID } from "crypto";
22
+ import { readFileSync } from "fs";
23
+ import { userInfo, platform } from "os";
24
+ import { createInterface } from "readline";
25
+ var ENV_API_KEY = "MEMROSETTA_SYNC_API_KEY";
26
+ var KEY_SOURCE_HINT = [
27
+ "API key required. Use exactly one of:",
28
+ " --key <value> (direct, visible in history)",
29
+ " --key-stdin (pipe from stdin)",
30
+ " --key-file <path> (read from file)",
31
+ ` ${ENV_API_KEY}=<value> (environment variable)`,
32
+ "",
33
+ "On POSIX TTYs an interactive hidden prompt is also available.",
34
+ "See: memrosetta sync enable --help"
35
+ ].join("\n");
36
+ function parseSubcommand(args) {
37
+ const first = args[0];
38
+ if (!first || first.startsWith("--")) return null;
39
+ if (first === "enable" || first === "disable" || first === "status" || first === "now" || first === "device-id" || first === "backfill") {
40
+ return first;
41
+ }
42
+ return null;
43
+ }
44
+ var CONTROL_CHAR_REGEX = /[\x00-\x1F\x7F]/;
45
+ function validateApiKey(key, sourceLabel) {
46
+ const trimmed = key.trim();
47
+ if (trimmed.length === 0) {
48
+ throw new Error(`API key from ${sourceLabel} is empty.`);
49
+ }
50
+ if (CONTROL_CHAR_REGEX.test(trimmed)) {
51
+ throw new Error(
52
+ `API key from ${sourceLabel} contains control characters. Try --key-file or MEMROSETTA_SYNC_API_KEY instead.`
53
+ );
54
+ }
55
+ return trimmed;
56
+ }
57
+ function readKeyFile(path) {
58
+ try {
59
+ return readFileSync(path, "utf-8");
60
+ } catch (err) {
61
+ const msg = err instanceof Error ? err.message : String(err);
62
+ throw new Error(`Could not read --key-file '${path}': ${msg}`);
63
+ }
64
+ }
65
+ async function resolveApiKey(args) {
66
+ const directKey = optionalOption(args, "--key");
67
+ const keyFile = optionalOption(args, "--key-file");
68
+ const useStdin = hasFlag(args, "--key-stdin");
69
+ const explicitCount = [directKey !== void 0, keyFile !== void 0, useStdin].filter(Boolean).length;
70
+ if (explicitCount > 1) {
71
+ throw new Error(
72
+ "Specify only one of --key, --key-stdin, --key-file."
73
+ );
74
+ }
75
+ if (directKey !== void 0) {
76
+ return validateApiKey(directKey, "--key");
77
+ }
78
+ if (useStdin) {
79
+ const raw = await readStdinKey();
80
+ if (!raw) {
81
+ throw new Error(
82
+ "--key-stdin produced no input. On Windows PowerShell, prefer --key-file or MEMROSETTA_SYNC_API_KEY."
83
+ );
84
+ }
85
+ return validateApiKey(raw, "--key-stdin");
86
+ }
87
+ if (keyFile !== void 0) {
88
+ return validateApiKey(readKeyFile(keyFile), `--key-file ${keyFile}`);
89
+ }
90
+ const envKey = process.env[ENV_API_KEY];
91
+ if (envKey !== void 0 && envKey.length > 0) {
92
+ return validateApiKey(envKey, ENV_API_KEY);
93
+ }
94
+ if (platform() !== "win32" && process.stdin.isTTY) {
95
+ const raw = await readHiddenInput("API key: ");
96
+ return validateApiKey(raw, "hidden prompt");
97
+ }
98
+ throw new Error(KEY_SOURCE_HINT);
99
+ }
100
+ async function readHiddenInput(prompt) {
101
+ const stdin = process.stdin;
102
+ const stdout = process.stdout;
103
+ if (!stdin.isTTY) {
104
+ throw new Error("Interactive input requires a TTY. Use --key-stdin to pipe the key instead.");
105
+ }
106
+ stdout.write(prompt);
107
+ const originalWrite = stdout.write.bind(stdout);
108
+ let muted = true;
109
+ stdout.write = (chunk, ...rest) => {
110
+ if (!muted) {
111
+ return originalWrite(chunk, ...rest);
112
+ }
113
+ const str = typeof chunk === "string" ? chunk : chunk?.toString?.("utf-8") ?? "";
114
+ if (str === "\n" || str === "\r\n" || str === "\r") {
115
+ return originalWrite(chunk, ...rest);
116
+ }
117
+ return true;
118
+ };
119
+ const rl = createInterface({
120
+ input: stdin,
121
+ output: stdout,
122
+ terminal: true
123
+ });
124
+ try {
125
+ const answer = await new Promise((resolve, reject) => {
126
+ rl.once("close", () => {
127
+ reject(new Error("Aborted"));
128
+ });
129
+ rl.question("", (value) => {
130
+ resolve(value);
131
+ });
132
+ });
133
+ return answer;
134
+ } finally {
135
+ muted = false;
136
+ stdout.write = originalWrite;
137
+ rl.close();
138
+ }
139
+ }
140
+ async function readStdinKey() {
141
+ const chunks = [];
142
+ for await (const chunk of process.stdin) {
143
+ chunks.push(chunk);
144
+ }
145
+ return Buffer.concat(chunks).toString("utf-8").trim();
146
+ }
147
+ async function testConnection(serverUrl, apiKey) {
148
+ const url = `${serverUrl.replace(/\/$/, "")}/sync/health`;
149
+ try {
150
+ const res = await fetch(url, {
151
+ headers: { Authorization: `Bearer ${apiKey}` }
152
+ });
153
+ if (!res.ok) {
154
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
155
+ }
156
+ } catch (err) {
157
+ const msg = err instanceof Error ? err.message : String(err);
158
+ throw new Error(`Sync server health check failed: ${msg}`);
159
+ }
160
+ }
161
+ async function withSyncClient(dbPath, config, fn) {
162
+ const Database = (await import("better-sqlite3")).default;
163
+ const { SyncClient, ensureSyncSchema } = await import("@memrosetta/sync-client");
164
+ if (!config.syncServerUrl || !config.syncApiKey || !config.syncDeviceId) {
165
+ throw new Error("Sync is not configured. Run: memrosetta sync enable --server <url>");
166
+ }
167
+ if (CONTROL_CHAR_REGEX.test(config.syncApiKey)) {
168
+ throw new Error(
169
+ `Stored API key is invalid (contains control characters from a previous terminal input). Re-run with one of:
170
+ memrosetta sync enable --server ${config.syncServerUrl} --key <api-key>
171
+ memrosetta sync enable --server ${config.syncServerUrl} --key-file path/to/key
172
+ $env:${ENV_API_KEY}='<api-key>'; memrosetta sync enable --server ${config.syncServerUrl}`
173
+ );
174
+ }
175
+ const db = new Database(dbPath);
176
+ try {
177
+ ensureSyncSchema(db);
178
+ const client = new SyncClient(db, {
179
+ serverUrl: config.syncServerUrl,
180
+ apiKey: config.syncApiKey,
181
+ deviceId: config.syncDeviceId,
182
+ userId: config.syncUserId ?? userInfo().username
183
+ });
184
+ return await fn(client, db);
185
+ } finally {
186
+ db.close();
187
+ }
188
+ }
189
+ async function runEnable(options) {
190
+ const { args, format } = options;
191
+ let serverUrl;
192
+ try {
193
+ serverUrl = requireOption(args, "--server", "server URL");
194
+ } catch (err) {
195
+ outputError(err instanceof Error ? err.message : String(err), format);
196
+ process.exitCode = 1;
197
+ return;
198
+ }
199
+ let apiKey;
200
+ try {
201
+ apiKey = await resolveApiKey(args);
202
+ } catch (err) {
203
+ outputError(err instanceof Error ? err.message : String(err), format);
204
+ process.exitCode = 1;
205
+ return;
206
+ }
207
+ const skipTest = hasFlag(args, "--no-test");
208
+ if (!skipTest) {
209
+ try {
210
+ await testConnection(serverUrl, apiKey);
211
+ } catch (err) {
212
+ outputError(
213
+ `${err instanceof Error ? err.message : String(err)}
214
+ Use --no-test to skip the health check.`,
215
+ format
216
+ );
217
+ process.exitCode = 1;
218
+ return;
219
+ }
220
+ }
221
+ const existing = getConfig();
222
+ const deviceId = existing.syncDeviceId ?? `device-${randomUUID().slice(0, 8)}`;
223
+ const userOverride = optionalOption(args, "--user");
224
+ const syncUserId = userOverride ?? existing.syncUserId ?? userInfo().username;
225
+ writeConfig({
226
+ ...existing,
227
+ syncEnabled: true,
228
+ syncServerUrl: serverUrl,
229
+ syncApiKey: apiKey,
230
+ syncDeviceId: deviceId,
231
+ syncUserId
232
+ });
233
+ if (format === "text") {
234
+ process.stdout.write("Sync enabled.\n");
235
+ process.stdout.write(` Server: ${serverUrl}
236
+ `);
237
+ process.stdout.write(` UserId: ${syncUserId}
238
+ `);
239
+ process.stdout.write(` DeviceId: ${deviceId}
240
+ `);
241
+ if (skipTest) {
242
+ process.stdout.write(" (health check skipped)\n");
243
+ }
244
+ return;
245
+ }
246
+ output(
247
+ { enabled: true, serverUrl, userId: syncUserId, deviceId, healthCheckSkipped: skipTest },
248
+ format
249
+ );
250
+ }
251
+ function runDisable(options) {
252
+ const { format } = options;
253
+ const existing = getConfig();
254
+ writeConfig({
255
+ ...existing,
256
+ syncEnabled: false
257
+ });
258
+ if (format === "text") {
259
+ process.stdout.write("Sync disabled. (server URL and API key preserved for re-enable)\n");
260
+ return;
261
+ }
262
+ output({ enabled: false }, format);
263
+ }
264
+ async function runStatus(options) {
265
+ const { format, db } = options;
266
+ const config = getConfig();
267
+ const dbPath = db ?? config.dbPath ?? getDefaultDbPath();
268
+ if (!config.syncEnabled) {
269
+ if (format === "text") {
270
+ process.stdout.write("Sync: disabled\n");
271
+ if (config.syncServerUrl) {
272
+ process.stdout.write(` Server: ${config.syncServerUrl}
273
+ `);
274
+ }
275
+ if (config.syncUserId) {
276
+ process.stdout.write(` UserId: ${config.syncUserId}
277
+ `);
278
+ }
279
+ if (config.syncDeviceId) {
280
+ process.stdout.write(` DeviceId: ${config.syncDeviceId}
281
+ `);
282
+ }
283
+ return;
284
+ }
285
+ output(
286
+ {
287
+ enabled: false,
288
+ serverUrl: config.syncServerUrl ?? null,
289
+ userId: config.syncUserId ?? null,
290
+ deviceId: config.syncDeviceId ?? null
291
+ },
292
+ format
293
+ );
294
+ return;
295
+ }
296
+ try {
297
+ const status = await withSyncClient(dbPath, config, async (client) => client.getStatus());
298
+ if (format === "text") {
299
+ process.stdout.write("Sync: enabled\n");
300
+ process.stdout.write(` Server: ${status.serverUrl}
301
+ `);
302
+ process.stdout.write(` UserId: ${status.userId}
303
+ `);
304
+ process.stdout.write(` DeviceId: ${status.deviceId}
305
+ `);
306
+ process.stdout.write(` Pending ops: ${status.pendingOps}
307
+ `);
308
+ process.stdout.write(` Current cursor: ${status.cursor}
309
+ `);
310
+ process.stdout.write(
311
+ ` Last push: ${status.lastPush.successAt ?? "never"}` + (status.lastPush.attemptAt && status.lastPush.attemptAt !== status.lastPush.successAt ? ` (last attempt: ${status.lastPush.attemptAt})` : "") + "\n"
312
+ );
313
+ process.stdout.write(
314
+ ` Last pull: ${status.lastPull.successAt ?? "never"}` + (status.lastPull.attemptAt && status.lastPull.attemptAt !== status.lastPull.successAt ? ` (last attempt: ${status.lastPull.attemptAt})` : "") + "\n"
315
+ );
316
+ return;
317
+ }
318
+ output(status, format);
319
+ } catch (err) {
320
+ outputError(err instanceof Error ? err.message : String(err), format);
321
+ process.exitCode = 1;
322
+ }
323
+ }
324
+ async function runNow(options) {
325
+ const { args, format, db } = options;
326
+ const config = getConfig();
327
+ const dbPath = db ?? config.dbPath ?? getDefaultDbPath();
328
+ if (!config.syncEnabled) {
329
+ outputError("Sync is disabled. Run: memrosetta sync enable --server <url>", format);
330
+ process.exitCode = 1;
331
+ return;
332
+ }
333
+ const pushOnly = hasFlag(args, "--push-only");
334
+ const pullOnly = hasFlag(args, "--pull-only");
335
+ try {
336
+ const result = await withSyncClient(dbPath, config, async (client) => {
337
+ let pushed = 0;
338
+ let pulled = 0;
339
+ if (!pullOnly) {
340
+ const pushResult = await client.push();
341
+ pushed = pushResult.pushed;
342
+ }
343
+ if (!pushOnly) {
344
+ pulled = await client.pull();
345
+ }
346
+ return { pushed, pulled };
347
+ });
348
+ if (format === "text") {
349
+ process.stdout.write(`Sync complete. pushed=${result.pushed} pulled=${result.pulled}
350
+ `);
351
+ return;
352
+ }
353
+ output(result, format);
354
+ } catch (err) {
355
+ outputError(err instanceof Error ? err.message : String(err), format);
356
+ process.exitCode = 1;
357
+ }
358
+ }
359
+ function runDeviceId(options) {
360
+ const { format } = options;
361
+ const config = getConfig();
362
+ if (!config.syncDeviceId) {
363
+ outputError("No deviceId set. Run: memrosetta sync enable --server <url>", format);
364
+ process.exitCode = 1;
365
+ return;
366
+ }
367
+ if (format === "text") {
368
+ process.stdout.write(`${config.syncDeviceId}
369
+ `);
370
+ return;
371
+ }
372
+ output({ deviceId: config.syncDeviceId }, format);
373
+ }
374
+ async function runBackfill(options) {
375
+ const { args, format, db } = options;
376
+ const config = getConfig();
377
+ const dbPath = db ?? config.dbPath ?? getDefaultDbPath();
378
+ if (!config.syncEnabled) {
379
+ outputError("Sync is disabled. Run: memrosetta sync enable --server <url>", format);
380
+ process.exitCode = 1;
381
+ return;
382
+ }
383
+ const dryRun = hasFlag(args, "--dry-run");
384
+ const userFilter = optionalOption(args, "--user");
385
+ const namespaceFilter = optionalOption(args, "--namespace");
386
+ const includeRelations = !hasFlag(args, "--memories-only");
387
+ const sync = await openCliSyncContext(dbPath);
388
+ if (!sync.enabled) {
389
+ outputError("Sync is not fully configured. Run: memrosetta sync enable", format);
390
+ process.exitCode = 1;
391
+ return;
392
+ }
393
+ try {
394
+ const { default: Database } = await import("better-sqlite3");
395
+ const readDb = new Database(dbPath, { readonly: true });
396
+ try {
397
+ const params = [];
398
+ let where = "1=1";
399
+ if (userFilter) {
400
+ where += " AND user_id = ?";
401
+ params.push(userFilter);
402
+ }
403
+ if (namespaceFilter) {
404
+ where += " AND namespace = ?";
405
+ params.push(namespaceFilter);
406
+ }
407
+ const memRows = readDb.prepare(
408
+ `SELECT memory_id, user_id, namespace, memory_type, content, raw_text,
409
+ document_date, source_id, confidence, salience, keywords,
410
+ event_date_start, event_date_end, invalidated_at, learned_at
411
+ FROM memories
412
+ WHERE ${where}`
413
+ ).all(...params);
414
+ let memoriesQueued = 0;
415
+ let relationsQueued = 0;
416
+ const memoryIdSet = /* @__PURE__ */ new Set();
417
+ for (const row of memRows) {
418
+ memoryIdSet.add(row.memory_id);
419
+ if (dryRun) continue;
420
+ const op = {
421
+ opId: deterministicOpId("memory_created", row.memory_id),
422
+ opType: "memory_created",
423
+ deviceId: sync.deviceId,
424
+ userId: sync.userId,
425
+ createdAt: row.learned_at,
426
+ payload: {
427
+ memoryId: row.memory_id,
428
+ userId: row.user_id,
429
+ namespace: row.namespace ?? void 0,
430
+ memoryType: row.memory_type,
431
+ content: row.content,
432
+ rawText: row.raw_text ?? void 0,
433
+ documentDate: row.document_date ?? void 0,
434
+ sourceId: row.source_id ?? void 0,
435
+ confidence: row.confidence,
436
+ salience: row.salience,
437
+ // core stores keywords as a space-joined string, not JSON.
438
+ keywords: row.keywords ? row.keywords.split(" ").filter((k) => k.length > 0) : void 0,
439
+ eventDateStart: row.event_date_start ?? void 0,
440
+ eventDateEnd: row.event_date_end ?? void 0,
441
+ invalidatedAt: row.invalidated_at ?? void 0,
442
+ learnedAt: row.learned_at
443
+ }
444
+ };
445
+ sync.enqueue(op);
446
+ memoriesQueued++;
447
+ }
448
+ if (dryRun) memoriesQueued = memRows.length;
449
+ if (includeRelations) {
450
+ const relRows = readDb.prepare(
451
+ "SELECT src_memory_id, dst_memory_id, relation_type, created_at, reason FROM memory_relations"
452
+ ).all();
453
+ for (const row of relRows) {
454
+ if (!memoryIdSet.has(row.src_memory_id) || !memoryIdSet.has(row.dst_memory_id)) {
455
+ continue;
456
+ }
457
+ if (!dryRun) {
458
+ const relKey = `${row.src_memory_id}|${row.dst_memory_id}|${row.relation_type}`;
459
+ const op = {
460
+ opId: deterministicOpId("relation_created", relKey),
461
+ opType: "relation_created",
462
+ deviceId: sync.deviceId,
463
+ userId: sync.userId,
464
+ createdAt: row.created_at,
465
+ payload: {
466
+ srcMemoryId: row.src_memory_id,
467
+ dstMemoryId: row.dst_memory_id,
468
+ relationType: row.relation_type,
469
+ createdAt: row.created_at,
470
+ reason: row.reason ?? void 0
471
+ }
472
+ };
473
+ sync.enqueue(op);
474
+ }
475
+ relationsQueued++;
476
+ }
477
+ }
478
+ const result = {
479
+ memoriesQueued,
480
+ relationsQueued,
481
+ dryRun,
482
+ userFilter: userFilter ?? null,
483
+ namespaceFilter: namespaceFilter ?? null
484
+ };
485
+ if (format === "text") {
486
+ process.stdout.write(
487
+ `${dryRun ? "Dry run: would enqueue" : "Enqueued"} ${memoriesQueued} memories` + (includeRelations ? ` and ${relationsQueued} relations` : "") + ".\n"
488
+ );
489
+ if (userFilter || namespaceFilter) {
490
+ process.stdout.write(
491
+ ` Filters: user=${userFilter ?? "*"} namespace=${namespaceFilter ?? "*"}
492
+ `
493
+ );
494
+ }
495
+ if (!dryRun) {
496
+ process.stdout.write("Run `memrosetta sync now` to push to the server.\n");
497
+ }
498
+ return;
499
+ }
500
+ output(result, format);
501
+ } finally {
502
+ readDb.close();
503
+ }
504
+ } finally {
505
+ sync.close();
506
+ }
507
+ }
508
+ async function run(options) {
509
+ const sub = parseSubcommand(options.args);
510
+ if (!sub) {
511
+ outputError(
512
+ "Usage: memrosetta sync <enable|disable|status|now|device-id|backfill>\n\n enable --server <url> [--key <key> | --key-stdin | --key-file <p>]\n disable\n status\n now [--push-only | --pull-only]\n device-id\n backfill [--user <id>] [--namespace <ns>] [--memories-only] [--dry-run]\n",
513
+ options.format
514
+ );
515
+ process.exitCode = 1;
516
+ return;
517
+ }
518
+ const rest = { ...options, args: options.args.slice(1) };
519
+ switch (sub) {
520
+ case "enable":
521
+ await runEnable(rest);
522
+ return;
523
+ case "disable":
524
+ runDisable(rest);
525
+ return;
526
+ case "status":
527
+ await runStatus(rest);
528
+ return;
529
+ case "now":
530
+ await runNow(rest);
531
+ return;
532
+ case "device-id":
533
+ runDeviceId(rest);
534
+ return;
535
+ case "backfill":
536
+ await runBackfill(rest);
537
+ return;
538
+ }
539
+ }
540
+ export {
541
+ run
542
+ };
@@ -0,0 +1,53 @@
1
+ import {
2
+ optionalOption
3
+ } from "./chunk-SYPVELIW.js";
4
+ import {
5
+ getEngine
6
+ } from "./chunk-VAVUPQZA.js";
7
+ import {
8
+ output
9
+ } from "./chunk-ET6TNQOJ.js";
10
+ import {
11
+ getDefaultUserId
12
+ } from "./chunk-SEPYQK3J.js";
13
+
14
+ // src/commands/working-memory.ts
15
+ async function run(options) {
16
+ const { args, format, db, noEmbeddings } = options;
17
+ const userId = optionalOption(args, "--user") ?? getDefaultUserId();
18
+ const maxTokensStr = optionalOption(args, "--max-tokens");
19
+ const maxTokens = maxTokensStr ? parseInt(maxTokensStr, 10) : void 0;
20
+ if (maxTokens !== void 0 && (isNaN(maxTokens) || maxTokens <= 0)) {
21
+ throw new Error("--max-tokens must be a positive integer");
22
+ }
23
+ const engine = await getEngine({ db, noEmbeddings });
24
+ const memories = await engine.workingMemory(userId, maxTokens);
25
+ if (format === "text") {
26
+ if (memories.length === 0) {
27
+ process.stdout.write("No working memory found.\n");
28
+ return;
29
+ }
30
+ const totalTokens = memories.reduce(
31
+ (sum, m) => sum + Math.ceil(m.content.length / 4),
32
+ 0
33
+ );
34
+ for (const memory of memories) {
35
+ const tier = memory.tier.toUpperCase();
36
+ const score = memory.activationScore.toFixed(2);
37
+ process.stdout.write(
38
+ `[${tier}|${score}] ${memory.content} (${memory.memoryType})
39
+ `
40
+ );
41
+ }
42
+ process.stdout.write(
43
+ `
44
+ ${memories.length} memories, ~${totalTokens} tokens
45
+ `
46
+ );
47
+ return;
48
+ }
49
+ output({ userId, maxTokens: maxTokens ?? 3e3, memories }, format);
50
+ }
51
+ export {
52
+ run
53
+ };
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@memrosetta/cli",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {
7
7
  "memrosetta": "./dist/index.js",
8
8
  "memrosetta-on-stop": "./dist/hooks/on-stop.js",
9
- "memrosetta-on-prompt": "./dist/hooks/on-prompt.js"
9
+ "memrosetta-on-prompt": "./dist/hooks/on-prompt.js",
10
+ "memrosetta-enforce-claude-code": "./dist/hooks/enforce-claude-code.js"
10
11
  },
11
12
  "files": [
12
13
  "dist",
@@ -15,10 +16,10 @@
15
16
  ],
16
17
  "dependencies": {
17
18
  "better-sqlite3": "^11.0.0",
18
- "@memrosetta/types": "0.4.0",
19
+ "@memrosetta/core": "0.4.0",
19
20
  "@memrosetta/embeddings": "0.3.0",
20
- "@memrosetta/sync-client": "0.1.4",
21
- "@memrosetta/core": "0.4.0"
21
+ "@memrosetta/types": "0.4.0",
22
+ "@memrosetta/sync-client": "0.1.5"
22
23
  },
23
24
  "optionalDependencies": {
24
25
  "@memrosetta/llm": "0.3.0"
@@ -30,7 +31,7 @@
30
31
  "vitest": "^2.0.0"
31
32
  },
32
33
  "scripts": {
33
- "build": "tsup src/index.ts src/hooks/on-stop.ts src/hooks/on-prompt.ts --format esm --external better-sqlite3 --external @memrosetta/core --external @memrosetta/embeddings --external @memrosetta/sync-client --external @memrosetta/types --external @memrosetta/llm --external @huggingface/transformers --external sqlite-vec --external onnxruntime-node",
34
+ "build": "tsup src/index.ts src/hooks/on-stop.ts src/hooks/on-prompt.ts src/hooks/enforce-claude-code.ts --format esm --external better-sqlite3 --external @memrosetta/core --external @memrosetta/embeddings --external @memrosetta/sync-client --external @memrosetta/types --external @memrosetta/llm --external @memrosetta/extractor --external @huggingface/transformers --external sqlite-vec --external onnxruntime-node",
34
35
  "start": "node dist/index.js",
35
36
  "test": "vitest run",
36
37
  "test:watch": "vitest",