open-think 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
+
3
+ // src/lib/paths.ts
4
+ import path from "path";
5
+ import fs from "fs";
6
+ function getHome() {
7
+ const home = process.env.HOME;
8
+ if (!home) {
9
+ throw new Error("HOME environment variable is not set");
10
+ }
11
+ return home;
12
+ }
13
+ function sanitizeName(name) {
14
+ if (!name || /[\/\\\.]{2}/.test(name) || /[^a-zA-Z0-9_-]/.test(name)) {
15
+ throw new Error(`Invalid cortex name: "${name}". Use only alphanumeric characters, hyphens, and underscores.`);
16
+ }
17
+ return name;
18
+ }
19
+ function getThinkHome() {
20
+ const thinkHome = process.env.THINK_HOME;
21
+ if (thinkHome === void 0 || thinkHome === "") return null;
22
+ return thinkHome;
23
+ }
24
+ function getThinkDir() {
25
+ return getThinkHome() ?? path.join(getHome(), ".think");
26
+ }
27
+ function getThinkConfigDir() {
28
+ const thinkHome = getThinkHome();
29
+ if (thinkHome) return path.join(thinkHome, "config");
30
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(getHome(), ".config");
31
+ return path.join(xdgConfig, "think");
32
+ }
33
+ function getThinkDataDir() {
34
+ const thinkHome = getThinkHome();
35
+ if (thinkHome) return path.join(thinkHome, "data");
36
+ const xdgData = process.env.XDG_DATA_HOME || path.join(getHome(), ".local", "share");
37
+ return path.join(xdgData, "think");
38
+ }
39
+ function getEngramsDir() {
40
+ return path.join(getThinkDir(), "engrams");
41
+ }
42
+ function getEngramDbPath(cortexName) {
43
+ return path.join(getEngramsDir(), `${sanitizeName(cortexName)}.db`);
44
+ }
45
+ function getRepoPath() {
46
+ return path.join(getThinkDir(), "repo");
47
+ }
48
+ function getLongtermDir() {
49
+ return path.join(getThinkDir(), "longterm");
50
+ }
51
+ function getLongtermPath(cortexName) {
52
+ return path.join(getLongtermDir(), `${sanitizeName(cortexName)}.md`);
53
+ }
54
+ function getCuratorMdPath() {
55
+ return path.join(getThinkDir(), "curator.md");
56
+ }
57
+ function ensureThinkDirs() {
58
+ fs.mkdirSync(getEngramsDir(), { recursive: true });
59
+ fs.mkdirSync(getLongtermDir(), { recursive: true });
60
+ }
61
+
62
+ export {
63
+ getThinkConfigDir,
64
+ getThinkDataDir,
65
+ getEngramsDir,
66
+ getEngramDbPath,
67
+ getRepoPath,
68
+ getLongtermPath,
69
+ getCuratorMdPath,
70
+ ensureThinkDirs
71
+ };
@@ -1,88 +1,33 @@
1
1
  #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
+ import {
3
+ getRepoPath,
4
+ getThinkConfigDir
5
+ } from "./chunk-DCTG6IK4.js";
2
6
 
3
7
  // src/lib/git.ts
4
8
  import { execFileSync } from "child_process";
5
- import fs3 from "fs";
6
- import path3 from "path";
9
+ import fs2 from "fs";
10
+ import path2 from "path";
7
11
 
8
- // src/lib/paths.ts
12
+ // src/lib/config.ts
9
13
  import path from "path";
10
14
  import fs from "fs";
11
- function getHome() {
12
- const home = process.env.HOME;
13
- if (!home) {
14
- throw new Error("HOME environment variable is not set");
15
- }
16
- return home;
17
- }
18
- function sanitizeName(name) {
19
- if (!name || /[\/\\\.]{2}/.test(name) || /[^a-zA-Z0-9_-]/.test(name)) {
20
- throw new Error(`Invalid cortex name: "${name}". Use only alphanumeric characters, hyphens, and underscores.`);
21
- }
22
- return name;
23
- }
24
- function getThinkHome() {
25
- const thinkHome = process.env.THINK_HOME;
26
- if (thinkHome === void 0 || thinkHome === "") return null;
27
- return thinkHome;
28
- }
29
- function getThinkDir() {
30
- return getThinkHome() ?? path.join(getHome(), ".think");
31
- }
32
- function getThinkConfigDir() {
33
- const thinkHome = getThinkHome();
34
- if (thinkHome) return path.join(thinkHome, "config");
35
- const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(getHome(), ".config");
36
- return path.join(xdgConfig, "think");
37
- }
38
- function getThinkDataDir() {
39
- const thinkHome = getThinkHome();
40
- if (thinkHome) return path.join(thinkHome, "data");
41
- const xdgData = process.env.XDG_DATA_HOME || path.join(getHome(), ".local", "share");
42
- return path.join(xdgData, "think");
43
- }
44
- function getEngramsDir() {
45
- return path.join(getThinkDir(), "engrams");
46
- }
47
- function getEngramDbPath(cortexName) {
48
- return path.join(getEngramsDir(), `${sanitizeName(cortexName)}.db`);
49
- }
50
- function getRepoPath() {
51
- return path.join(getThinkDir(), "repo");
52
- }
53
- function getLongtermDir() {
54
- return path.join(getThinkDir(), "longterm");
55
- }
56
- function getLongtermPath(cortexName) {
57
- return path.join(getLongtermDir(), `${sanitizeName(cortexName)}.md`);
58
- }
59
- function getCuratorMdPath() {
60
- return path.join(getThinkDir(), "curator.md");
61
- }
62
- function ensureThinkDirs() {
63
- fs.mkdirSync(getEngramsDir(), { recursive: true });
64
- fs.mkdirSync(getLongtermDir(), { recursive: true });
65
- }
66
-
67
- // src/lib/config.ts
68
- import path2 from "path";
69
- import fs2 from "fs";
70
15
  import { v4 as uuidv4 } from "uuid";
