@memories.sh/cli 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,492 +1,64 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ensureEmbeddingsSchema,
4
+ getEmbedding,
5
+ storeEmbedding
6
+ } from "./chunk-4GNYCHD4.js";
7
+ import {
8
+ addMemory,
9
+ bulkForgetByIds,
10
+ findMemoriesToForget,
11
+ forgetMemory,
12
+ getContext,
13
+ getGitRoot,
14
+ getMemoryById,
15
+ getProjectId,
16
+ getRules,
17
+ listMemories,
18
+ searchMemories,
19
+ updateMemory
20
+ } from "./chunk-JMN3U7AI.js";
21
+ import {
22
+ getConfigDir,
23
+ getDb,
24
+ readSyncConfig,
25
+ resetDb,
26
+ saveSyncConfig,
27
+ syncDb
28
+ } from "./chunk-BDPB4ABQ.js";
2
29
 
3
30
  // src/index.ts
4
- import { Command as Command22 } from "commander";
31
+ import { Command as Command28 } from "commander";
5
32
 
6
33
  // src/commands/init.ts
7
34
  import { Command } from "commander";
8
35
  import chalk2 from "chalk";
9
36
 
10
- // src/lib/db.ts
11
- import { createClient } from "@libsql/client";
12
- import { mkdir, readFile, writeFile } from "fs/promises";
37
+ // src/lib/auth.ts
38
+ import { readFile, writeFile, mkdir, unlink } from "fs/promises";
13
39
  import { existsSync } from "fs";
14
40
  import { join } from "path";
15
41
  import { homedir } from "os";
16
- function resolveConfigDir() {
17
- return process.env.MEMORIES_DATA_DIR ?? join(homedir(), ".config", "memories");
18
- }
19
- function getConfigDir() {
20
- return resolveConfigDir();
21
- }
22
- function getDbPath() {
23
- return join(resolveConfigDir(), "local.db");
24
- }
25
- function getSyncConfigPath() {
26
- return join(resolveConfigDir(), "sync.json");
27
- }
28
- var client;
29
- async function getDb() {
30
- if (client) return client;
31
- const configDir = resolveConfigDir();
32
- const dbPath = getDbPath();
33
- await mkdir(configDir, { recursive: true });
34
- const sync = await readSyncConfig();
35
- if (sync) {
36
- client = createClient({
37
- url: `file:${dbPath}`,
38
- syncUrl: sync.syncUrl,
39
- authToken: sync.syncToken
40
- });
41
- await runMigrations(client);
42
- await client.sync();
43
- } else {
44
- client = createClient({ url: `file:${dbPath}` });
45
- await runMigrations(client);
46
- }
47
- return client;
48
- }
49
- function resetDb() {
50
- client?.close();
51
- client = void 0;
52
- }
53
- async function syncDb() {
54
- const db = await getDb();
55
- await db.sync();
56
- }
57
- async function saveSyncConfig(config) {
58
- const configDir = resolveConfigDir();
59
- await mkdir(configDir, { recursive: true });
60
- await writeFile(getSyncConfigPath(), JSON.stringify(config, null, 2), "utf-8");
61
- }
62
- async function readSyncConfig() {
63
- const syncPath = getSyncConfigPath();
64
- if (!existsSync(syncPath)) return null;
65
- const raw = await readFile(syncPath, "utf-8");
66
- return JSON.parse(raw);
67
- }
68
- async function runMigrations(db) {
69
- await db.execute(
70
- `CREATE TABLE IF NOT EXISTS memories (
71
- id TEXT PRIMARY KEY,
72
- content TEXT NOT NULL,
73
- tags TEXT,
74
- scope TEXT NOT NULL DEFAULT 'global',
75
- project_id TEXT,
76
- type TEXT NOT NULL DEFAULT 'note',
77
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
78
- updated_at TEXT NOT NULL DEFAULT (datetime('now')),
79
- deleted_at TEXT
80
- )`
81
- );
82
- try {
83
- await db.execute(`ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'`);
84
- } catch {
85
- }
86
- try {
87
- await db.execute(`ALTER TABLE memories ADD COLUMN project_id TEXT`);
88
- } catch {
89
- }
90
- try {
91
- await db.execute(`ALTER TABLE memories ADD COLUMN type TEXT NOT NULL DEFAULT 'note'`);
92
- } catch {
93
- }
94
- await db.execute(
95
- `CREATE TABLE IF NOT EXISTS configs (
96
- key TEXT PRIMARY KEY,
97
- value TEXT NOT NULL
98
- )`
99
- );
100
- await db.execute(
101
- `CREATE TABLE IF NOT EXISTS projects (
102
- id TEXT PRIMARY KEY,
103
- name TEXT NOT NULL,
104
- path TEXT,
105
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
106
- )`
107
- );
108
- await db.execute(
109
- `CREATE TABLE IF NOT EXISTS sync_state (
110
- id TEXT PRIMARY KEY,
111
- last_synced_at TEXT,
112
- remote_url TEXT
113
- )`
114
- );
115
- await db.execute(
116
- `CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
117
- content,
118
- tags,
119
- content='memories',
120
- content_rowid='rowid'
121
- )`
122
- );
123
- await db.execute(`DROP TRIGGER IF EXISTS memories_ai`);
124
- await db.execute(`DROP TRIGGER IF EXISTS memories_ad`);
125
- await db.execute(`DROP TRIGGER IF EXISTS memories_au`);
126
- await db.execute(`
127
- CREATE TRIGGER memories_ai AFTER INSERT ON memories
128
- WHEN NEW.deleted_at IS NULL
129
- BEGIN
130
- INSERT INTO memories_fts(rowid, content, tags) VALUES (NEW.rowid, NEW.content, NEW.tags);
131
- END
132
- `);
133
- await db.execute(`
134
- CREATE TRIGGER memories_ad AFTER DELETE ON memories BEGIN
135
- INSERT INTO memories_fts(memories_fts, rowid, content, tags) VALUES('delete', OLD.rowid, OLD.content, OLD.tags);
136
- END
137
- `);
138
- await db.execute(`
139
- CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN
140
- INSERT INTO memories_fts(memories_fts, rowid, content, tags) VALUES('delete', OLD.rowid, OLD.content, OLD.tags);
141
- INSERT INTO memories_fts(rowid, content, tags)
142
- SELECT NEW.rowid, NEW.content, NEW.tags WHERE NEW.deleted_at IS NULL;
143
- END
144
- `);
145
- try {
146
- await db.execute(`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type)`);
147
- } catch {
148
- }
149
- try {
150
- await db.execute(`CREATE INDEX IF NOT EXISTS idx_memories_scope_project ON memories(scope, project_id)`);
151
- } catch {
152
- }
153
- }
154
-
155
- // src/lib/git.ts
156
- import { execSync } from "child_process";
157
- function getGitRemoteUrl(cwd) {
158
- try {
159
- const remote = execSync("git remote get-url origin", {
160
- cwd,
161
- encoding: "utf-8",
162
- stdio: ["pipe", "pipe", "pipe"]
163
- }).trim();
164
- return remote || null;
165
- } catch {
166
- return null;
167
- }
168
- }
169
- function getGitRoot(cwd) {
170
- try {
171
- const root = execSync("git rev-parse --show-toplevel", {
172
- cwd,
173
- encoding: "utf-8",
174
- stdio: ["pipe", "pipe", "pipe"]
175
- }).trim();
176
- return root || null;
177
- } catch {
178
- return null;
179
- }
180
- }
181
- function normalizeGitUrl(url) {
182
- let normalized = url.trim();
183
- if (normalized.endsWith(".git")) {
184
- normalized = normalized.slice(0, -4);
185
- }
186
- const sshMatch = normalized.match(/^git@([^:]+):(.+)$/);
187
- if (sshMatch) {
188
- return `${sshMatch[1]}/${sshMatch[2]}`;
189
- }
190
- const httpsMatch = normalized.match(/^https?:\/\/([^/]+)\/(.+)$/);
191
- if (httpsMatch) {
192
- return `${httpsMatch[1]}/${httpsMatch[2]}`;
193
- }
194
- return normalized;
195
- }
196
- function getProjectId(cwd) {
197
- const remoteUrl = getGitRemoteUrl(cwd);
198
- if (!remoteUrl) return null;
199
- return normalizeGitUrl(remoteUrl);
200
- }
201
-
202
- // src/lib/memory.ts
203
- import { nanoid } from "nanoid";
204
- async function addMemory(content, opts) {
205
- const db = await getDb();
206
- const id = nanoid(12);
207
- const tags = opts?.tags?.length ? opts.tags.join(",") : null;
208
- const type = opts?.type ?? "note";
209
- let scope = "global";
210
- let projectId = null;
211
- if (!opts?.global) {
212
- projectId = opts?.projectId ?? getProjectId();
213
- if (projectId) {
214
- scope = "project";
215
- }
216
- }
217
- await db.execute({
218
- sql: `INSERT INTO memories (id, content, tags, scope, project_id, type) VALUES (?, ?, ?, ?, ?, ?)`,
219
- args: [id, content, tags, scope, projectId, type]
220
- });
221
- const result = await db.execute({
222
- sql: `SELECT * FROM memories WHERE id = ?`,
223
- args: [id]
224
- });
225
- return result.rows[0];
226
- }
227
- async function searchMemories(query, opts) {
228
- const db = await getDb();
229
- const limit = opts?.limit ?? 20;
230
- const includeGlobal = opts?.includeGlobal ?? true;
231
- const projectId = opts?.globalOnly ? void 0 : opts?.projectId ?? getProjectId();
232
- const scopeConditions = [];
233
- const args = [];
234
- if (includeGlobal) {
235
- scopeConditions.push("m.scope = 'global'");
236
- }
237
- if (projectId) {
238
- scopeConditions.push("(m.scope = 'project' AND m.project_id = ?)");
239
- args.push(projectId);
240
- }
241
- if (scopeConditions.length === 0) {
242
- return [];
243
- }
244
- let typeFilter = "";
245
- if (opts?.types?.length) {
246
- const placeholders = opts.types.map(() => "?").join(", ");
247
- typeFilter = `AND m.type IN (${placeholders})`;
248
- args.push(...opts.types);
249
- }
250
- const ftsQuery = query.split(/\s+/).filter(Boolean).map((term) => `"${term}"*`).join(" OR ");
251
- args.push(limit);
252
- try {
253
- const result = await db.execute({
254
- sql: `
255
- SELECT m.*, bm25(memories_fts) as rank
256
- FROM memories m
257
- JOIN memories_fts fts ON m.rowid = fts.rowid
258
- WHERE memories_fts MATCH ?
259
- AND m.deleted_at IS NULL
260
- AND (${scopeConditions.join(" OR ")})
261
- ${typeFilter}
262
- ORDER BY rank ASC, m.created_at DESC
263
- LIMIT ?
264
- `,
265
- args: [ftsQuery, ...args]
266
- });
267
- return result.rows;
268
- } catch (error2) {
269
- console.error("FTS search failed, falling back to LIKE:", error2);
270
- return searchMemoriesLike(query, opts);
271
- }
272
- }
273
- async function searchMemoriesLike(query, opts) {
274
- const db = await getDb();
275
- const limit = opts?.limit ?? 20;
276
- const includeGlobal = opts?.includeGlobal ?? true;
277
- const projectId = opts?.globalOnly ? void 0 : opts?.projectId ?? getProjectId();
278
- const conditions = ["deleted_at IS NULL", "content LIKE ?"];
279
- const args = [`%${query}%`];
280
- const scopeConditions = [];
281
- if (includeGlobal) {
282
- scopeConditions.push("scope = 'global'");
283
- }
284
- if (projectId) {
285
- scopeConditions.push("(scope = 'project' AND project_id = ?)");
286
- args.push(projectId);
287
- }
288
- if (scopeConditions.length === 0) {
289
- return [];
290
- }
291
- conditions.push(`(${scopeConditions.join(" OR ")})`);
292
- if (opts?.types?.length) {
293
- const placeholders = opts.types.map(() => "?").join(", ");
294
- conditions.push(`type IN (${placeholders})`);
295
- args.push(...opts.types);
296
- }
297
- args.push(limit);
298
- const result = await db.execute({
299
- sql: `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC LIMIT ?`,
300
- args
301
- });
302
- return result.rows;
303
- }
304
- async function listMemories(opts) {
305
- const db = await getDb();
306
- const limit = opts?.limit ?? 50;
307
- const includeGlobal = opts?.includeGlobal ?? true;
308
- const projectId = opts?.globalOnly ? void 0 : opts?.projectId ?? getProjectId();
309
- const conditions = ["deleted_at IS NULL"];
310
- const args = [];
311
- const scopeConditions = [];
312
- if (includeGlobal) {
313
- scopeConditions.push("scope = 'global'");
314
- }
315
- if (projectId) {
316
- scopeConditions.push("(scope = 'project' AND project_id = ?)");
317
- args.push(projectId);
318
- }
319
- if (scopeConditions.length === 0) {
320
- return [];
321
- }
322
- conditions.push(`(${scopeConditions.join(" OR ")})`);
323
- if (opts?.tags?.length) {
324
- const tagClauses = opts.tags.map(() => `tags LIKE ?`).join(" OR ");
325
- conditions.push(`(${tagClauses})`);
326
- args.push(...opts.tags.map((t) => `%${t}%`));
327
- }
328
- if (opts?.types?.length) {
329
- const placeholders = opts.types.map(() => "?").join(", ");
330
- conditions.push(`type IN (${placeholders})`);
331
- args.push(...opts.types);
332
- }
333
- args.push(limit);
334
- const result = await db.execute({
335
- sql: `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY type ASC, scope ASC, created_at DESC LIMIT ?`,
336
- args
337
- });
338
- return result.rows;
339
- }
340
- async function getRules(opts) {
341
- const db = await getDb();
342
- const projectId = opts?.projectId ?? getProjectId();
343
- const conditions = ["deleted_at IS NULL", "type = 'rule'"];
344
- const args = [];
345
- const scopeConditions = ["scope = 'global'"];
346
- if (projectId) {
347
- scopeConditions.push("(scope = 'project' AND project_id = ?)");
348
- args.push(projectId);
349
- }
350
- conditions.push(`(${scopeConditions.join(" OR ")})`);
351
- const result = await db.execute({
352
- sql: `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY scope ASC, created_at ASC`,
353
- args
354
- });
355
- return result.rows;
356
- }
357
- async function getContext(query, opts) {
358
- const projectId = opts?.projectId ?? getProjectId();
359
- const limit = opts?.limit ?? 10;
360
- const rules = await getRules({ projectId: projectId ?? void 0 });
361
- let memories = [];
362
- if (query) {
363
- memories = await searchMemories(query, {
364
- projectId: projectId ?? void 0,
365
- limit,
366
- types: ["decision", "fact", "note"]
367
- // Exclude rules, they're already included
368
- });
369
- }
370
- return { rules, memories };
371
- }
372
- async function updateMemory(id, updates) {
373
- const db = await getDb();
374
- const existing = await db.execute({
375
- sql: `SELECT * FROM memories WHERE id = ? AND deleted_at IS NULL`,
376
- args: [id]
377
- });
378
- if (existing.rows.length === 0) return null;
379
- const setClauses = ["updated_at = datetime('now')"];
380
- const args = [];
381
- if (updates.content !== void 0) {
382
- setClauses.push("content = ?");
383
- args.push(updates.content);
384
- }
385
- if (updates.tags !== void 0) {
386
- setClauses.push("tags = ?");
387
- args.push(updates.tags.length ? updates.tags.join(",") : null);
388
- }
389
- if (updates.type !== void 0) {
390
- setClauses.push("type = ?");
391
- args.push(updates.type);
392
- }
393
- args.push(id);
394
- await db.execute({
395
- sql: `UPDATE memories SET ${setClauses.join(", ")} WHERE id = ?`,
396
- args
397
- });
398
- const result = await db.execute({
399
- sql: `SELECT * FROM memories WHERE id = ?`,
400
- args: [id]
401
- });
402
- return result.rows[0];
403
- }
404
- async function forgetMemory(id) {
405
- const db = await getDb();
406
- const existing = await db.execute({
407
- sql: `SELECT id FROM memories WHERE id = ? AND deleted_at IS NULL`,
408
- args: [id]
409
- });
410
- if (existing.rows.length === 0) return false;
411
- await db.execute({
412
- sql: `UPDATE memories SET deleted_at = datetime('now') WHERE id = ?`,
413
- args: [id]
414
- });
415
- return true;
416
- }
417
- async function findMemoriesToForget(filter) {
418
- const db = await getDb();
419
- const conditions = ["deleted_at IS NULL"];
420
- const args = [];
421
- if (filter.types?.length) {
422
- const placeholders = filter.types.map(() => "?").join(", ");
423
- conditions.push(`type IN (${placeholders})`);
424
- args.push(...filter.types);
425
- }
426
- if (filter.tags?.length) {
427
- const tagClauses = filter.tags.map(() => `tags LIKE ?`).join(" OR ");
428
- conditions.push(`(${tagClauses})`);
429
- args.push(...filter.tags.map((t) => `%${t}%`));
430
- }
431
- if (filter.olderThanDays !== void 0) {
432
- conditions.push(`created_at < datetime('now', ?)`);
433
- args.push(`-${filter.olderThanDays} days`);
434
- }
435
- if (filter.pattern) {
436
- const likePattern = filter.pattern.replace(/%/g, "\\%").replace(/_/g, "\\_").replace(/\*/g, "%").replace(/\?/g, "_");
437
- conditions.push(`content LIKE ? ESCAPE '\\'`);
438
- args.push(`%${likePattern}%`);
439
- }
440
- if (filter.projectId) {
441
- conditions.push("(scope = 'project' AND project_id = ?)");
442
- args.push(filter.projectId);
443
- }
444
- const result = await db.execute({
445
- sql: `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC`,
446
- args
447
- });
448
- return result.rows;
449
- }
450
- async function bulkForgetByIds(ids) {
451
- if (ids.length === 0) return 0;
452
- const db = await getDb();
453
- const batchSize = 500;
454
- for (let i = 0; i < ids.length; i += batchSize) {
455
- const batch = ids.slice(i, i + batchSize);
456
- const placeholders = batch.map(() => "?").join(", ");
457
- await db.execute({
458
- sql: `UPDATE memories SET deleted_at = datetime('now') WHERE id IN (${placeholders})`,
459
- args: batch
460
- });
461
- }
462
- return ids.length;
463
- }
464
-
465
- // src/lib/auth.ts
466
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, unlink } from "fs/promises";
467
- import { existsSync as existsSync2 } from "fs";
468
- import { join as join2 } from "path";
469
- import { homedir as homedir2 } from "os";
470
- var AUTH_DIR = join2(homedir2(), ".config", "memories");
471
- var AUTH_FILE = join2(AUTH_DIR, "auth.json");
42
+ var AUTH_DIR = join(homedir(), ".config", "memories");
43
+ var AUTH_FILE = join(AUTH_DIR, "auth.json");
472
44
  async function readAuth() {
473
- if (!existsSync2(AUTH_FILE)) return null;
45
+ if (!existsSync(AUTH_FILE)) return null;
474
46
  try {
475
- const raw = await readFile2(AUTH_FILE, "utf-8");
47
+ const raw = await readFile(AUTH_FILE, "utf-8");
476
48
  return JSON.parse(raw);
477
49
  } catch {
478
50
  return null;
479
51
  }
480
52
  }
