@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/chunk-4GNYCHD4.js +146 -0
- package/dist/chunk-BDPB4ABQ.js +155 -0
- package/dist/chunk-JMN3U7AI.js +373 -0
- package/dist/embeddings-GH4HB6X7.js +26 -0
- package/dist/index.js +1023 -580
- package/dist/memory-57CD5DYG.js +28 -0
- package/package.json +2 -1
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
|
|
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/
|
|
11
|
-
import {
|
|
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
|
-
|
|
17
|
-
|
|
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 (!
|
|
45
|
+
if (!existsSync(AUTH_FILE)) return null;
|
|
474
46
|
try {
|
|
475
|
-
const raw = await
|
|
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
|
|
483
|
-
await
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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(`
|
|
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("
|
|
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
|
-
|
|
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
|
|
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(
|
|
177
|
+
dim(`+ ${rule}`);
|
|
604
178
|
}
|
|
605
179
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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("
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1266
|
-
import { join as
|
|
1267
|
-
import { existsSync as
|
|
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 =
|
|
1273
|
-
await
|
|
1274
|
-
const configPath =
|
|
1275
|
-
if (!
|
|
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
|
|
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 =
|
|
1291
|
-
if (!
|
|
1292
|
-
const raw = await
|
|
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
|
|
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-${
|
|
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
|
|
1769
|
-
import { join as
|
|
1770
|
-
import { homedir as
|
|
1771
|
-
var DB_PATH =
|
|
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 (
|
|
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 (
|
|
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
|
|
1879
|
-
import { existsSync as
|
|
1880
|
-
import { dirname, resolve, join as
|
|
1881
|
-
import { homedir as
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
|
2058
|
-
await
|
|
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
|
|
2082
|
-
const dataDir = process.env.MEMORIES_DATA_DIR ??
|
|
2083
|
-
return
|
|
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 =
|
|
2087
|
-
if (!
|
|
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
|
|
2203
|
-
import { nanoid as
|
|
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 =
|
|
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
|
|
2358
|
-
import { join as
|
|
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 =
|
|
2367
|
-
if (
|
|
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
|
|
2499
|
-
import { existsSync as
|
|
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
|
|
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 =
|
|
2523
|
-
if (
|
|
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 =
|
|
2527
|
-
if (
|
|
2289
|
+
const huskyLegacyPath = join7(".husky", "_", hookName);
|
|
2290
|
+
if (existsSync6(join7(".husky", "_"))) {
|
|
2528
2291
|
return { path: huskyLegacyPath, type: "husky" };
|
|
2529
2292
|
}
|
|
2530
|
-
if (
|
|
2293
|
+
if (existsSync6(huskyPath)) {
|
|
2531
2294
|
return { path: huskyPath, type: "husky" };
|
|
2532
2295
|
}
|
|
2533
|
-
return { path:
|
|
2296
|
+
return { path: join7(gitDir, "hooks", hookName), type: "git" };
|
|
2534
2297
|
}
|
|
2535
2298
|
function detectLintStaged() {
|
|
2536
2299
|
try {
|
|
2537
|
-
if (!
|
|
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 (
|
|
2555
|
-
const content = await
|
|
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
|
|
2323
|
+
await writeFile5(hookPath, content.trimEnd() + "\n" + HOOK_SNIPPET + "\n", "utf-8");
|
|
2561
2324
|
} else {
|
|
2562
|
-
await
|
|
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 (!
|
|
2351
|
+
if (!existsSync6(hookPath)) {
|
|
2589
2352
|
console.log(chalk15.dim("No hook file found."));
|
|
2590
2353
|
return;
|
|
2591
2354
|
}
|
|
2592
|
-
const content = await
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
2647
|
-
import { existsSync as
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
2806
|
-
import { existsSync as
|
|
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 (!
|
|
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
|
|
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/
|
|
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
|
|
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 ${
|
|
3099
|
-
dim(`Run ${
|
|
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
|
-
|
|
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(
|
|
3109
|
-
console.log(` ${
|
|
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 =
|
|
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 ${
|
|
3572
|
+
success(`Logged in as ${chalk25.bold(data.email)}`);
|
|
3140
3573
|
dim("Your cloud database has been provisioned automatically.");
|
|
3141
3574
|
nextSteps([
|
|
3142
|
-
`${
|
|
3143
|
-
`${
|
|
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
|
|
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
|
|
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();
|