71
16
  function getConfigDir() {
72
17
  return getThinkConfigDir();
73
18
  }
74
19
  function configPath() {
75
- return path2.join(getConfigDir(), "config.json");
20
+ return path.join(getConfigDir(), "config.json");
76
21
  }
77
22
  function saveConfig(config) {
78
23
  const dir = getConfigDir();
79
- fs2.mkdirSync(dir, { recursive: true });
80
- fs2.writeFileSync(configPath(), JSON.stringify(config, null, 2) + "\n", "utf-8");
24
+ fs.mkdirSync(dir, { recursive: true });
25
+ fs.writeFileSync(configPath(), JSON.stringify(config, null, 2) + "\n", "utf-8");
81
26
  }
82
27
  function getConfig() {
83
28
  const fp = configPath();
84
- if (fs2.existsSync(fp)) {
85
- const raw = fs2.readFileSync(fp, "utf-8");
29
+ if (fs.existsSync(fp)) {
30
+ const raw = fs.readFileSync(fp, "utf-8");
86
31
  return JSON.parse(raw);
87
32
  }
88
33
  const config = {
@@ -108,14 +53,14 @@ function ensureRepoCloned() {
108
53
  throw new Error("No cortex repo configured. Run: think cortex setup");
109
54
  }
110
55
  const repoPath = getRepoPath();
111
- if (fs3.existsSync(path3.join(repoPath, ".git"))) {
56
+ if (fs2.existsSync(path2.join(repoPath, ".git"))) {
112
57
  const remote = runGit(["remote", "get-url", "origin"], repoPath);
113
58
  if (remote !== config.cortex.repo) {
114
59
  throw new Error(`Repo at ${repoPath} points to ${remote}, expected ${config.cortex.repo}`);
115
60
  }
116
61
  return;
117
62
  }
118
- fs3.mkdirSync(repoPath, { recursive: true });
63
+ fs2.mkdirSync(repoPath, { recursive: true });
119
64
  execFileSync("git", ["clone", "--no-checkout", config.cortex.repo, repoPath], {
120
65
  encoding: "utf-8",
121
66
  stdio: ["pipe", "pipe", "pipe"]
@@ -136,7 +81,7 @@ function createOrphanBranch(branchName) {
136
81
  } catch {
137
82
  }
138
83
  const repoPath = getRepoPath();
139
- fs3.writeFileSync(path3.join(repoPath, "000001.jsonl"), "", "utf-8");
84
+ fs2.writeFileSync(path2.join(repoPath, "000001.jsonl"), "", "utf-8");
140
85
  runGit(["add", "000001.jsonl"]);
141
86
  runGit(["commit", "-m", `init: create cortex ${branchName}`]);
142
87
  runGit(["push", "--set-upstream", "origin", branchName]);
@@ -153,7 +98,7 @@ function readFileFromBranch(branchName, filePath) {
153
98
  }
154
99
  function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3, targetFile = "memories.jsonl") {
155
100
  const repoPath = getRepoPath();
156
- const filePath = path3.join(repoPath, targetFile);
101
+ const filePath = path2.join(repoPath, targetFile);
157
102
  try {
158
103
  runGit(["switch", branchName]);
159
104
  } catch {
@@ -172,7 +117,7 @@ function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3, ta
172
117
  }
173
118
  }
174
119
  const content = newLines.join("\n") + "\n";
175
- fs3.appendFileSync(filePath, content, "utf-8");
120
+ fs2.appendFileSync(filePath, content, "utf-8");
176
121
  runGit(["add", targetFile]);
177
122
  runGit(["commit", "-m", commitMessage]);
178
123
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -222,11 +167,11 @@ function migrateToBuckets(branchName) {
222
167
  runGit(["pull", "--rebase", "origin", branchName]);
223
168
  } catch {
224
169
  }
225
- const legacyPath = path3.join(repoPath, "memories.jsonl");
226
- const bucketPath = path3.join(repoPath, "000001.jsonl");
227
- if (fs3.existsSync(legacyPath) && !fs3.existsSync(bucketPath)) {
170
+ const legacyPath = path2.join(repoPath, "memories.jsonl");
171
+ const bucketPath = path2.join(repoPath, "000001.jsonl");
172
+ if (fs2.existsSync(legacyPath) && !fs2.existsSync(bucketPath)) {
228
173
  const preMigrationRef = runGit(["rev-parse", "HEAD"]);
229
- fs3.renameSync(legacyPath, bucketPath);
174
+ fs2.renameSync(legacyPath, bucketPath);
230
175
  runGit(["add", "-A"]);
231
176
  runGit(["commit", "-m", "migrate: memories.jsonl -> 000001.jsonl"]);
232
177
  for (let attempt = 1; attempt <= 3; attempt++) {
@@ -248,12 +193,6 @@ function migrateToBuckets(branchName) {
248
193
  }
249
194
 
250
195
  export {
251
- getThinkDataDir,
252
- getEngramsDir,
253
- getEngramDbPath,
254
- getLongtermPath,
255
- getCuratorMdPath,
256
- ensureThinkDirs,
257
196
  getConfigDir,
258
197
  saveConfig,
259
198
  getConfig,
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
+ import {
3
+ ensureThinkDirs,
4
+ getEngramDbPath
5
+ } from "./chunk-DCTG6IK4.js";
6
+
7
+ // src/db/memory-queries.ts
8
+ import { v7 as uuidv7 } from "uuid";
9
+
10
+ // src/db/engrams.ts
11
+ import { DatabaseSync } from "node:sqlite";
12
+
13
+ // src/db/migrate.ts
14
+ function runMigrations(db, migrations2) {
15
+ db.exec(`
16
+ CREATE TABLE IF NOT EXISTS _migrations (
17
+ version INTEGER PRIMARY KEY NOT NULL,
18
+ applied_at TEXT NOT NULL
19
+ ) STRICT;
20
+ `);
21
+ const currentVersion = db.prepare(
22
+ "SELECT COALESCE(MAX(version), 0) as version FROM _migrations"
23
+ ).get();
24
+ const pending = migrations2.filter((m) => m.version > currentVersion.version).sort((a, b) => a.version - b.version);
25
+ for (const migration of pending) {
26
+ db.exec("BEGIN");
27
+ try {
28
+ migration.up(db);
29
+ db.prepare("INSERT INTO _migrations (version, applied_at) VALUES (?, ?)").run(
30
+ migration.version,
31
+ (/* @__PURE__ */ new Date()).toISOString()
32
+ );
33
+ db.exec("COMMIT");
34
+ } catch (err) {
35
+ db.exec("ROLLBACK");
36
+ throw new Error(`Migration v${migration.version} failed: ${err instanceof Error ? err.message : String(err)}`);
37
+ }
38
+ }
39
+ }
40
+
41
+ // src/db/engrams.ts
42
+ var dbs = /* @__PURE__ */ new Map();
43
+ var migrations = [
44
+ {
45
+ version: 1,
46
+ up: (db) => {
47
+ db.exec(`
48
+ CREATE TABLE IF NOT EXISTS engrams (
49
+ id TEXT PRIMARY KEY NOT NULL,
50
+ content TEXT NOT NULL,
51
+ created_at TEXT NOT NULL,
52
+ expires_at TEXT NOT NULL,
53
+ evaluated_at TEXT,
54
+ promoted INTEGER,
55
+ deleted_at TEXT
56
+ ) STRICT;
57
+ `);
58
+ db.exec(`
59
+ CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts
60
+ USING fts5(content, content='engrams', content_rowid='rowid');
61
+ `);
62
+ db.exec(`
63
+ CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
64
+ INSERT INTO engrams_fts(rowid, content) VALUES (new.rowid, new.content);
65
+ END;
66
+ `);
67
+ db.exec(`
68
+ CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
69
+ INSERT INTO engrams_fts(engrams_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
70
+ END;
71
+ `);
72
+ }
73
+ },
74
+ {
75
+ version: 2,
76
+ up: (db) => {
77
+ db.exec(`
78
+ CREATE TABLE IF NOT EXISTS memories (
79
+ id TEXT PRIMARY KEY NOT NULL,
80
+ ts TEXT NOT NULL,
81
+ author TEXT NOT NULL,
82
+ content TEXT NOT NULL,
83
+ source_ids TEXT NOT NULL DEFAULT '[]',
84
+ created_at TEXT NOT NULL,
85
+ deleted_at TEXT,
86
+ sync_version INTEGER NOT NULL DEFAULT 0
87
+ ) STRICT;
88
+ `);
89
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memories_ts ON memories(ts);");
90
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memories_sync_version ON memories(sync_version);");
91
+ db.exec(`
92
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts
93
+ USING fts5(content, content='memories', content_rowid='rowid');
94
+ `);
95
+ db.exec(`
96
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
97
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
98
+ END;
99
+ `);
100
+ db.exec(`
101
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
102
+ INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
103
+ END;
104
+ `);
105
+ db.exec(`
106
+ CREATE TABLE IF NOT EXISTS longterm_summary (
107
+ id INTEGER PRIMARY KEY CHECK (id = 1),
108
+ content TEXT NOT NULL,
109
+ updated_at TEXT NOT NULL,
110
+ sync_version INTEGER NOT NULL DEFAULT 0
111
+ ) STRICT;
112
+ `);
113
+ db.exec(`
114
+ CREATE TABLE IF NOT EXISTS sync_cursors (
115
+ backend TEXT NOT NULL,
116
+ direction TEXT NOT NULL,
117
+ cursor_value TEXT NOT NULL,
118
+ updated_at TEXT NOT NULL,
119
+ PRIMARY KEY (backend, direction)
120
+ ) STRICT;
121
+ `);
122
+ }
123
+ },
124
+ {
125
+ version: 3,
126
+ up: (db) => {
127
+ db.exec("ALTER TABLE engrams ADD COLUMN episode_key TEXT;");
128
+ db.exec("CREATE INDEX IF NOT EXISTS idx_engrams_episode_key ON engrams(episode_key);");
129
+ db.exec("ALTER TABLE memories ADD COLUMN episode_key TEXT;");
130
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memories_episode_key ON memories(episode_key);");
131
+ }
132
+ },
133
+ {
134
+ version: 4,
135
+ up: (db) => {
136
+ db.exec("ALTER TABLE engrams ADD COLUMN context TEXT;");
137
+ db.exec("ALTER TABLE engrams ADD COLUMN decisions TEXT;");
138
+ }
139
+ }
140
+ ];
141
+ function getCortexDb(cortexName) {
142
+ const cached = dbs.get(cortexName);
143
+ if (cached) return cached;
144
+ ensureThinkDirs();
145
+ const dbPath = getEngramDbPath(cortexName);
146
+ const db = new DatabaseSync(dbPath);
147
+ db.exec("PRAGMA journal_mode = WAL");
148
+ db.exec("PRAGMA synchronous = NORMAL");
149
+ runMigrations(db, migrations);
150
+ dbs.set(cortexName, db);
151
+ return db;
152
+ }
153
+ function closeCortexDb(cortexName) {
154
+ const db = dbs.get(cortexName);
155
+ if (db) {
156
+ db.close();
157
+ dbs.delete(cortexName);
158
+ }
159
+ }
160
+
161
+ // src/db/memory-queries.ts
162
+ function insertMemory(cortexName, params) {
163
+ const db = getCortexDb(cortexName);
164
+ const id = params.id ?? uuidv7();
165
+ const now = (/* @__PURE__ */ new Date()).toISOString();
166
+ const sourceIds = JSON.stringify(params.source_ids ?? []);
167
+ const episodeKey = params.episode_key ?? null;
168
+ db.prepare(
169
+ `INSERT INTO memories (id, ts, author, content, source_ids, created_at, deleted_at, sync_version, episode_key)
170
+ VALUES (?, ?, ?, ?, ?, ?, ?, (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories), ?)`
171
+ ).run(id, params.ts, params.author, params.content, sourceIds, now, params.deleted_at ?? null, episodeKey);
172
+ const row = db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
173
+ return row;
174
+ }
175
+ function insertMemoryIfNotExists(cortexName, params) {
176
+ const db = getCortexDb(cortexName);
177
+ const existing = db.prepare("SELECT id FROM memories WHERE id = ?").get(params.id);
178
+ if (existing) return false;
179
+ insertMemory(cortexName, params);
180
+ return true;
181
+ }
182
+ function getMemories(cortexName, params = {}) {
183
+ const db = getCortexDb(cortexName);
184
+ const conditions = ["deleted_at IS NULL"];
185
+ const values = [];
186
+ if (params.since) {
187
+ conditions.push("ts >= ?");
188
+ values.push(params.since);
189
+ }
190
+ if (params.until) {
191
+ conditions.push("ts <= ?");
192
+ values.push(params.until);
193
+ }
194
+ const where = `WHERE ${conditions.join(" AND ")}`;
195
+ if (params.limit) {
196
+ values.push(params.limit);
197
+ return db.prepare(
198
+ `SELECT * FROM memories ${where} ORDER BY ts ASC LIMIT ?`
199
+ ).all(...values);
200
+ }
201
+ return db.prepare(
202
+ `SELECT * FROM memories ${where} ORDER BY ts ASC`
203
+ ).all(...values);
204
+ }
205
+ function getMemoriesBySyncVersion(cortexName, sinceVersion) {
206
+ const db = getCortexDb(cortexName);
207
+ return db.prepare(
208
+ "SELECT * FROM memories WHERE sync_version > ? ORDER BY sync_version ASC"
209
+ ).all(sinceVersion);
210
+ }
211
+ function searchMemories(cortexName, query, limit = 20) {
212
+ const db = getCortexDb(cortexName);
213
+ try {
214
+ return db.prepare(
215
+ `SELECT m.* FROM memories m JOIN memories_fts f ON m.rowid = f.rowid
216
+ WHERE memories_fts MATCH ? AND m.deleted_at IS NULL
217
+ ORDER BY rank LIMIT ?`
218
+ ).all(query, limit);
219
+ } catch {
220
+ const pattern = `%${query.replace(/%/g, "\\%").replace(/_/g, "\\_")}%`;
221
+ return db.prepare(
222
+ `SELECT * FROM memories WHERE content LIKE ? ESCAPE '\\' AND deleted_at IS NULL ORDER BY ts DESC LIMIT ?`
223
+ ).all(pattern, limit);
224
+ }
225
+ }
226
+ function tombstoneMemory(cortexName, id) {
227
+ const db = getCortexDb(cortexName);
228
+ db.prepare(
229
+ `UPDATE memories SET deleted_at = ?, sync_version = (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories)
230
+ WHERE id = ? AND deleted_at IS NULL`
231
+ ).run((/* @__PURE__ */ new Date()).toISOString(), id);
232
+ }
233
+ function getLongtermSummary(cortexName) {
234
+ const db = getCortexDb(cortexName);
235
+ const row = db.prepare("SELECT content FROM longterm_summary WHERE id = 1").get();
236
+ return row?.content ?? null;
237
+ }
238
+ function setLongtermSummary(cortexName, content) {
239
+ const db = getCortexDb(cortexName);
240
+ db.prepare(
241
+ `INSERT INTO longterm_summary (id, content, updated_at, sync_version)
242
+ VALUES (1, ?, ?, (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories))
243
+ ON CONFLICT(id) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at, sync_version = excluded.sync_version`
244
+ ).run(content, (/* @__PURE__ */ new Date()).toISOString());
245
+ }
246
+ function getSyncCursor(cortexName, backend, direction) {
247
+ const db = getCortexDb(cortexName);
248
+ const row = db.prepare(
249
+ "SELECT cursor_value FROM sync_cursors WHERE backend = ? AND direction = ?"
250
+ ).get(backend, direction);
251
+ return row?.cursor_value ?? null;
252
+ }
253
+ function setSyncCursor(cortexName, backend, direction, cursorValue) {
254
+ const db = getCortexDb(cortexName);
255
+ db.prepare(
256
+ `INSERT INTO sync_cursors (backend, direction, cursor_value, updated_at)
257
+ VALUES (?, ?, ?, ?)
258
+ ON CONFLICT(backend, direction) DO UPDATE SET cursor_value = excluded.cursor_value, updated_at = excluded.updated_at`
259
+ ).run(backend, direction, cursorValue, (/* @__PURE__ */ new Date()).toISOString());
260
+ }
261
+ function getMemoryCount(cortexName) {
262
+ const db = getCortexDb(cortexName);
263
+ const row = db.prepare("SELECT COUNT(*) as count FROM memories WHERE deleted_at IS NULL").get();
264
+ return row.count;
265
+ }
266
+ function getMemoryByEpisodeKey(cortexName, episodeKey) {
267
+ const db = getCortexDb(cortexName);
268
+ const row = db.prepare(
269
+ "SELECT * FROM memories WHERE episode_key = ? AND deleted_at IS NULL LIMIT 1"
270
+ ).get(episodeKey);
271
+ return row ?? null;
272
+ }
273
+
274
+ export {
275
+ getCortexDb,
276
+ closeCortexDb,
277
+ insertMemory,
278
+ insertMemoryIfNotExists,
279
+ getMemories,
280
+ getMemoriesBySyncVersion,
281
+ searchMemories,
282
+ tombstoneMemory,
283
+ getLongtermSummary,
284
+ setLongtermSummary,
285
+ getSyncCursor,
286
+ setSyncCursor,
287
+ getMemoryCount,
288
+ getMemoryByEpisodeKey
289
+ };
@@ -11,7 +11,8 @@ import {
11
11
  listRemoteBranches,
12
12
  migrateToBuckets,
13
13
  readFileFromBranch
14
- } from "./chunk-N4VAGRBF.js";
14
+ } from "./chunk-ICK2JU5B.js";
15
+ import "./chunk-DCTG6IK4.js";
15
16
  export {
16
17
  appendAndCommit,
17
18
  branchExists,
package/dist/index.js CHANGED
@@ -1,24 +1,41 @@
1
1
  #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
+ import {
3
+ closeCortexDb,
4
+ getCortexDb,
5
+ getLongtermSummary,
6
+ getMemories,
7
+ getMemoriesBySyncVersion,
8
+ getMemoryByEpisodeKey,
9
+ getMemoryCount,
10
+ getSyncCursor,
11
+ insertMemory,
12
+ insertMemoryIfNotExists,
13
+ searchMemories,
14
+ setLongtermSummary,
15
+ setSyncCursor,
16
+ tombstoneMemory
17
+ } from "./chunk-MSOBQE64.js";
2
18
  import {
3
19
  appendAndCommit,
4
20
  countBranchFileLines,
5
21
  createOrphanBranch,
6
22
  ensureRepoCloned,
7
- ensureThinkDirs,
8
23
  fetchBranch,
9
24
  getConfig,
10
25
  getConfigDir,
11
- getCuratorMdPath,
12
- getEngramDbPath,
13
- getEngramsDir,
14
- getLongtermPath,
15
- getThinkDataDir,
16
26
  listBranchFiles,
17
27
  listRemoteBranches,
18
28
  migrateToBuckets,
19
29
  readFileFromBranch,
20
30
  saveConfig
21
- } from "./chunk-N4VAGRBF.js";
31
+ } from "./chunk-ICK2JU5B.js";
32
+ import {
33
+ ensureThinkDirs,
34
+ getCuratorMdPath,
35
+ getEngramsDir,
36
+ getLongtermPath,
37
+ getThinkDataDir
38
+ } from "./chunk-DCTG6IK4.js";
22
39
 
23
40
  // src/index.ts
24
41
  import fs11 from "fs";
@@ -153,159 +170,6 @@ function deleteEntriesByContent(pattern) {
153
170
 
154
171
  // src/db/engram-queries.ts
155
172
  import { v7 as uuidv72 } from "uuid";
156
-
157
- // src/db/engrams.ts
158
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
159
-
160
- // src/db/migrate.ts
161
- function runMigrations(db2, migrations2) {
162
- db2.exec(`
163
- CREATE TABLE IF NOT EXISTS _migrations (
164
- version INTEGER PRIMARY KEY NOT NULL,
165
- applied_at TEXT NOT NULL
166
- ) STRICT;
167
- `);
168
- const currentVersion = db2.prepare(
169
- "SELECT COALESCE(MAX(version), 0) as version FROM _migrations"
170
- ).get();
171
- const pending = migrations2.filter((m) => m.version > currentVersion.version).sort((a, b) => a.version - b.version);
172
- for (const migration of pending) {
173
- db2.exec("BEGIN");
174
- try {
175
- migration.up(db2);
176
- db2.prepare("INSERT INTO _migrations (version, applied_at) VALUES (?, ?)").run(
177
- migration.version,
178
- (/* @__PURE__ */ new Date()).toISOString()
179
- );
180
- db2.exec("COMMIT");
181
- } catch (err) {
182
- db2.exec("ROLLBACK");
183
- throw new Error(`Migration v${migration.version} failed: ${err instanceof Error ? err.message : String(err)}`);
184
- }
185
- }
186
- }
187
-
188
- // src/db/engrams.ts
189
- var dbs = /* @__PURE__ */ new Map();
190
- var migrations = [
191
- {
192
- version: 1,
193
- up: (db2) => {
194
- db2.exec(`
195
- CREATE TABLE IF NOT EXISTS engrams (
196
- id TEXT PRIMARY KEY NOT NULL,
197
- content TEXT NOT NULL,
198
- created_at TEXT NOT NULL,
199
- expires_at TEXT NOT NULL,
200
- evaluated_at TEXT,
201
- promoted INTEGER,
202
- deleted_at TEXT
203
- ) STRICT;
204
- `);
205
- db2.exec(`
206
- CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts
207
- USING fts5(content, content='engrams', content_rowid='rowid');
208
- `);
209
- db2.exec(`
210
- CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
211
- INSERT INTO engrams_fts(rowid, content) VALUES (new.rowid, new.content);
212
- END;
213
- `);
214
- db2.exec(`
215
- CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
216
- INSERT INTO engrams_fts(engrams_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
217
- END;
218
- `);
219
- }
220
- },
221
- {
222
- version: 2,
223
- up: (db2) => {
224
- db2.exec(`
225
- CREATE TABLE IF NOT EXISTS memories (
226
- id TEXT PRIMARY KEY NOT NULL,
227
- ts TEXT NOT NULL,
228
- author TEXT NOT NULL,
229
- content TEXT NOT NULL,
230
- source_ids TEXT NOT NULL DEFAULT '[]',
231
- created_at TEXT NOT NULL,
232
- deleted_at TEXT,
233
- sync_version INTEGER NOT NULL DEFAULT 0
234
- ) STRICT;
235
- `);
236
- db2.exec("CREATE INDEX IF NOT EXISTS idx_memories_ts ON memories(ts);");
237
- db2.exec("CREATE INDEX IF NOT EXISTS idx_memories_sync_version ON memories(sync_version);");
238
- db2.exec(`
239
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts
240
- USING fts5(content, content='memories', content_rowid='rowid');
241
- `);
242
- db2.exec(`
243
- CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
244
- INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
245
- END;
246
- `);
247
- db2.exec(`
248
- CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
249
- INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
250
- END;
251
- `);
252
- db2.exec(`
253
- CREATE TABLE IF NOT EXISTS longterm_summary (
254
- id INTEGER PRIMARY KEY CHECK (id = 1),
255
- content TEXT NOT NULL,
256
- updated_at TEXT NOT NULL,
257
- sync_version INTEGER NOT NULL DEFAULT 0
258
- ) STRICT;
259
- `);
260
- db2.exec(`
261
- CREATE TABLE IF NOT EXISTS sync_cursors (
262
- backend TEXT NOT NULL,
263
- direction TEXT NOT NULL,
264
- cursor_value TEXT NOT NULL,
265
- updated_at TEXT NOT NULL,
266
- PRIMARY KEY (backend, direction)
267
- ) STRICT;
268
- `);
269
- }
270
- },
271
- {
272
- version: 3,
273
- up: (db2) => {
274
- db2.exec("ALTER TABLE engrams ADD COLUMN episode_key TEXT;");
275
- db2.exec("CREATE INDEX IF NOT EXISTS idx_engrams_episode_key ON engrams(episode_key);");
276
- db2.exec("ALTER TABLE memories ADD COLUMN episode_key TEXT;");
277
- db2.exec("CREATE INDEX IF NOT EXISTS idx_memories_episode_key ON memories(episode_key);");
278
- }
279
- },
280
- {
281
- version: 4,
282
- up: (db2) => {
283
- db2.exec("ALTER TABLE engrams ADD COLUMN context TEXT;");
284
- db2.exec("ALTER TABLE engrams ADD COLUMN decisions TEXT;");
285
- }
286
- }
287
- ];
288
- function getCortexDb(cortexName) {
289
- const cached = dbs.get(cortexName);
290
- if (cached) return cached;
291
- ensureThinkDirs();
292
- const dbPath = getEngramDbPath(cortexName);
293
- const db2 = new DatabaseSync2(dbPath);
294
- db2.exec("PRAGMA journal_mode = WAL");
295
- db2.exec("PRAGMA synchronous = NORMAL");
296
- runMigrations(db2, migrations);
297
- dbs.set(cortexName, db2);
298
- return db2;
299
- }
300
- function closeCortexDb(cortexName) {
301
- const db2 = dbs.get(cortexName);
302
- if (db2) {
303
- db2.close();
304
- dbs.delete(cortexName);
305
- }
306
- }
307
-
308
- // src/db/engram-queries.ts
309
173
  function insertEngram(cortexName, params) {
310
174
  const db2 = getCortexDb(cortexName);
311
175
  const id = uuidv72();
@@ -1118,105 +982,6 @@ import { Command as Command9 } from "commander";
1118
982
  import chalk9 from "chalk";
1119
983
  import readline2 from "readline";
1120
984
 
1121
- // src/db/memory-queries.ts
1122
- import { v7 as uuidv73 } from "uuid";
1123
- function insertMemory(cortexName, params) {
1124
- const db2 = getCortexDb(cortexName);
1125
- const id = params.id ?? uuidv73();
1126
- const now = (/* @__PURE__ */ new Date()).toISOString();
1127
- const sourceIds = JSON.stringify(params.source_ids ?? []);
1128
- const episodeKey = params.episode_key ?? null;
1129
- db2.prepare(
1130
- `INSERT INTO memories (id, ts, author, content, source_ids, created_at, deleted_at, sync_version, episode_key)
1131
- VALUES (?, ?, ?, ?, ?, ?, ?, (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories), ?)`
1132
- ).run(id, params.ts, params.author, params.content, sourceIds, now, params.deleted_at ?? null, episodeKey);
1133
- const row = db2.prepare("SELECT * FROM memories WHERE id = ?").get(id);
1134
- return row;
1135
- }
1136
- function insertMemoryIfNotExists(cortexName, params) {
1137
- const db2 = getCortexDb(cortexName);
1138
- const existing = db2.prepare("SELECT id FROM memories WHERE id = ?").get(params.id);
1139
- if (existing) return false;
1140
- insertMemory(cortexName, params);
1141
- return true;
1142
- }
1143
- function getMemories(cortexName, params = {}) {
1144
- const db2 = getCortexDb(cortexName);
1145
- const conditions = ["deleted_at IS NULL"];
1146
- const values = [];
1147
- if (params.since) {
1148
- conditions.push("ts >= ?");
1149
- values.push(params.since);
1150
- }
1151
- if (params.until) {
1152
- conditions.push("ts <= ?");
1153
- values.push(params.until);
1154
- }
1155
- const where = `WHERE ${conditions.join(" AND ")}`;
1156
- if (params.limit) {
1157
- values.push(params.limit);
1158
- return db2.prepare(
1159
- `SELECT * FROM memories ${where} ORDER BY ts ASC LIMIT ?`
1160
- ).all(...values);
1161
- }
1162
- return db2.prepare(
1163
- `SELECT * FROM memories ${where} ORDER BY ts ASC`
1164
- ).all(...values);
1165
- }
1166
- function getMemoriesBySyncVersion(cortexName, sinceVersion) {
1167
- const db2 = getCortexDb(cortexName);
1168
- return db2.prepare(
1169
- "SELECT * FROM memories WHERE sync_version > ? ORDER BY sync_version ASC"
1170
- ).all(sinceVersion);
1171
- }
1172
- function tombstoneMemory(cortexName, id) {
1173
- const db2 = getCortexDb(cortexName);
1174
- db2.prepare(
1175
- `UPDATE memories SET deleted_at = ?, sync_version = (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories)
1176
- WHERE id = ? AND deleted_at IS NULL`
1177
- ).run((/* @__PURE__ */ new Date()).toISOString(), id);
1178
- }
1179
- function getLongtermSummary(cortexName) {
1180
- const db2 = getCortexDb(cortexName);
1181
- const row = db2.prepare("SELECT content FROM longterm_summary WHERE id = 1").get();
1182
- return row?.content ?? null;
1183
- }
1184
- function setLongtermSummary(cortexName, content) {
1185
- const db2 = getCortexDb(cortexName);
1186
- db2.prepare(
1187
- `INSERT INTO longterm_summary (id, content, updated_at, sync_version)
1188
- VALUES (1, ?, ?, (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories))
1189
- ON CONFLICT(id) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at, sync_version = excluded.sync_version`
1190
- ).run(content, (/* @__PURE__ */ new Date()).toISOString());
1191
- }
1192
- function getSyncCursor(cortexName, backend, direction) {
1193
- const db2 = getCortexDb(cortexName);
1194
- const row = db2.prepare(
1195
- "SELECT cursor_value FROM sync_cursors WHERE backend = ? AND direction = ?"
1196
- ).get(backend, direction);
1197
- return row?.cursor_value ?? null;
1198
- }
1199
- function setSyncCursor(cortexName, backend, direction, cursorValue) {
1200
- const db2 = getCortexDb(cortexName);
1201
- db2.prepare(
1202
- `INSERT INTO sync_cursors (backend, direction, cursor_value, updated_at)
1203
- VALUES (?, ?, ?, ?)
1204
- ON CONFLICT(backend, direction) DO UPDATE SET cursor_value = excluded.cursor_value, updated_at = excluded.updated_at`
1205
- ).run(backend, direction, cursorValue, (/* @__PURE__ */ new Date()).toISOString());
1206
- }
1207
- function getMemoryCount(cortexName) {
1208
- const db2 = getCortexDb(cortexName);
1209
- const row = db2.prepare("SELECT COUNT(*) as count FROM memories WHERE deleted_at IS NULL").get();
1210
- return row.count;
1211
- }
1212
- function getMemoryByEpisodeKey(cortexName, episodeKey) {
1213
- const db2 = getCortexDb(cortexName);
1214
- const row = db2.prepare(
1215
- "SELECT * FROM memories WHERE episode_key = ? AND deleted_at IS NULL LIMIT 1"
1216
- ).get(episodeKey);
1217
- return row ?? null;
1218
- }
1219
-
1220
985
  // src/lib/curator.ts
1221
986
  import fs7 from "fs";
1222
987
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
@@ -1752,7 +1517,7 @@ cortexCommand.addCommand(new Command9("setup").description("Configure a sync bac
1752
1517
  const adapter = getSyncAdapter();
1753
1518
  if (adapter) {
1754
1519
  try {
1755
- const { ensureRepoCloned: ensureRepoCloned2 } = await import("./git-R4CVMKV7.js");
1520
+ const { ensureRepoCloned: ensureRepoCloned2 } = await import("./git-BRGF6DFD.js");
1756
1521
  ensureRepoCloned2();
1757
1522
  console.log(chalk9.green("\u2713") + " Repo cloned");
1758
1523
  } catch (err) {
@@ -2256,44 +2021,77 @@ var monitorCommand = new Command11("monitor").description("Show what got promote
2256
2021
  // src/commands/recall.ts
2257
2022
  import { Command as Command12 } from "commander";
2258
2023
  import chalk12 from "chalk";
2259
- var recallCommand = new Command12("recall").argument("<query>", "What to recall").description("Search memories and local engrams").option("--days <n>", "Days of memories to include", "14").action(async (query3, opts) => {
2024
+ var recallCommand = new Command12("recall").argument("<query>", "What to recall").description("Search memories and local engrams").option("--engrams", "Also search local engrams (not just memories)").option("--all", "Dump all recent memories + long-term summary (ignores query for memories)").option("--days <n>", "Days of memories to include (only with --all)", "14").option("--limit <n>", "Max results to return", "20").action(async (query3, opts) => {
2260
2025
  const config = getConfig();
2261
2026
  const cortex = config.cortex?.active;
2262
2027
  if (!cortex) {
2263
2028
  console.error(chalk12.red("No active cortex. Run: think cortex switch <name>"));
2264
2029
  process.exit(1);
2265
2030
  }
2266
- const days = parseInt(opts.days, 10);
2267
- const cutoff = new Date(Date.now() - days * 864e5).toISOString();
2268
- const recentMemories = getMemories(cortex, { since: cutoff });
2269
- const matchingEngrams = searchEngrams(cortex, query3);
2270
- const longterm = getLongtermSummary(cortex);
2271
- if (recentMemories.length > 0) {
2272
- console.log(chalk12.cyan(`Team memories (last ${days} days):`));
2273
- for (const m of recentMemories) {
2031
+ const limit = parseInt(opts.limit, 10);
2032
+ if (opts.all) {
2033
+ const { getMemories: getMemories2 } = await import("./memory-queries-IPGGUAQW.js");
2034
+ const days = parseInt(opts.days, 10);
2035
+ const cutoff = new Date(Date.now() - days * 864e5).toISOString();
2036
+ const recentMemories = getMemories2(cortex, { since: cutoff });
2037
+ const longterm = getLongtermSummary(cortex);
2038
+ const matchingEngrams = searchEngrams(cortex, query3);
2039
+ if (recentMemories.length > 0) {
2040
+ console.log(chalk12.cyan(`Team memories (last ${days} days):`));
2041
+ for (const m of recentMemories) {
2042
+ const ts = m.ts.slice(0, 16).replace("T", " ");
2043
+ console.log(` ${chalk12.gray(ts)} ${chalk12.dim(m.author + ":")} ${m.content}`);
2044
+ }
2045
+ console.log();
2046
+ }
2047
+ if (longterm) {
2048
+ console.log(chalk12.cyan("Long-term context:"));
2049
+ console.log(` ${longterm}`);
2050
+ console.log();
2051
+ }
2052
+ if (matchingEngrams.length > 0) {
2053
+ console.log(chalk12.cyan(`Matching engrams (local):`));
2054
+ for (const e of matchingEngrams) {
2055
+ const ts = e.created_at.slice(0, 16).replace("T", " ");
2056
+ console.log(` ${chalk12.gray(ts)} ${e.content}`);
2057
+ }
2058
+ console.log();
2059
+ }
2060
+ if (recentMemories.length === 0 && matchingEngrams.length === 0 && !longterm) {
2061
+ console.log(chalk12.dim("No results found."));
2062
+ }
2063
+ closeCortexDb(cortex);
2064
+ return;
2065
+ }
2066
+ const matchingMemories = searchMemories(cortex, query3, limit);
2067
+ if (matchingMemories.length > 0) {
2068
+ console.log(chalk12.cyan(`Matching memories (${matchingMemories.length}):`));
2069
+ for (const m of matchingMemories) {
2274
2070
  const ts = m.ts.slice(0, 16).replace("T", " ");
2275
2071
  console.log(` ${chalk12.gray(ts)} ${chalk12.dim(m.author + ":")} ${m.content}`);
2276
2072
  }
2277
2073
  console.log();
2278
2074
  } else {
2279
- console.log(chalk12.dim("No recent memories."));
2280
- console.log();
2281
- }
2282
- if (longterm) {
2283
- console.log(chalk12.cyan("Long-term context:"));
2284
- console.log(` ${longterm}`);
2285
- console.log();
2286
- }
2287
- if (matchingEngrams.length > 0) {
2288
- console.log(chalk12.cyan(`Matching engrams (local):`));
2289
- for (const e of matchingEngrams) {
2290
- const ts = e.created_at.slice(0, 16).replace("T", " ");
2291
- console.log(` ${chalk12.gray(ts)} ${e.content}`);
2075
+ const longterm = getLongtermSummary(cortex);
2076
+ if (longterm) {
2077
+ console.log(chalk12.dim("No matching memories. Showing long-term context:"));
2078
+ console.log(` ${longterm}`);
2079
+ console.log();
2080
+ } else {
2081
+ console.log(chalk12.dim("No matching memories."));
2082
+ console.log();
2292
2083
  }
2293
- console.log();
2294
2084
  }
2295
- if (recentMemories.length === 0 && matchingEngrams.length === 0 && !longterm) {
2296
- console.log(chalk12.dim("No results found."));
2085
+ if (opts.engrams) {
2086
+ const matchingEngrams = searchEngrams(cortex, query3, limit);
2087
+ if (matchingEngrams.length > 0) {
2088
+ console.log(chalk12.cyan(`Matching engrams (${matchingEngrams.length}):`));
2089
+ for (const e of matchingEngrams) {
2090
+ const ts = e.created_at.slice(0, 16).replace("T", " ");
2091
+ console.log(` ${chalk12.gray(ts)} ${e.content}`);
2092
+ }
2093
+ console.log();
2094
+ }
2297
2095
  }
2298
2096
  closeCortexDb(cortex);
2299
2097
  });
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
+ import {
3
+ getLongtermSummary,
4
+ getMemories,
5
+ getMemoriesBySyncVersion,
6
+ getMemoryByEpisodeKey,
7
+ getMemoryCount,
8
+ getSyncCursor,
9
+ insertMemory,
10
+ insertMemoryIfNotExists,
11
+ searchMemories,
12
+ setLongtermSummary,
13
+ setSyncCursor,
14
+ tombstoneMemory
15
+ } from "./chunk-MSOBQE64.js";
16
+ import "./chunk-DCTG6IK4.js";
17
+ export {
18
+ getLongtermSummary,
19
+ getMemories,
20
+ getMemoriesBySyncVersion,
21
+ getMemoryByEpisodeKey,
22
+ getMemoryCount,
23
+ getSyncCursor,
24
+ insertMemory,
25
+ insertMemoryIfNotExists,
26
+ searchMemories,
27
+ setLongtermSummary,
28
+ setSyncCursor,
29
+ tombstoneMemory
30
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-think",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "description": "Local-first CLI that gives AI agents persistent, curated memory",
6
6
  "bin": {