481
53
  async function saveAuth(data) {
482
- await mkdir2(AUTH_DIR, { recursive: true });
483
- await writeFile2(AUTH_FILE, JSON.stringify(data, null, 2), {
54
+ await mkdir(AUTH_DIR, { recursive: true });
55
+ await writeFile(AUTH_FILE, JSON.stringify(data, null, 2), {
484
56
  encoding: "utf-8",
485
57
  mode: 384
486
58
  });
487
59
  }
488
60
  async function clearAuth() {
489
- if (existsSync2(AUTH_FILE)) {
61
+ if (existsSync(AUTH_FILE)) {
490
62
  await unlink(AUTH_FILE);
491
63
  }
492
64
  }
@@ -559,56 +131,74 @@ function proFeature(feature) {
559
131
  }
560
132
 
561
133
  // src/commands/init.ts
562
- var initCommand = new Command("init").description("Initialize memories for the current project or globally").option("-g, --global", "Initialize global memories (user-wide)").option("-r, --rule <rule>", "Add an initial rule", (val, acc) => [...acc, val], []).action(async (opts) => {
134
+ var SUPPORTED_TOOLS = [
135
+ { name: "Cursor", cmd: "cursor" },
136
+ { name: "Claude Code", cmd: "claude" },
137
+ { name: "GitHub Copilot", cmd: "copilot" },
138
+ { name: "Windsurf", cmd: "windsurf" },
139
+ { name: "Gemini CLI", cmd: "gemini" }
140
+ ];
141
+ var initCommand = new Command("init").description("Initialize memories - one place for all your AI coding tools").option("-g, --global", "Initialize global rules (apply to all projects)").option("-r, --rule <rule>", "Add an initial rule", (val, acc) => [...acc, val], []).action(async (opts) => {
563
142
  try {
564
143
  banner();
565
- step(1, 3, "Initializing database...");
144
+ console.log(chalk2.dim(" One place for your rules. Works with every tool.\n"));
145
+ step(1, 3, "Setting up rule storage...");
566
146
  await getDb();
567
147
  const configDir = getConfigDir();
568
- dim(`Database: ${configDir}/local.db`);
148
+ dim(`Location: ${configDir}/local.db`);
569
149
  step(2, 3, "Detecting scope...");
570
150
  let useGlobal = opts.global;
571
151
  if (!useGlobal) {
572
152
  const projectId = getProjectId();
573
153
  const gitRoot = getGitRoot();
574
154
  if (!projectId) {
575
- warn("Not in a git repository - using global scope");
576
- dim("Global memories apply to all projects");
577
155
  useGlobal = true;
156
+ success("Global scope (rules apply to all projects)");
578
157
  } else {
579
- success("Using project scope");
158
+ success("Project scope detected");
580
159
  dim(`Project: ${projectId}`);
581
160
  dim(`Root: ${gitRoot}`);
161
+ dim("Use --global for rules that apply everywhere");
582
162
  }
583
- }
584
- if (useGlobal) {
585
- success("Using global scope (applies to all projects)");
586
- }
587
- step(3, 3, "Checking account...");
588
- const auth = await readAuth();
589
- if (auth) {
590
- success(`Logged in as ${chalk2.bold(auth.email)}`);
591
163
  } else {
592
- dim("Not logged in (local-only mode)");
593
- dim("Run " + chalk2.cyan("memories login") + " for cloud sync");
164
+ success("Global scope (rules apply to all projects)");
594
165
  }
166
+ step(3, 3, "Supported tools...");
167
+ const toolList = SUPPORTED_TOOLS.map((t) => t.name).join(", ");
168
+ success(`${toolList}, + any MCP client`);
595
169
  if (opts.rule?.length) {
596
170
  console.log("");
597
- info("Adding initial rules...");
171
+ info("Adding rules...");
598
172
  for (const rule of opts.rule) {
599
173
  const memory = await addMemory(rule, {
600
174
  type: "rule",
601
175
  global: useGlobal
602
176
  });
603
- dim(`${memory.id}: ${rule}`);
177
+ dim(`+ ${rule}`);
604
178
  }
605
179
  }
606
- nextSteps([
607
- `${chalk2.cyan("memories add")} ${chalk2.dim('"Your first memory"')}`,
608
- `${chalk2.cyan("memories add --rule")} ${chalk2.dim('"Always use TypeScript"')}`,
609
- `${chalk2.cyan("memories generate")} ${chalk2.dim("to create IDE rule files")}`,
610
- `${chalk2.cyan("memories serve")} ${chalk2.dim("to start MCP server")}`
611
- ]);
180
+ console.log("");
181
+ const auth = await readAuth();
182
+ if (auth) {
183
+ success(`Syncing as ${chalk2.bold(auth.email)}`);
184
+ } else {
185
+ dim("Local only. Run " + chalk2.cyan("memories login") + " to sync across machines.");
186
+ }
187
+ console.log("");
188
+ console.log(chalk2.bold(" Quick Start:"));
189
+ console.log("");
190
+ console.log(chalk2.dim(" 1. Add your rules:"));
191
+ console.log(` ${chalk2.cyan("memories add --rule")} ${chalk2.dim('"Always use TypeScript strict mode"')}`);
192
+ console.log(` ${chalk2.cyan("memories add --rule")} ${chalk2.dim('"Prefer functional components in React"')}`);
193
+ console.log("");
194
+ console.log(chalk2.dim(" 2. Generate for your tools:"));
195
+ console.log(` ${chalk2.cyan("memories generate cursor")} ${chalk2.dim("\u2192 .cursor/rules/memories.mdc")}`);
196
+ console.log(` ${chalk2.cyan("memories generate claude")} ${chalk2.dim("\u2192 CLAUDE.md")}`);
197
+ console.log(` ${chalk2.cyan("memories generate copilot")} ${chalk2.dim("\u2192 .github/copilot-instructions.md")}`);
198
+ console.log(` ${chalk2.cyan("memories generate all")} ${chalk2.dim("\u2192 all tools at once")}`);
199
+ console.log("");
200
+ console.log(chalk2.dim(" 3. Switch tools anytime - your rules follow you."));
201
+ console.log("");
612
202
  } catch (error2) {
613
203
  error("Failed to initialize: " + (error2 instanceof Error ? error2.message : "Unknown error"));
614
204
  process.exit(1);
@@ -618,9 +208,145 @@ var initCommand = new Command("init").description("Initialize memories for the c
618
208
  // src/commands/add.ts
619
209
  import { Command as Command2 } from "commander";
620
210
  import chalk3 from "chalk";
211
+
212
+ // src/lib/templates.ts
213
+ import { input } from "@inquirer/prompts";
214
+ var BUILT_IN_TEMPLATES = [
215
+ {
216
+ name: "decision",
217
+ description: "Document an architectural or technical decision",
218
+ type: "decision",
219
+ fields: [
220
+ { name: "what", prompt: "What did you decide?", required: true },
221
+ { name: "why", prompt: "Why this choice?", required: true },
222
+ { name: "alternatives", prompt: "What alternatives were considered? (optional)", required: false }
223
+ ],
224
+ format: (v) => {
225
+ let result = `${v.what}. Rationale: ${v.why}`;
226
+ if (v.alternatives) result += `. Alternatives considered: ${v.alternatives}`;
227
+ return result;
228
+ }
229
+ },
230
+ {
231
+ name: "error-fix",
232
+ description: "Document a bug and its solution",
233
+ type: "fact",
234
+ fields: [
235
+ { name: "error", prompt: "What was the error/bug?", required: true },
236
+ { name: "cause", prompt: "What caused it?", required: true },
237
+ { name: "solution", prompt: "How did you fix it?", required: true }
238
+ ],
239
+ format: (v) => `Bug: ${v.error}. Cause: ${v.cause}. Fix: ${v.solution}`
240
+ },
241
+ {
242
+ name: "api-endpoint",
243
+ description: "Document an API endpoint",
244
+ type: "fact",
245
+ fields: [
246
+ { name: "method", prompt: "HTTP method (GET, POST, etc.)?", required: true },
247
+ { name: "path", prompt: "Endpoint path?", required: true },
248
+ { name: "description", prompt: "What does it do?", required: true },
249
+ { name: "notes", prompt: "Any important notes? (optional)", required: false }
250
+ ],
251
+ format: (v) => {
252
+ let result = `${v.method.toUpperCase()} ${v.path} - ${v.description}`;
253
+ if (v.notes) result += `. Note: ${v.notes}`;
254
+ return result;
255
+ }
256
+ },
257
+ {
258
+ name: "dependency",
259
+ description: "Document why a dependency was added",
260
+ type: "decision",
261
+ fields: [
262
+ { name: "name", prompt: "Package/library name?", required: true },
263
+ { name: "purpose", prompt: "Why do we use it?", required: true },
264
+ { name: "version", prompt: "Version constraint? (optional)", required: false }
265
+ ],
266
+ format: (v) => {
267
+ let result = `Using ${v.name} for ${v.purpose}`;
268
+ if (v.version) result += ` (${v.version})`;
269
+ return result;
270
+ }
271
+ },
272
+ {
273
+ name: "pattern",
274
+ description: "Document a code pattern or convention",
275
+ type: "rule",
276
+ fields: [
277
+ { name: "name", prompt: "Pattern name?", required: true },
278
+ { name: "when", prompt: "When to use it?", required: true },
279
+ { name: "example", prompt: "Brief example? (optional)", required: false }
280
+ ],
281
+ format: (v) => {
282
+ let result = `${v.name}: ${v.when}`;
283
+ if (v.example) result += `. Example: ${v.example}`;
284
+ return result;
285
+ }
286
+ },
287
+ {
288
+ name: "gotcha",
289
+ description: "Document a non-obvious issue or gotcha",
290
+ type: "fact",
291
+ fields: [
292
+ { name: "issue", prompt: "What's the gotcha?", required: true },
293
+ { name: "context", prompt: "When does it happen?", required: true },
294
+ { name: "workaround", prompt: "How to avoid/handle it?", required: false }
295
+ ],
296
+ format: (v) => {
297
+ let result = `Gotcha: ${v.issue} (${v.context})`;
298
+ if (v.workaround) result += `. Workaround: ${v.workaround}`;
299
+ return result;
300
+ }
301
+ }
302
+ ];
303
+ function getTemplate(name) {
304
+ return BUILT_IN_TEMPLATES.find((t) => t.name === name);
305
+ }
306
+ function listTemplates() {
307
+ return BUILT_IN_TEMPLATES;
308
+ }
309
+ async function fillTemplate(template) {
310
+ const values = {};
311
+ for (const field of template.fields) {
312
+ const value = await input({
313
+ message: field.prompt,
314
+ validate: (v) => {
315
+ if (field.required && !v.trim()) return "This field is required";
316
+ return true;
317
+ }
318
+ });
319
+ if (value.trim()) {
320
+ values[field.name] = value.trim();
321
+ }
322
+ }
323
+ return template.format(values);
324
+ }
325
+
326
+ // src/commands/add.ts
621
327
  var VALID_TYPES = ["rule", "decision", "fact", "note"];
622
- var addCommand = new Command2("add").description("Add a new memory").argument("<content>", "Memory content").option("-t, --tags <tags>", "Comma-separated tags").option("-g, --global", "Store as global memory (default: project-scoped if in git repo)").option("--type <type>", "Memory type: rule, decision, fact, note (default: note)").option("-r, --rule", "Shorthand for --type rule").option("-d, --decision", "Shorthand for --type decision").option("-f, --fact", "Shorthand for --type fact").action(async (content, opts) => {
328
+ var addCommand = new Command2("add").description("Add a new memory").argument("[content]", "Memory content (optional if using --template)").option("-t, --tags <tags>", "Comma-separated tags").option("-g, --global", "Store as global memory (default: project-scoped if in git repo)").option("--type <type>", "Memory type: rule, decision, fact, note (default: note)").option("-r, --rule", "Shorthand for --type rule").option("-d, --decision", "Shorthand for --type decision").option("-f, --fact", "Shorthand for --type fact").option("--template <name>", "Use a template (run 'memories template list' to see options)").action(async (contentArg, opts) => {
623
329
  try {
330
+ let content = contentArg;
331
+ let typeFromTemplate;
332
+ if (opts.template) {
333
+ const template = getTemplate(opts.template);
334
+ if (!template) {
335
+ error(`Template "${opts.template}" not found`);
336
+ dim("Run 'memories template list' to see available templates");
337
+ process.exit(1);
338
+ }
339
+ console.log("");
340
+ info(`Using template: ${template.name}`);
341
+ dim(template.description);
342
+ console.log("");
343
+ content = await fillTemplate(template);
344
+ typeFromTemplate = template.type;
345
+ }
346
+ if (!content) {
347
+ error("Content is required. Provide content or use --template");
348
+ process.exit(1);
349
+ }
624
350
  const auth = await readAuth();
625
351
  if (auth) {
626
352
  try {
@@ -638,7 +364,7 @@ var addCommand = new Command2("add").description("Add a new memory").argument("<
638
364
  }
639
365
  }
640
366
  const tags = opts.tags?.split(",").map((t) => t.trim());
641
- let type = "note";
367
+ let type = typeFromTemplate ?? "note";
642
368
  if (opts.rule) type = "rule";
643
369
  else if (opts.decision) type = "decision";
644
370
  else if (opts.fact) type = "fact";
@@ -876,13 +602,14 @@ var TYPE_ICONS2 = {
876
602
  note: "\u{1F4DD}"
877
603
  };
878
604
  var VALID_TYPES3 = ["rule", "decision", "fact", "note"];
879
- function formatMemory2(m) {
605
+ function formatMemory2(m, score) {
880
606
  const icon = TYPE_ICONS2[m.type] || "\u{1F4DD}";
881
607
  const scope = m.scope === "global" ? chalk6.dim("G") : chalk6.dim("P");
882
608
  const tags = m.tags ? chalk6.dim(` [${m.tags}]`) : "";
883
- return `${icon} ${scope} ${chalk6.dim(m.id)} ${m.content}${tags}`;
609
+ const scoreStr = score !== void 0 ? chalk6.cyan(` (${Math.round(score * 100)}%)`) : "";
610
+ return `${icon} ${scope} ${chalk6.dim(m.id)} ${m.content}${tags}${scoreStr}`;
884
611
  }
885
- var searchCommand = new Command5("search").description("Search memories using full-text search").argument("<query>", "Search query").option("-l, --limit <n>", "Max results", "20").option("--type <type>", "Filter by type: rule, decision, fact, note").option("-g, --global", "Search only global memories").option("--project-only", "Search only project memories (exclude global)").option("--json", "Output as JSON").action(async (query, opts) => {
612
+ var searchCommand = new Command5("search").description("Search memories using full-text or semantic search").argument("<query>", "Search query").option("-l, --limit <n>", "Max results", "20").option("--type <type>", "Filter by type: rule, decision, fact, note").option("-g, --global", "Search only global memories").option("--project-only", "Search only project memories (exclude global)").option("-s, --semantic", "Use semantic (AI) search instead of keyword search").option("--json", "Output as JSON").action(async (query, opts) => {
886
613
  try {
887
614
  let types;
888
615
  if (opts.type) {
@@ -905,6 +632,42 @@ var searchCommand = new Command5("search").description("Search memories using fu
905
632
  return;
906
633
  }
907
634
  }
635
+ if (opts.semantic) {
636
+ try {
637
+ const { semanticSearch, isModelAvailable } = await import("./embeddings-GH4HB6X7.js");
638
+ if (!await isModelAvailable()) {
639
+ console.log(chalk6.yellow("\u26A0") + " Loading embedding model for first time (this may take a moment)...");
640
+ }
641
+ const results = await semanticSearch(query, {
642
+ limit: parseInt(opts.limit, 10),
643
+ projectId
644
+ });
645
+ if (opts.json) {
646
+ console.log(JSON.stringify(results, null, 2));
647
+ return;
648
+ }
649
+ if (results.length === 0) {
650
+ console.log(chalk6.dim(`No semantically similar memories found for "${query}"`));
651
+ console.log(chalk6.dim("Try running 'memories embed' to generate embeddings for existing memories."));
652
+ return;
653
+ }
654
+ console.log(chalk6.bold(`Semantic results for "${query}":`));
655
+ console.log("");
656
+ for (const r of results) {
657
+ const { getMemoryById: getMemoryById2 } = await import("./memory-57CD5DYG.js");
658
+ const m = await getMemoryById2(r.id);
659
+ if (m) {
660
+ console.log(formatMemory2(m, r.score));
661
+ }
662
+ }
663
+ console.log(chalk6.dim(`
664
+ ${results.length} results (semantic search)`));
665
+ return;
666
+ } catch (error2) {
667
+ console.error(chalk6.red("\u2717") + " Semantic search failed:", error2 instanceof Error ? error2.message : "Unknown error");
668
+ console.log(chalk6.dim("Falling back to keyword search..."));
669
+ }
670
+ }
908
671
  const memories = await searchMemories(query, {
909
672
  limit: parseInt(opts.limit, 10),
910
673
  types,
@@ -1117,7 +880,7 @@ var forgetCommand = new Command7("forget").description("Soft-delete memories by
1117
880
  // src/commands/export.ts
1118
881
  import { Command as Command8 } from "commander";
1119
882
  import chalk9 from "chalk";
1120
- import { writeFile as writeFile3 } from "fs/promises";
883
+ import { writeFile as writeFile2 } from "fs/promises";
1121
884
  var exportCommand = new Command8("export").description("Export memories to JSON or YAML file").option("-o, --output <file>", "Output file path (default: stdout)").option("-f, --format <format>", "Output format: json, yaml (default: json)", "json").option("-g, --global", "Export only global memories").option("--project-only", "Export only project memories").option("--type <type>", "Filter by type: rule, decision, fact, note").action(async (opts) => {
1122
885
  try {
1123
886
  const projectId = getProjectId();
@@ -1164,7 +927,7 @@ var exportCommand = new Command8("export").description("Export memories to JSON
1164
927
  output = JSON.stringify(exportData, null, 2);
1165
928
  }
1166
929
  if (opts.output) {
1167
- await writeFile3(opts.output, output, "utf-8");
930
+ await writeFile2(opts.output, output, "utf-8");
1168
931
  console.log(chalk9.green("\u2713") + ` Exported ${memories.length} memories to ${chalk9.dim(opts.output)}`);
1169
932
  } else {
1170
933
  console.log(output);
@@ -1178,10 +941,10 @@ var exportCommand = new Command8("export").description("Export memories to JSON
1178
941
  // src/commands/import.ts
1179
942
  import { Command as Command9 } from "commander";
1180
943
  import chalk10 from "chalk";
1181
- import { readFile as readFile3 } from "fs/promises";
944
+ import { readFile as readFile2 } from "fs/promises";
1182
945
  var importCommand = new Command9("import").description("Import memories from JSON or YAML file").argument("<file>", "Input file path").option("-f, --format <format>", "Input format: json, yaml (auto-detected from extension)").option("-g, --global", "Import all as global memories (override file scope)").option("--dry-run", "Show what would be imported without actually importing").action(async (file, opts) => {
1183
946
  try {
1184
- const content = await readFile3(file, "utf-8");
947
+ const content = await readFile2(file, "utf-8");
1185
948
  let format = opts.format;
1186
949
  if (!format) {
1187
950
  if (file.endsWith(".yaml") || file.endsWith(".yml")) {
@@ -1262,17 +1025,17 @@ ${validMemories.length} memories would be imported`));
1262
1025
  import { Command as Command10 } from "commander";
1263
1026
 
1264
1027
  // src/lib/config.ts
1265
- import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
1266
- import { join as join3 } from "path";
1267
- import { existsSync as existsSync3 } from "fs";
1028
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
1029
+ import { join as join2 } from "path";
1030
+ import { existsSync as existsSync2 } from "fs";
1268
1031
  import { parse, stringify } from "yaml";
1269
1032
  var AGENTS_DIR = ".agents";
1270
1033
  var CONFIG_FILE = "config.yaml";
1271
1034
  async function initConfig(dir) {
1272
- const agentsDir = join3(dir, AGENTS_DIR);
1273
- await mkdir3(agentsDir, { recursive: true });
1274
- const configPath = join3(agentsDir, CONFIG_FILE);
1275
- if (!existsSync3(configPath)) {
1035
+ const agentsDir = join2(dir, AGENTS_DIR);
1036
+ await mkdir2(agentsDir, { recursive: true });
1037
+ const configPath = join2(agentsDir, CONFIG_FILE);
1038
+ if (!existsSync2(configPath)) {
1276
1039
  const defaultConfig = {
1277
1040
  name: "my-project",
1278
1041
  description: "Agent memory configuration",
@@ -1282,14 +1045,14 @@ async function initConfig(dir) {
1282
1045
  store: "~/.config/memories/local.db"
1283
1046
  }
1284
1047
  };
1285
- await writeFile4(configPath, stringify(defaultConfig), "utf-8");
1048
+ await writeFile3(configPath, stringify(defaultConfig), "utf-8");
1286
1049
  }
1287
1050
  return configPath;
1288
1051
  }
1289
1052
  async function readConfig(dir) {
1290
- const configPath = join3(dir, AGENTS_DIR, CONFIG_FILE);
1291
- if (!existsSync3(configPath)) return null;
1292
- const raw = await readFile4(configPath, "utf-8");
1053
+ const configPath = join2(dir, AGENTS_DIR, CONFIG_FILE);
1054
+ if (!existsSync2(configPath)) return null;
1055
+ const raw = await readFile3(configPath, "utf-8");
1293
1056
  return parse(raw);
1294
1057
  }
1295
1058
 
@@ -1715,7 +1478,7 @@ import { Command as Command12 } from "commander";
1715
1478
  import ora from "ora";
1716
1479
 
1717
1480
  // src/lib/turso.ts
1718
- import { nanoid as nanoid2 } from "nanoid";
1481
+ import { nanoid } from "nanoid";
1719
1482
  var API_BASE = "https://api.turso.tech/v1";
1720
1483
  function getApiToken() {
1721
1484
  const token = process.env.TURSO_PLATFORM_API_TOKEN;
@@ -1742,7 +1505,7 @@ async function api(path, opts) {
1742
1505
  return res.json();
1743
1506
  }
1744
1507
  async function createDatabase(org) {
1745
- const name = `memories-${nanoid2(8).toLowerCase()}`;
1508
+ const name = `memories-${nanoid(8).toLowerCase()}`;
1746
1509
  const { database } = await api(
1747
1510
  `/organizations/${org}/databases`,
1748
1511
  {
@@ -1765,10 +1528,10 @@ async function createDatabaseToken(org, dbName) {
1765
1528
  }
1766
1529
 
1767
1530
  // src/commands/sync.ts
1768
- import { unlinkSync, existsSync as existsSync4 } from "fs";
1769
- import { join as join4 } from "path";
1770
- import { homedir as homedir3 } from "os";
1771
- var DB_PATH = join4(homedir3(), ".config", "memories", "local.db");
1531
+ import { unlinkSync, existsSync as existsSync3 } from "fs";
1532
+ import { join as join3 } from "path";
1533
+ import { homedir as homedir2 } from "os";
1534
+ var DB_PATH = join3(homedir2(), ".config", "memories", "local.db");
1772
1535
  var syncCommand = new Command12("sync").description(
1773
1536
  "Manage remote sync"
1774
1537
  );
@@ -1791,7 +1554,7 @@ syncCommand.command("enable").description("Provision a cloud database and enable
1791
1554
  const profileRes = await apiFetch("/api/db/credentials");
1792
1555
  if (profileRes.ok) {
1793
1556
  const creds = await profileRes.json();
1794
- if (existsSync4(DB_PATH)) {
1557
+ if (existsSync3(DB_PATH)) {
1795
1558
  resetDb();
1796
1559
  unlinkSync(DB_PATH);
1797
1560
  }
@@ -1827,7 +1590,7 @@ syncCommand.command("enable").description("Provision a cloud database and enable
1827
1590
  spinner.text = "Generating auth token...";
1828
1591
  const token = await createDatabaseToken(opts.org, db.name);
1829
1592
  const syncUrl = `libsql://${db.hostname}`;
1830
- if (existsSync4(DB_PATH)) {
1593
+ if (existsSync3(DB_PATH)) {
1831
1594
  resetDb();
1832
1595
  unlinkSync(DB_PATH);
1833
1596
  }
@@ -1875,10 +1638,10 @@ syncCommand.command("status").description("Show sync configuration").action(asyn
1875
1638
  // src/commands/generate.ts
1876
1639
  import { Command as Command13 } from "commander";
1877
1640
  import chalk11 from "chalk";
1878
- import { writeFile as writeFile5, readFile as readFile5, mkdir as mkdir4 } from "fs/promises";
1879
- import { existsSync as existsSync5, watch as fsWatch } from "fs";
1880
- import { dirname, resolve, join as join5 } from "path";
1881
- import { homedir as homedir4 } from "os";
1641
+ import { writeFile as writeFile4, readFile as readFile4, mkdir as mkdir3 } from "fs/promises";
1642
+ import { existsSync as existsSync4, watch as fsWatch } from "fs";
1643
+ import { dirname, resolve, join as join4 } from "path";
1644
+ import { homedir as homedir3 } from "os";
1882
1645
  import { checkbox } from "@inquirer/prompts";
1883
1646
  var MARKER = "Generated by memories.sh";
1884
1647
  var VALID_TYPES6 = ["rule", "decision", "fact", "note"];
@@ -2015,7 +1778,7 @@ function makeFooter() {
2015
1778
  }
2016
1779
  async function hasOurMarker(filePath) {
2017
1780
  try {
2018
- const content = await readFile5(filePath, "utf-8");
1781
+ const content = await readFile4(filePath, "utf-8");
2019
1782
  return content.includes(MARKER);
2020
1783
  } catch {
2021
1784
  return false;
@@ -2026,7 +1789,7 @@ async function checkGitignore(filePath) {
2026
1789
  if (TRACK_BY_DEFAULT.has(filePath)) return;
2027
1790
  const gitignorePath = resolve(".gitignore");
2028
1791
  try {
2029
- const content = existsSync5(gitignorePath) ? await readFile5(gitignorePath, "utf-8") : "";
1792
+ const content = existsSync4(gitignorePath) ? await readFile4(gitignorePath, "utf-8") : "";
2030
1793
  const lines = content.split("\n");
2031
1794
  const parentDir = filePath.split("/")[0];
2032
1795
  if (lines.some((l) => l.trim() === filePath || l.trim() === parentDir || l.trim() === `${parentDir}/`)) {
@@ -2045,7 +1808,7 @@ async function writeTarget(target, memories, opts) {
2045
1808
  console.log();
2046
1809
  return;
2047
1810
  }
2048
- if (existsSync5(outPath)) {
1811
+ if (existsSync4(outPath)) {
2049
1812
  const ours = await hasOurMarker(outPath);
2050
1813
  if (!ours && !opts.force) {
2051
1814
  console.error(
@@ -2054,8 +1817,8 @@ async function writeTarget(target, memories, opts) {
2054
1817
  return;
2055
1818
  }
2056
1819
  }
2057
- await mkdir4(dirname(outPath), { recursive: true });
2058
- await writeFile5(outPath, content, "utf-8");
1820
+ await mkdir3(dirname(outPath), { recursive: true });
1821
+ await writeFile4(outPath, content, "utf-8");
2059
1822
  console.log(chalk11.green("\u2713") + ` Wrote ${target.name} \u2192 ${chalk11.dim(outPath)}`);
2060
1823
  await checkGitignore(opts.output ?? target.defaultPath);
2061
1824
  }
@@ -2078,13 +1841,13 @@ function parseTypes(raw) {
2078
1841
  }
2079
1842
  return types;
2080
1843
  }
2081
- function getDbPath2() {
2082
- const dataDir = process.env.MEMORIES_DATA_DIR ?? join5(homedir4(), ".config", "memories");
2083
- return join5(dataDir, "local.db");
1844
+ function getDbPath() {
1845
+ const dataDir = process.env.MEMORIES_DATA_DIR ?? join4(homedir3(), ".config", "memories");
1846
+ return join4(dataDir, "local.db");
2084
1847
  }
2085
1848
  async function runWatch(targets, memories, opts) {
2086
- const dbPath = getDbPath2();
2087
- if (!existsSync5(dbPath)) {
1849
+ const dbPath = getDbPath();
1850
+ if (!existsSync4(dbPath)) {
2088
1851
  console.error(chalk11.red("\u2717") + " Database not found. Run: memories init");
2089
1852
  process.exit(1);
2090
1853
  }
@@ -2199,8 +1962,8 @@ import chalk12 from "chalk";
2199
1962
  import { execFileSync as execFileSync2 } from "child_process";
2200
1963
  import { writeFileSync, readFileSync, unlinkSync as unlinkSync2 } from "fs";
2201
1964
  import { tmpdir } from "os";
2202
- import { join as join6 } from "path";
2203
- import { nanoid as nanoid3 } from "nanoid";
1965
+ import { join as join5 } from "path";
1966
+ import { nanoid as nanoid2 } from "nanoid";
2204
1967
  import { select } from "@inquirer/prompts";
2205
1968
  var VALID_TYPES7 = ["rule", "decision", "fact", "note"];
2206
1969
  function truncate2(str, max) {
@@ -2249,7 +2012,7 @@ var editCommand = new Command14("edit").description("Edit an existing memory").a
2249
2012
  let newContent = opts.content;
2250
2013
  if (newContent === void 0 && opts.tags === void 0 && opts.type === void 0) {
2251
2014
  const editor = process.env.EDITOR || process.env.VISUAL || "vi";
2252
- const tmpFile = join6(tmpdir(), `memories-edit-${nanoid3(6)}.md`);
2015
+ const tmpFile = join5(tmpdir(), `memories-edit-${nanoid2(6)}.md`);
2253
2016
  writeFileSync(tmpFile, memory.content, "utf-8");
2254
2017
  try {
2255
2018
  execFileSync2(editor, [tmpFile], { stdio: "inherit" });
@@ -2354,8 +2117,8 @@ var statsCommand = new Command15("stats").description("Show memory statistics").
2354
2117
  // src/commands/doctor.ts
2355
2118
  import { Command as Command16 } from "commander";
2356
2119
  import chalk14 from "chalk";
2357
- import { existsSync as existsSync6 } from "fs";
2358
- import { join as join7 } from "path";
2120
+ import { existsSync as existsSync5 } from "fs";
2121
+ import { join as join6 } from "path";
2359
2122
  var doctorCommand = new Command16("doctor").description("Check memories health and diagnose issues").option("--fix", "Attempt to fix issues found").action(async (opts) => {
2360
2123
  try {
2361
2124
  console.log(chalk14.bold("memories doctor\n"));
@@ -2363,8 +2126,8 @@ var doctorCommand = new Command16("doctor").description("Check memories health a
2363
2126
  {
2364
2127
  name: "Database file",
2365
2128
  run: async () => {
2366
- const dbPath = join7(getConfigDir(), "local.db");
2367
- if (existsSync6(dbPath)) {
2129
+ const dbPath = join6(getConfigDir(), "local.db");
2130
+ if (existsSync5(dbPath)) {
2368
2131
  return { ok: true, message: `Found at ${dbPath}` };
2369
2132
  }
2370
2133
  return { ok: false, message: `Not found at ${dbPath}. Run: memories init` };
@@ -2495,10 +2258,10 @@ var doctorCommand = new Command16("doctor").description("Check memories health a
2495
2258
  // src/commands/hook.ts
2496
2259
  import { Command as Command17 } from "commander";
2497
2260
  import chalk15 from "chalk";
2498
- import { readFile as readFile6, writeFile as writeFile6, chmod } from "fs/promises";
2499
- import { existsSync as existsSync7, readFileSync as readFileSync2 } from "fs";
2261
+ import { readFile as readFile5, writeFile as writeFile5, chmod } from "fs/promises";
2262
+ import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
2500
2263
  import { execFileSync as execFileSync3 } from "child_process";
2501
- import { join as join8 } from "path";
2264
+ import { join as join7 } from "path";
2502
2265
  var HOOK_MARKER_START = "# >>> memories.sh hook >>>";
2503
2266
  var HOOK_MARKER_END = "# <<< memories.sh hook <<<";
2504
2267
  var HOOK_SNIPPET = `
@@ -2519,22 +2282,22 @@ function getGitDir() {
2519
2282
  function getHookLocation(hookName) {
2520
2283
  const gitDir = getGitDir();
2521
2284
  if (!gitDir) return null;
2522
- const huskyPath = join8(".husky", hookName);
2523
- if (existsSync7(".husky") && !existsSync7(join8(".husky", "_"))) {
2285
+ const huskyPath = join7(".husky", hookName);
2286
+ if (existsSync6(".husky") && !existsSync6(join7(".husky", "_"))) {
2524
2287
  return { path: huskyPath, type: "husky" };
2525
2288
  }
2526
- const huskyLegacyPath = join8(".husky", "_", hookName);
2527
- if (existsSync7(join8(".husky", "_"))) {
2289
+ const huskyLegacyPath = join7(".husky", "_", hookName);
2290
+ if (existsSync6(join7(".husky", "_"))) {
2528
2291
  return { path: huskyLegacyPath, type: "husky" };
2529
2292
  }
2530
- if (existsSync7(huskyPath)) {
2293
+ if (existsSync6(huskyPath)) {
2531
2294
  return { path: huskyPath, type: "husky" };
2532
2295
  }
2533
- return { path: join8(gitDir, "hooks", hookName), type: "git" };
2296
+ return { path: join7(gitDir, "hooks", hookName), type: "git" };
2534
2297
  }
2535
2298
  function detectLintStaged() {
2536
2299
  try {
2537
- if (!existsSync7("package.json")) return false;
2300
+ if (!existsSync6("package.json")) return false;
2538
2301
  const pkg = JSON.parse(readFileSync2("package.json", "utf-8"));
2539
2302
  return !!(pkg["lint-staged"] || pkg.devDependencies?.["lint-staged"] || pkg.dependencies?.["lint-staged"]);
2540
2303
  } catch {
@@ -2551,15 +2314,15 @@ hookCommand.addCommand(
2551
2314
  process.exit(1);
2552
2315
  }
2553
2316
  const hookPath = location.path;
2554
- if (existsSync7(hookPath)) {
2555
- const content = await readFile6(hookPath, "utf-8");
2317
+ if (existsSync6(hookPath)) {
2318
+ const content = await readFile5(hookPath, "utf-8");
2556
2319
  if (content.includes(HOOK_MARKER_START)) {
2557
2320
  console.log(chalk15.dim("Hook already installed. Use 'memories hook uninstall' first to reinstall."));
2558
2321
  return;
2559
2322
  }
2560
- await writeFile6(hookPath, content.trimEnd() + "\n" + HOOK_SNIPPET + "\n", "utf-8");
2323
+ await writeFile5(hookPath, content.trimEnd() + "\n" + HOOK_SNIPPET + "\n", "utf-8");
2561
2324
  } else {
2562
- await writeFile6(hookPath, "#!/bin/sh\n" + HOOK_SNIPPET + "\n", "utf-8");
2325
+ await writeFile5(hookPath, "#!/bin/sh\n" + HOOK_SNIPPET + "\n", "utf-8");
2563
2326
  }
2564
2327
  await chmod(hookPath, 493);
2565
2328
  const locationLabel = location.type === "husky" ? "Husky" : ".git/hooks";
@@ -2585,11 +2348,11 @@ hookCommand.addCommand(
2585
2348
  process.exit(1);
2586
2349
  }
2587
2350
  const hookPath = location.path;
2588
- if (!existsSync7(hookPath)) {
2351
+ if (!existsSync6(hookPath)) {
2589
2352
  console.log(chalk15.dim("No hook file found."));
2590
2353
  return;
2591
2354
  }
2592
- const content = await readFile6(hookPath, "utf-8");
2355
+ const content = await readFile5(hookPath, "utf-8");
2593
2356
  if (!content.includes(HOOK_MARKER_START)) {
2594
2357
  console.log(chalk15.dim("No memories hook found in " + opts.hook));
2595
2358
  return;
@@ -2603,7 +2366,7 @@ hookCommand.addCommand(
2603
2366
  await unlink2(hookPath);
2604
2367
  console.log(chalk15.green("\u2713") + ` Removed ${chalk15.dim(opts.hook)} hook (was memories-only)`);
2605
2368
  } else {
2606
- await writeFile6(hookPath, cleaned, "utf-8");
2369
+ await writeFile5(hookPath, cleaned, "utf-8");
2607
2370
  console.log(chalk15.green("\u2713") + ` Removed memories section from ${chalk15.dim(opts.hook)}`);
2608
2371
  }
2609
2372
  } catch (error2) {
@@ -2620,11 +2383,11 @@ hookCommand.addCommand(
2620
2383
  console.error(chalk15.red("\u2717") + " Not in a git repository");
2621
2384
  process.exit(1);
2622
2385
  }
2623
- if (!existsSync7(hookPath)) {
2386
+ if (!existsSync6(hookPath)) {
2624
2387
  console.log(chalk15.dim("Not installed") + ` \u2014 no ${opts.hook} hook found`);
2625
2388
  return;
2626
2389
  }
2627
- const content = await readFile6(hookPath, "utf-8");
2390
+ const content = await readFile5(hookPath, "utf-8");
2628
2391
  if (content.includes(HOOK_MARKER_START)) {
2629
2392
  console.log(chalk15.green("\u2713") + ` Installed in ${chalk15.dim(hookPath)}`);
2630
2393
  } else {
@@ -2643,8 +2406,8 @@ function escapeRegex(str) {
2643
2406
  // src/commands/ingest.ts
2644
2407
  import { Command as Command18 } from "commander";
2645
2408
  import chalk16 from "chalk";
2646
- import { readFile as readFile7 } from "fs/promises";
2647
- import { existsSync as existsSync8 } from "fs";
2409
+ import { readFile as readFile6 } from "fs/promises";
2410
+ import { existsSync as existsSync7 } from "fs";
2648
2411
  var SOURCES = [
2649
2412
  { name: "cursor", paths: [".cursor/rules/memories.mdc", ".cursorrules"], description: "Cursor rules" },
2650
2413
  { name: "claude", paths: ["CLAUDE.md"], description: "Claude Code instructions" },
@@ -2711,7 +2474,7 @@ var ingestCommand = new Command18("ingest").description("Import memories from ex
2711
2474
  if (opts.all) {
2712
2475
  for (const src of SOURCES) {
2713
2476
  for (const p of src.paths) {
2714
- if (existsSync8(p)) {
2477
+ if (existsSync7(p)) {
2715
2478
  filesToProcess.push({ name: src.name, path: p });
2716
2479
  }
2717
2480
  }
@@ -2720,7 +2483,7 @@ var ingestCommand = new Command18("ingest").description("Import memories from ex
2720
2483
  const known = SOURCES.find((s) => s.name === source);
2721
2484
  if (known) {
2722
2485
  for (const p of known.paths) {
2723
- if (existsSync8(p)) {
2486
+ if (existsSync7(p)) {
2724
2487
  filesToProcess.push({ name: known.name, path: p });
2725
2488
  break;
2726
2489
  }
@@ -2729,7 +2492,7 @@ var ingestCommand = new Command18("ingest").description("Import memories from ex
2729
2492
  console.error(chalk16.red("\u2717") + ` No ${known.description} file found at: ${known.paths.join(", ")}`);
2730
2493
  process.exit(1);
2731
2494
  }
2732
- } else if (existsSync8(source)) {
2495
+ } else if (existsSync7(source)) {
2733
2496
  filesToProcess.push({ name: "file", path: source });
2734
2497
  } else {
2735
2498
  console.error(chalk16.red("\u2717") + ` Unknown source "${source}". Valid: ${SOURCES.map((s) => s.name).join(", ")}, or a file path`);
@@ -2754,7 +2517,7 @@ var ingestCommand = new Command18("ingest").description("Import memories from ex
2754
2517
  let totalImported = 0;
2755
2518
  let totalSkipped = 0;
2756
2519
  for (const file of filesToProcess) {
2757
- const content = await readFile7(file.path, "utf-8");
2520
+ const content = await readFile6(file.path, "utf-8");
2758
2521
  if (content.includes(MARKER2)) {
2759
2522
  console.log(chalk16.dim(` Skipping ${file.path} (generated by memories.sh)`));
2760
2523
  continue;
@@ -2802,8 +2565,8 @@ var ingestCommand = new Command18("ingest").description("Import memories from ex
2802
2565
  // src/commands/diff.ts
2803
2566
  import { Command as Command19 } from "commander";
2804
2567
  import chalk17 from "chalk";
2805
- import { readFile as readFile8 } from "fs/promises";
2806
- import { existsSync as existsSync9 } from "fs";
2568
+ import { readFile as readFile7 } from "fs/promises";
2569
+ import { existsSync as existsSync8 } from "fs";
2807
2570
  import { resolve as resolve2 } from "path";
2808
2571
  var MARKER3 = "Generated by memories.sh";
2809
2572
  var VALID_TYPES9 = ["rule", "decision", "fact", "note"];
@@ -2848,7 +2611,7 @@ async function fetchMemories2(types) {
2848
2611
  }
2849
2612
  async function diffTarget(target, currentMemories, outputPath) {
2850
2613
  const filePath = resolve2(outputPath ?? target.defaultPath);
2851
- if (!existsSync9(filePath)) {
2614
+ if (!existsSync8(filePath)) {
2852
2615
  return {
2853
2616
  added: currentMemories.map((m) => m.content),
2854
2617
  removed: [],
@@ -2859,7 +2622,7 @@ async function diffTarget(target, currentMemories, outputPath) {
2859
2622
  generatedAt: null
2860
2623
  };
2861
2624
  }
2862
- const content = await readFile8(filePath, "utf-8");
2625
+ const content = await readFile7(filePath, "utf-8");
2863
2626
  const isOurs = content.includes(MARKER3);
2864
2627
  const generatedAt = extractTimestamp(content);
2865
2628
  const fileMemories = extractFileMemories(content);
@@ -3078,10 +2841,680 @@ tagCommand.addCommand(
3078
2841
  })
3079
2842
  );
3080
2843
 
3081
- // src/commands/login.ts
2844
+ // src/commands/validate.ts
3082
2845
  import { Command as Command21 } from "commander";
3083
2846
  import chalk19 from "chalk";
2847
+ function levenshtein(a, b) {
2848
+ const m = a.length, n = b.length;
2849
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
2850
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
2851
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
2852
+ for (let i = 1; i <= m; i++) {
2853
+ for (let j = 1; j <= n; j++) {
2854
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
2855
+ }
2856
+ }
2857
+ return dp[m][n];
2858
+ }
2859
+ function similarity(a, b) {
2860
+ const maxLen = Math.max(a.length, b.length);
2861
+ if (maxLen === 0) return 1;
2862
+ return 1 - levenshtein(a.toLowerCase(), b.toLowerCase()) / maxLen;
2863
+ }
2864
+ var CONFLICT_PAIRS = [
2865
+ ["always", "never"],
2866
+ ["use", "avoid"],
2867
+ ["prefer", "avoid"],
2868
+ ["enable", "disable"],
2869
+ ["tabs", "spaces"],
2870
+ ["single", "double"],
2871
+ ["require", "forbid"],
2872
+ ["must", "must not"],
2873
+ ["should", "should not"]
2874
+ ];
2875
+ function extractKeywords(text) {
2876
+ return text.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
2877
+ }
2878
+ function findTopicOverlap(a, b) {
2879
+ const wordsA = new Set(extractKeywords(a));
2880
+ const wordsB = new Set(extractKeywords(b));
2881
+ const overlap = [];
2882
+ for (const w of wordsA) {
2883
+ if (wordsB.has(w) && w.length > 3) overlap.push(w);
2884
+ }
2885
+ return overlap;
2886
+ }
2887
+ function checkForConflict(a, b) {
2888
+ const lowerA = a.toLowerCase();
2889
+ const lowerB = b.toLowerCase();
2890
+ for (const [pos, neg] of CONFLICT_PAIRS) {
2891
+ const aHasPos = lowerA.includes(pos);
2892
+ const aHasNeg = lowerA.includes(neg);
2893
+ const bHasPos = lowerB.includes(pos);
2894
+ const bHasNeg = lowerB.includes(neg);
2895
+ if (aHasPos && bHasNeg || aHasNeg && bHasPos) {
2896
+ const overlap = findTopicOverlap(a, b);
2897
+ if (overlap.length > 0) return true;
2898
+ }
2899
+ }
2900
+ return false;
2901
+ }
2902
+ var validateCommand = new Command21("validate").description("Check for conflicting or duplicate rules").option("--fix", "Interactive mode to resolve issues").action(async (opts) => {
2903
+ try {
2904
+ const projectId = getProjectId() ?? void 0;
2905
+ const rules = await getRules({ projectId });
2906
+ if (rules.length === 0) {
2907
+ console.log(chalk19.dim("No rules to validate."));
2908
+ return;
2909
+ }
2910
+ console.log(chalk19.bold("\u{1F50D} Validating rules...\n"));
2911
+ const issues = [];
2912
+ for (let i = 0; i < rules.length; i++) {
2913
+ for (let j = i + 1; j < rules.length; j++) {
2914
+ const a = rules[i];
2915
+ const b = rules[j];
2916
+ if (a.content.toLowerCase() === b.content.toLowerCase()) {
2917
+ issues.push({ type: "duplicate", memory1: a, memory2: b });
2918
+ continue;
2919
+ }
2920
+ const sim = similarity(a.content, b.content);
2921
+ if (sim > 0.85) {
2922
+ issues.push({
2923
+ type: "near-duplicate",
2924
+ memory1: a,
2925
+ memory2: b,
2926
+ detail: `${Math.round(sim * 100)}% similar`
2927
+ });
2928
+ continue;
2929
+ }
2930
+ if (checkForConflict(a.content, b.content)) {
2931
+ const overlap = findTopicOverlap(a.content, b.content);
2932
+ issues.push({
2933
+ type: "conflict",
2934
+ memory1: a,
2935
+ memory2: b,
2936
+ detail: `Topic: ${overlap.join(", ")}`
2937
+ });
2938
+ }
2939
+ }
2940
+ }
2941
+ if (issues.length === 0) {
2942
+ console.log(chalk19.green("\u2713") + ` ${rules.length} rules validated, no issues found.`);
2943
+ return;
2944
+ }
2945
+ console.log(chalk19.yellow("\u26A0\uFE0F Potential Issues Found:\n"));
2946
+ let num = 1;
2947
+ for (const issue of issues) {
2948
+ const typeLabel = issue.type === "duplicate" ? "Exact duplicate" : issue.type === "near-duplicate" ? "Near duplicate" : "Potential conflict";
2949
+ const color = issue.type === "conflict" ? chalk19.red : chalk19.yellow;
2950
+ console.log(color(`${num}. ${typeLabel}${issue.detail ? ` (${issue.detail})` : ""}:`));
2951
+ console.log(` \u{1F4CC} "${issue.memory1.content}"`);
2952
+ console.log(` \u{1F4CC} "${issue.memory2.content}"`);
2953
+ if (issue.type === "duplicate" || issue.type === "near-duplicate") {
2954
+ console.log(chalk19.dim(" \u2192 Consider merging these rules\n"));
2955
+ } else {
2956
+ console.log(chalk19.dim(" \u2192 These rules may contradict each other\n"));
2957
+ }
2958
+ num++;
2959
+ }
2960
+ const duplicates = issues.filter((i) => i.type === "duplicate" || i.type === "near-duplicate").length;
2961
+ const conflicts = issues.filter((i) => i.type === "conflict").length;
2962
+ console.log(chalk19.bold(`${rules.length} rules validated, ${issues.length} issues found`));
2963
+ if (duplicates > 0) console.log(chalk19.dim(` ${duplicates} duplicate(s)`));
2964
+ if (conflicts > 0) console.log(chalk19.dim(` ${conflicts} conflict(s)`));
2965
+ if (opts.fix) {
2966
+ console.log(chalk19.dim("\n--fix mode not yet implemented. Review issues above manually."));
2967
+ }
2968
+ } catch (error2) {
2969
+ console.error(chalk19.red("\u2717") + " Validation failed:", error2 instanceof Error ? error2.message : "Unknown error");
2970
+ process.exit(1);
2971
+ }
2972
+ });
2973
+
2974
+ // src/commands/stale.ts
2975
+ import { Command as Command22 } from "commander";
2976
+ import chalk20 from "chalk";
2977
+ import { createInterface as createInterface2 } from "readline";
2978
+ var TYPE_ICONS4 = {
2979
+ rule: "\u{1F4CC}",
2980
+ decision: "\u{1F4A1}",
2981
+ fact: "\u{1F4CB}",
2982
+ note: "\u{1F4DD}"
2983
+ };
2984
+ async function prompt(message) {
2985
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
2986
+ return new Promise((resolve3) => {
2987
+ rl.question(message, (answer) => {
2988
+ rl.close();
2989
+ resolve3(answer.toLowerCase().trim());
2990
+ });
2991
+ });
2992
+ }
2993
+ var staleCommand = new Command22("stale").description("Find memories that haven't been updated in a while").option("--days <n>", "Staleness threshold in days (default: 90)", "90").option("--type <type>", "Filter by memory type: rule, decision, fact, note").option("--json", "Output as JSON").action(async (opts) => {
2994
+ try {
2995
+ const db = await getDb();
2996
+ const days = parseInt(opts.days, 10);
2997
+ const projectId = getProjectId() ?? void 0;
2998
+ if (isNaN(days) || days <= 0) {
2999
+ console.error(chalk20.red("\u2717") + " --days must be a positive number");
3000
+ process.exit(1);
3001
+ }
3002
+ let sql = `
3003
+ SELECT id, content, type, scope, created_at, updated_at,
3004
+ CAST((julianday('now') - julianday(COALESCE(updated_at, created_at))) AS INTEGER) as days_old
3005
+ FROM memories
3006
+ WHERE deleted_at IS NULL
3007
+ AND (julianday('now') - julianday(COALESCE(updated_at, created_at))) > ?
3008
+ `;
3009
+ const args = [days];
3010
+ if (opts.type) {
3011
+ sql += " AND type = ?";
3012
+ args.push(opts.type);
3013
+ }
3014
+ sql += " ORDER BY days_old DESC";
3015
+ const result = await db.execute({ sql, args });
3016
+ const stale = result.rows;
3017
+ if (opts.json) {
3018
+ console.log(JSON.stringify(stale, null, 2));
3019
+ return;
3020
+ }
3021
+ if (stale.length === 0) {
3022
+ console.log(chalk20.green("\u2713") + ` No memories older than ${days} days.`);
3023
+ return;
3024
+ }
3025
+ console.log(chalk20.bold(`\u23F0 Stale Memories (not updated in ${days}+ days)
3026
+ `));
3027
+ for (const m of stale.slice(0, 30)) {
3028
+ const icon = TYPE_ICONS4[m.type] || "\u{1F4DD}";
3029
+ const scope = m.scope === "global" ? chalk20.dim("G") : chalk20.dim("P");
3030
+ const preview = m.content.length > 50 ? m.content.slice(0, 47) + "..." : m.content;
3031
+ console.log(` ${icon} ${scope} ${chalk20.dim(m.id)} ${preview} ${chalk20.yellow(`${m.days_old}d`)}`);
3032
+ }
3033
+ if (stale.length > 30) {
3034
+ console.log(chalk20.dim(` ... and ${stale.length - 30} more`));
3035
+ }
3036
+ console.log("");
3037
+ console.log(`${stale.length} stale ${stale.length === 1 ? "memory" : "memories"} found`);
3038
+ console.log(chalk20.dim("Run 'memories review' to clean up interactively"));
3039
+ } catch (error2) {
3040
+ console.error(chalk20.red("\u2717") + " Failed:", error2 instanceof Error ? error2.message : "Unknown error");
3041
+ process.exit(1);
3042
+ }
3043
+ });
3044
+ var reviewCommand = new Command22("review").description("Interactively review and clean up stale memories").option("--days <n>", "Staleness threshold in days (default: 90)", "90").action(async (opts) => {
3045
+ try {
3046
+ const db = await getDb();
3047
+ const days = parseInt(opts.days, 10);
3048
+ const result = await db.execute({
3049
+ sql: `
3050
+ SELECT id, content, type, scope, created_at, updated_at,
3051
+ CAST((julianday('now') - julianday(COALESCE(updated_at, created_at))) AS INTEGER) as days_old
3052
+ FROM memories
3053
+ WHERE deleted_at IS NULL
3054
+ AND (julianday('now') - julianday(COALESCE(updated_at, created_at))) > ?
3055
+ ORDER BY days_old DESC
3056
+ `,
3057
+ args: [days]
3058
+ });
3059
+ const stale = result.rows;
3060
+ if (stale.length === 0) {
3061
+ console.log(chalk20.green("\u2713") + ` No stale memories to review.`);
3062
+ return;
3063
+ }
3064
+ console.log(chalk20.bold(`
3065
+ Reviewing ${stale.length} stale memories...
3066
+ `));
3067
+ let kept = 0, deleted = 0, skipped = 0;
3068
+ for (const m of stale) {
3069
+ const icon = TYPE_ICONS4[m.type] || "\u{1F4DD}";
3070
+ console.log(`${icon} ${chalk20.bold(m.type.toUpperCase())} (${m.scope})`);
3071
+ console.log(` "${m.content}"`);
3072
+ console.log(chalk20.dim(` Last updated: ${m.days_old} days ago`));
3073
+ console.log("");
3074
+ const answer = await prompt(" [k]eep [d]elete [s]kip [q]uit > ");
3075
+ if (answer === "q") {
3076
+ console.log("\nExiting review.");
3077
+ break;
3078
+ } else if (answer === "d") {
3079
+ await forgetMemory(m.id);
3080
+ console.log(chalk20.green(" \u2713 Deleted\n"));
3081
+ deleted++;
3082
+ } else if (answer === "k") {
3083
+ await db.execute({
3084
+ sql: "UPDATE memories SET updated_at = datetime('now') WHERE id = ?",
3085
+ args: [m.id]
3086
+ });
3087
+ console.log(chalk20.green(" \u2713 Kept (marked as reviewed)\n"));
3088
+ kept++;
3089
+ } else {
3090
+ console.log(chalk20.dim(" Skipped\n"));
3091
+ skipped++;
3092
+ }
3093
+ }
3094
+ console.log(chalk20.bold("\nReview Summary:"));
3095
+ console.log(` Kept: ${kept}, Deleted: ${deleted}, Skipped: ${skipped}`);
3096
+ } catch (error2) {
3097
+ console.error(chalk20.red("\u2717") + " Review failed:", error2 instanceof Error ? error2.message : "Unknown error");
3098
+ process.exit(1);
3099
+ }
3100
+ });
3101
+
3102
+ // src/commands/link.ts
3103
+ import { Command as Command23 } from "commander";
3104
+ import chalk21 from "chalk";
3105
+ import { nanoid as nanoid3 } from "nanoid";
3106
+ var LINK_TYPES = ["related", "supports", "supersedes", "contradicts"];
3107
+ var TYPE_ICONS5 = {
3108
+ rule: "\u{1F4CC}",
3109
+ decision: "\u{1F4A1}",
3110
+ fact: "\u{1F4CB}",
3111
+ note: "\u{1F4DD}"
3112
+ };
3113
+ async function ensureLinksTable() {
3114
+ const db = await getDb();
3115
+ await db.execute(`
3116
+ CREATE TABLE IF NOT EXISTS memory_links (
3117
+ id TEXT PRIMARY KEY,
3118
+ source_id TEXT NOT NULL,
3119
+ target_id TEXT NOT NULL,
3120
+ link_type TEXT NOT NULL DEFAULT 'related',
3121
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
3122
+ )
3123
+ `);
3124
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_links_source ON memory_links(source_id)`);
3125
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_links_target ON memory_links(target_id)`);
3126
+ }
3127
+ async function getLinkedMemories(memoryId) {
3128
+ const db = await getDb();
3129
+ await ensureLinksTable();
3130
+ const outgoing = await db.execute({
3131
+ sql: `
3132
+ SELECT m.*, ml.link_type
3133
+ FROM memory_links ml
3134
+ JOIN memories m ON ml.target_id = m.id
3135
+ WHERE ml.source_id = ? AND m.deleted_at IS NULL
3136
+ `,
3137
+ args: [memoryId]
3138
+ });
3139
+ const incoming = await db.execute({
3140
+ sql: `
3141
+ SELECT m.*, ml.link_type
3142
+ FROM memory_links ml
3143
+ JOIN memories m ON ml.source_id = m.id
3144
+ WHERE ml.target_id = ? AND m.deleted_at IS NULL
3145
+ `,
3146
+ args: [memoryId]
3147
+ });
3148
+ const results = [];
3149
+ for (const row of outgoing.rows) {
3150
+ results.push({
3151
+ memory: row,
3152
+ linkType: row.link_type,
3153
+ direction: "to"
3154
+ });
3155
+ }
3156
+ for (const row of incoming.rows) {
3157
+ results.push({
3158
+ memory: row,
3159
+ linkType: row.link_type,
3160
+ direction: "from"
3161
+ });
3162
+ }
3163
+ return results;
3164
+ }
3165
+ var linkCommand = new Command23("link").description("Link two related memories together").argument("<id1>", "First memory ID").argument("<id2>", "Second memory ID").option("-t, --type <type>", "Link type: related, supports, supersedes, contradicts", "related").action(async (id1, id2, opts) => {
3166
+ try {
3167
+ await ensureLinksTable();
3168
+ const db = await getDb();
3169
+ if (!LINK_TYPES.includes(opts.type)) {
3170
+ console.error(chalk21.red("\u2717") + ` Invalid link type. Valid: ${LINK_TYPES.join(", ")}`);
3171
+ process.exit(1);
3172
+ }
3173
+ const m1 = await getMemoryById(id1);
3174
+ const m2 = await getMemoryById(id2);
3175
+ if (!m1) {
3176
+ console.error(chalk21.red("\u2717") + ` Memory ${id1} not found`);
3177
+ process.exit(1);
3178
+ }
3179
+ if (!m2) {
3180
+ console.error(chalk21.red("\u2717") + ` Memory ${id2} not found`);
3181
+ process.exit(1);
3182
+ }
3183
+ const existing = await db.execute({
3184
+ sql: `SELECT id FROM memory_links WHERE
3185
+ (source_id = ? AND target_id = ?) OR (source_id = ? AND target_id = ?)`,
3186
+ args: [id1, id2, id2, id1]
3187
+ });
3188
+ if (existing.rows.length > 0) {
3189
+ console.log(chalk21.yellow("!") + " These memories are already linked");
3190
+ return;
3191
+ }
3192
+ const linkId = nanoid3(12);
3193
+ await db.execute({
3194
+ sql: `INSERT INTO memory_links (id, source_id, target_id, link_type) VALUES (?, ?, ?, ?)`,
3195
+ args: [linkId, id1, id2, opts.type]
3196
+ });
3197
+ const icon1 = TYPE_ICONS5[m1.type] || "\u{1F4DD}";
3198
+ const icon2 = TYPE_ICONS5[m2.type] || "\u{1F4DD}";
3199
+ console.log(chalk21.green("\u2713") + " Linked memories:");
3200
+ console.log(` ${icon1} ${chalk21.dim(id1)} "${m1.content.slice(0, 40)}..."`);
3201
+ console.log(` \u2193 ${chalk21.cyan(opts.type)}`);
3202
+ console.log(` ${icon2} ${chalk21.dim(id2)} "${m2.content.slice(0, 40)}..."`);
3203
+ } catch (error2) {
3204
+ console.error(chalk21.red("\u2717") + " Failed:", error2 instanceof Error ? error2.message : "Unknown error");
3205
+ process.exit(1);
3206
+ }
3207
+ });
3208
+ var unlinkCommand = new Command23("unlink").description("Remove link between two memories").argument("<id1>", "First memory ID").argument("<id2>", "Second memory ID").action(async (id1, id2) => {
3209
+ try {
3210
+ await ensureLinksTable();
3211
+ const db = await getDb();
3212
+ const result = await db.execute({
3213
+ sql: `DELETE FROM memory_links WHERE
3214
+ (source_id = ? AND target_id = ?) OR (source_id = ? AND target_id = ?)`,
3215
+ args: [id1, id2, id2, id1]
3216
+ });
3217
+ if (result.rowsAffected === 0) {
3218
+ console.log(chalk21.yellow("!") + " No link found between these memories");
3219
+ } else {
3220
+ console.log(chalk21.green("\u2713") + ` Unlinked ${id1} and ${id2}`);
3221
+ }
3222
+ } catch (error2) {
3223
+ console.error(chalk21.red("\u2717") + " Failed:", error2 instanceof Error ? error2.message : "Unknown error");
3224
+ process.exit(1);
3225
+ }
3226
+ });
3227
+ var showCommand = new Command23("show").description("Show a memory with its linked memories").argument("<id>", "Memory ID").option("--links", "Include linked memories").action(async (id, opts) => {
3228
+ try {
3229
+ const memory = await getMemoryById(id);
3230
+ if (!memory) {
3231
+ console.error(chalk21.red("\u2717") + ` Memory ${id} not found`);
3232
+ process.exit(1);
3233
+ }
3234
+ const icon = TYPE_ICONS5[memory.type] || "\u{1F4DD}";
3235
+ const scope = memory.scope === "global" ? "Global" : "Project";
3236
+ console.log("");
3237
+ console.log(`${icon} ${chalk21.bold(memory.type.toUpperCase())} (${scope})`);
3238
+ console.log(chalk21.dim(`ID: ${memory.id}`));
3239
+ console.log(chalk21.dim(`Created: ${memory.created_at}`));
3240
+ if (memory.tags) console.log(chalk21.dim(`Tags: ${memory.tags}`));
3241
+ console.log("");
3242
+ console.log(memory.content);
3243
+ if (opts.links) {
3244
+ await ensureLinksTable();
3245
+ const linked = await getLinkedMemories(id);
3246
+ if (linked.length > 0) {
3247
+ console.log("");
3248
+ console.log(chalk21.bold("Linked Memories:"));
3249
+ for (const { memory: m, linkType, direction } of linked) {
3250
+ const mIcon = TYPE_ICONS5[m.type] || "\u{1F4DD}";
3251
+ const arrow = direction === "to" ? "\u2192" : "\u2190";
3252
+ const preview = m.content.length > 50 ? m.content.slice(0, 47) + "..." : m.content;
3253
+ console.log(` ${arrow} ${chalk21.cyan(linkType)}: ${mIcon} ${preview}`);
3254
+ }
3255
+ }
3256
+ }
3257
+ console.log("");
3258
+ } catch (error2) {
3259
+ console.error(chalk21.red("\u2717") + " Failed:", error2 instanceof Error ? error2.message : "Unknown error");
3260
+ process.exit(1);
3261
+ }
3262
+ });
3263
+
3264
+ // src/commands/template.ts
3265
+ import { Command as Command24 } from "commander";
3266
+ import chalk22 from "chalk";
3267
+ var templateCommand = new Command24("template").description("Manage and use memory templates");
3268
+ templateCommand.command("list").description("List available templates").action(() => {
3269
+ const templates = listTemplates();
3270
+ console.log(chalk22.bold("\nAvailable Templates:\n"));
3271
+ for (const t of templates) {
3272
+ const typeIcon = t.type === "rule" ? "\u{1F4CC}" : t.type === "decision" ? "\u{1F4A1}" : t.type === "fact" ? "\u{1F4CB}" : "\u{1F4DD}";
3273
+ console.log(` ${chalk22.cyan(t.name.padEnd(15))} ${typeIcon} ${t.description}`);
3274
+ }
3275
+ console.log("");
3276
+ console.log(chalk22.dim("Use: memories template use <name>"));
3277
+ console.log("");
3278
+ });
3279
+ templateCommand.command("show <name>").description("Show template details and fields").action((name) => {
3280
+ const template = getTemplate(name);
3281
+ if (!template) {
3282
+ console.error(chalk22.red("\u2717") + ` Template "${name}" not found`);
3283
+ console.log(chalk22.dim("Run 'memories template list' to see available templates"));
3284
+ process.exit(1);
3285
+ }
3286
+ const typeIcon = template.type === "rule" ? "\u{1F4CC}" : template.type === "decision" ? "\u{1F4A1}" : template.type === "fact" ? "\u{1F4CB}" : "\u{1F4DD}";
3287
+ console.log("");
3288
+ console.log(chalk22.bold(template.name) + ` ${typeIcon} ${template.type}`);
3289
+ console.log(chalk22.dim(template.description));
3290
+ console.log("");
3291
+ console.log(chalk22.bold("Fields:"));
3292
+ for (const field of template.fields) {
3293
+ const required = field.required ? chalk22.red("*") : chalk22.dim("(optional)");
3294
+ console.log(` ${field.name.padEnd(15)} ${required} ${field.prompt}`);
3295
+ }
3296
+ console.log("");
3297
+ });
3298
+ templateCommand.command("use <name>").description("Create a memory using a template").option("-g, --global", "Store as global memory").action(async (name, opts) => {
3299
+ const template = getTemplate(name);
3300
+ if (!template) {
3301
+ console.error(chalk22.red("\u2717") + ` Template "${name}" not found`);
3302
+ console.log(chalk22.dim("Run 'memories template list' to see available templates"));
3303
+ process.exit(1);
3304
+ }
3305
+ console.log("");
3306
+ console.log(chalk22.bold(`Using template: ${template.name}`));
3307
+ console.log(chalk22.dim(template.description));
3308
+ console.log("");
3309
+ try {
3310
+ const content = await fillTemplate(template);
3311
+ const memory = await addMemory(content, {
3312
+ type: template.type,
3313
+ global: opts.global
3314
+ });
3315
+ console.log("");
3316
+ console.log(chalk22.green("\u2713") + ` Created ${template.type}: ${chalk22.dim(memory.id)}`);
3317
+ console.log(chalk22.dim(` "${content}"`));
3318
+ } catch (error2) {
3319
+ if (error2.message?.includes("User force closed")) {
3320
+ console.log("\nCancelled.");
3321
+ return;
3322
+ }
3323
+ throw error2;
3324
+ }
3325
+ });
3326
+
3327
+ // src/commands/history.ts
3328
+ import { Command as Command25 } from "commander";
3329
+ import chalk23 from "chalk";
3330
+ var TYPE_ICONS6 = {
3331
+ rule: "\u{1F4CC}",
3332
+ decision: "\u{1F4A1}",
3333
+ fact: "\u{1F4CB}",
3334
+ note: "\u{1F4DD}"
3335
+ };
3336
+ async function ensureHistoryTable() {
3337
+ const db = await getDb();
3338
+ await db.execute(`
3339
+ CREATE TABLE IF NOT EXISTS memory_history (
3340
+ id TEXT PRIMARY KEY,
3341
+ memory_id TEXT NOT NULL,
3342
+ content TEXT NOT NULL,
3343
+ tags TEXT,
3344
+ type TEXT NOT NULL,
3345
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
3346
+ change_type TEXT NOT NULL
3347
+ )
3348
+ `);
3349
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_history_memory ON memory_history(memory_id)`);
3350
+ }
3351
+ async function recordHistory(memory, changeType) {
3352
+ await ensureHistoryTable();
3353
+ const db = await getDb();
3354
+ const id = `${memory.id}-${Date.now()}`;
3355
+ await db.execute({
3356
+ sql: `INSERT INTO memory_history (id, memory_id, content, tags, type, change_type) VALUES (?, ?, ?, ?, ?, ?)`,
3357
+ args: [id, memory.id, memory.content, memory.tags, memory.type, changeType]
3358
+ });
3359
+ }
3360
+ var historyCommand = new Command25("history").description("View version history of a memory").argument("<id>", "Memory ID").option("--json", "Output as JSON").action(async (id, opts) => {
3361
+ try {
3362
+ await ensureHistoryTable();
3363
+ const db = await getDb();
3364
+ const memory = await getMemoryById(id);
3365
+ const result = await db.execute({
3366
+ sql: `SELECT *, ROW_NUMBER() OVER (ORDER BY changed_at ASC) as version
3367
+ FROM memory_history
3368
+ WHERE memory_id = ?
3369
+ ORDER BY changed_at DESC`,
3370
+ args: [id]
3371
+ });
3372
+ const history = result.rows;
3373
+ if (opts.json) {
3374
+ console.log(JSON.stringify({ current: memory, history }, null, 2));
3375
+ return;
3376
+ }
3377
+ if (!memory && history.length === 0) {
3378
+ console.error(chalk23.red("\u2717") + ` Memory ${id} not found`);
3379
+ process.exit(1);
3380
+ }
3381
+ const icon = memory ? TYPE_ICONS6[memory.type] || "\u{1F4DD}" : "\u{1F4DD}";
3382
+ console.log("");
3383
+ if (memory) {
3384
+ console.log(`${icon} ${chalk23.bold(memory.content)}`);
3385
+ console.log(chalk23.dim(`ID: ${id}`));
3386
+ } else {
3387
+ console.log(chalk23.dim(`Memory ${id} (deleted)`));
3388
+ }
3389
+ console.log("");
3390
+ if (history.length === 0) {
3391
+ console.log(chalk23.dim("No version history recorded."));
3392
+ console.log(chalk23.dim("History is recorded when memories are updated."));
3393
+ return;
3394
+ }
3395
+ console.log(chalk23.bold("History:"));
3396
+ console.log(chalk23.dim("\u2500".repeat(60)));
3397
+ for (const entry of history) {
3398
+ const changeIcon = entry.change_type === "created" ? "+" : entry.change_type === "updated" ? "~" : "-";
3399
+ const changeColor = entry.change_type === "created" ? chalk23.green : entry.change_type === "updated" ? chalk23.yellow : chalk23.red;
3400
+ const date = new Date(entry.changed_at).toLocaleDateString();
3401
+ const time = new Date(entry.changed_at).toLocaleTimeString();
3402
+ console.log(` ${changeColor(changeIcon)} v${entry.version} ${chalk23.dim(date + " " + time)}`);
3403
+ console.log(` "${entry.content.slice(0, 60)}${entry.content.length > 60 ? "..." : ""}"`);
3404
+ }
3405
+ console.log(chalk23.dim("\u2500".repeat(60)));
3406
+ console.log(chalk23.dim(`
3407
+ Use 'memories revert ${id} --to <version>' to restore a previous version`));
3408
+ } catch (error2) {
3409
+ console.error(chalk23.red("\u2717") + " Failed:", error2 instanceof Error ? error2.message : "Unknown error");
3410
+ process.exit(1);
3411
+ }
3412
+ });
3413
+ var revertCommand = new Command25("revert").description("Revert a memory to a previous version").argument("<id>", "Memory ID").requiredOption("--to <version>", "Version number to revert to").action(async (id, opts) => {
3414
+ try {
3415
+ await ensureHistoryTable();
3416
+ const db = await getDb();
3417
+ const version = parseInt(opts.to.replace("v", ""), 10);
3418
+ if (isNaN(version) || version < 1) {
3419
+ console.error(chalk23.red("\u2717") + " Invalid version number");
3420
+ process.exit(1);
3421
+ }
3422
+ const result = await db.execute({
3423
+ sql: `SELECT *, ROW_NUMBER() OVER (ORDER BY changed_at ASC) as version
3424
+ FROM memory_history
3425
+ WHERE memory_id = ?`,
3426
+ args: [id]
3427
+ });
3428
+ const history = result.rows;
3429
+ const targetEntry = history.find((h) => h.version === version);
3430
+ if (!targetEntry) {
3431
+ console.error(chalk23.red("\u2717") + ` Version ${version} not found for memory ${id}`);
3432
+ process.exit(1);
3433
+ }
3434
+ const current = await getMemoryById(id);
3435
+ if (current) {
3436
+ await recordHistory(current, "updated");
3437
+ }
3438
+ const updated = await updateMemory(id, {
3439
+ content: targetEntry.content,
3440
+ tags: targetEntry.tags ? targetEntry.tags.split(",") : void 0
3441
+ });
3442
+ if (!updated) {
3443
+ console.error(chalk23.red("\u2717") + ` Failed to revert memory ${id}`);
3444
+ process.exit(1);
3445
+ }
3446
+ console.log(chalk23.green("\u2713") + ` Reverted memory ${chalk23.dim(id)} to version ${version}`);
3447
+ console.log(chalk23.dim(` "${targetEntry.content.slice(0, 60)}${targetEntry.content.length > 60 ? "..." : ""}"`));
3448
+ } catch (error2) {
3449
+ console.error(chalk23.red("\u2717") + " Failed:", error2 instanceof Error ? error2.message : "Unknown error");
3450
+ process.exit(1);
3451
+ }
3452
+ });
3453
+
3454
+ // src/commands/embed.ts
3455
+ import { Command as Command26 } from "commander";
3456
+ import chalk24 from "chalk";
3084
3457
  import ora2 from "ora";
3458
+ var embedCommand = new Command26("embed").description("Generate embeddings for memories (enables semantic search)").option("--all", "Re-embed all memories, even those with existing embeddings").option("--dry-run", "Show what would be embedded without doing it").action(async (opts) => {
3459
+ try {
3460
+ await ensureEmbeddingsSchema();
3461
+ const db = await getDb();
3462
+ let sql = "SELECT id, content FROM memories WHERE deleted_at IS NULL";
3463
+ if (!opts.all) {
3464
+ sql += " AND embedding IS NULL";
3465
+ }
3466
+ const result = await db.execute(sql);
3467
+ const memories = result.rows;
3468
+ if (memories.length === 0) {
3469
+ console.log(chalk24.green("\u2713") + " All memories already have embeddings.");
3470
+ return;
3471
+ }
3472
+ if (opts.dryRun) {
3473
+ console.log(chalk24.bold(`Would embed ${memories.length} memories:
3474
+ `));
3475
+ for (const m of memories.slice(0, 10)) {
3476
+ const preview = m.content.length > 60 ? m.content.slice(0, 57) + "..." : m.content;
3477
+ console.log(` ${chalk24.dim(m.id)} ${preview}`);
3478
+ }
3479
+ if (memories.length > 10) {
3480
+ console.log(chalk24.dim(` ... and ${memories.length - 10} more`));
3481
+ }
3482
+ return;
3483
+ }
3484
+ console.log(chalk24.bold(`Embedding ${memories.length} memories...
3485
+ `));
3486
+ console.log(chalk24.dim("First run downloads the model (~30MB). Subsequent runs are faster.\n"));
3487
+ const spinner = ora2("Loading embedding model...").start();
3488
+ let embedded = 0;
3489
+ let failed = 0;
3490
+ for (const m of memories) {
3491
+ try {
3492
+ spinner.text = `Embedding ${embedded + 1}/${memories.length}: ${m.content.slice(0, 40)}...`;
3493
+ const embedding = await getEmbedding(m.content);
3494
+ await storeEmbedding(m.id, embedding);
3495
+ embedded++;
3496
+ } catch (error2) {
3497
+ failed++;
3498
+ }
3499
+ }
3500
+ spinner.stop();
3501
+ console.log(chalk24.green("\u2713") + ` Embedded ${embedded} memories`);
3502
+ if (failed > 0) {
3503
+ console.log(chalk24.yellow("\u26A0") + ` ${failed} memories failed to embed`);
3504
+ }
3505
+ console.log("");
3506
+ console.log(chalk24.dim("Now you can use semantic search:"));
3507
+ console.log(chalk24.cyan(' memories search --semantic "your query"'));
3508
+ } catch (error2) {
3509
+ console.error(chalk24.red("\u2717") + " Embedding failed:", error2 instanceof Error ? error2.message : "Unknown error");
3510
+ process.exit(1);
3511
+ }
3512
+ });
3513
+
3514
+ // src/commands/login.ts
3515
+ import { Command as Command27 } from "commander";
3516
+ import chalk25 from "chalk";
3517
+ import ora3 from "ora";
3085
3518
  import { randomBytes } from "crypto";
3086
3519
  import { execFile } from "child_process";
3087
3520
  var DEFAULT_API_URL = "https://memories.sh";
@@ -3091,29 +3524,29 @@ function openBrowser(url) {
3091
3524
  execFile(cmd, [url], () => {
3092
3525
  });
3093
3526
  }
3094
- var loginCommand = new Command21("login").description("Log in to memories.sh to enable cloud sync").option("--api-url <url>", "API base URL", DEFAULT_API_URL).action(async (opts) => {
3527
+ var loginCommand = new Command27("login").description("Log in to memories.sh to enable cloud sync").option("--api-url <url>", "API base URL", DEFAULT_API_URL).action(async (opts) => {
3095
3528
  banner();
3096
3529
  const existing = await readAuth();
3097
3530
  if (existing) {
3098
- warn(`Already logged in as ${chalk19.bold(existing.email)}`);
3099
- dim(`Run ${chalk19.cyan("memories logout")} to sign out first.`);
3531
+ warn(`Already logged in as ${chalk25.bold(existing.email)}`);
3532
+ dim(`Run ${chalk25.cyan("memories logout")} to sign out first.`);
3100
3533
  return;
3101
3534
  }
3102
3535
  box(
3103
- chalk19.bold("Pro features include:\n\n") + chalk19.dim("\u2192 ") + "Cloud sync & backup\n" + chalk19.dim("\u2192 ") + "Cross-device access\n" + chalk19.dim("\u2192 ") + "Web dashboard\n" + chalk19.dim("\u2192 ") + "Priority support",
3536
+ chalk25.bold("Pro features include:\n\n") + chalk25.dim("\u2192 ") + "Cloud sync & backup\n" + chalk25.dim("\u2192 ") + "Cross-device access\n" + chalk25.dim("\u2192 ") + "Web dashboard\n" + chalk25.dim("\u2192 ") + "Priority support",
3104
3537
  "Upgrade to Pro"
3105
3538
  );
3106
3539
  const code = randomBytes(16).toString("hex");
3107
3540
  const authUrl = `${opts.apiUrl}/app/auth/cli?code=${code}`;
3108
- console.log(chalk19.bold("Open this URL in your browser:\n"));
3109
- console.log(` ${chalk19.cyan(authUrl)}
3541
+ console.log(chalk25.bold("Open this URL in your browser:\n"));
3542
+ console.log(` ${chalk25.cyan(authUrl)}
3110
3543
  `);
3111
3544
  try {
3112
3545
  openBrowser(authUrl);
3113
3546
  dim("Browser opened automatically");
3114
3547
  } catch {
3115
3548
  }
3116
- const spinner = ora2({
3549
+ const spinner = ora3({
3117
3550
  text: "Waiting for authorization...",
3118
3551
  color: "magenta"
3119
3552
  }).start();
@@ -3136,11 +3569,11 @@ var loginCommand = new Command21("login").description("Log in to memories.sh to
3136
3569
  });
3137
3570
  spinner.stop();
3138
3571
  console.log("");
3139
- success(`Logged in as ${chalk19.bold(data.email)}`);
3572
+ success(`Logged in as ${chalk25.bold(data.email)}`);
3140
3573
  dim("Your cloud database has been provisioned automatically.");
3141
3574
  nextSteps([
3142
- `${chalk19.cyan("memories sync")} ${chalk19.dim("to sync your memories")}`,
3143
- `${chalk19.cyan("memories.sh/app")} ${chalk19.dim("to view your dashboard")}`
3575
+ `${chalk25.cyan("memories sync")} ${chalk25.dim("to sync your memories")}`,
3576
+ `${chalk25.cyan("memories.sh/app")} ${chalk25.dim("to view your dashboard")}`
3144
3577
  ]);
3145
3578
  return;
3146
3579
  }
@@ -3156,7 +3589,7 @@ var loginCommand = new Command21("login").description("Log in to memories.sh to
3156
3589
  spinner.stop();
3157
3590
  error("Authorization timed out. Please try again.");
3158
3591
  });
3159
- var logoutCommand = new Command21("logout").description("Log out of memories.sh").action(async () => {
3592
+ var logoutCommand = new Command27("logout").description("Log out of memories.sh").action(async () => {
3160
3593
  const existing = await readAuth();
3161
3594
  if (!existing) {
3162
3595
  info("Not logged in.");
@@ -3167,7 +3600,7 @@ var logoutCommand = new Command21("logout").description("Log out of memories.sh"
3167
3600
  });
3168
3601
 
3169
3602
  // src/index.ts
3170
- var program = new Command22().name("memories").description("A local-first memory layer for AI agents").version("0.2.1");
3603
+ var program = new Command28().name("memories").description("A local-first memory layer for AI agents").version("0.2.2");
3171
3604
  program.addCommand(initCommand);
3172
3605
  program.addCommand(addCommand);
3173
3606
  program.addCommand(recallCommand);
@@ -3188,6 +3621,16 @@ program.addCommand(hookCommand);
3188
3621
  program.addCommand(ingestCommand);
3189
3622
  program.addCommand(diffCommand);
3190
3623
  program.addCommand(tagCommand);
3624
+ program.addCommand(validateCommand);
3625
+ program.addCommand(staleCommand);
3626
+ program.addCommand(reviewCommand);
3627
+ program.addCommand(linkCommand);
3628
+ program.addCommand(unlinkCommand);
3629
+ program.addCommand(showCommand);
3630
+ program.addCommand(templateCommand);
3631
+ program.addCommand(historyCommand);
3632
+ program.addCommand(revertCommand);
3633
+ program.addCommand(embedCommand);
3191
3634
  program.addCommand(loginCommand);
3192
3635
  program.addCommand(logoutCommand);
3193
3636
  program.parse();