open-think 0.3.4 → 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +354 -236
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -39,8 +39,8 @@ import {
39
39
  } from "./chunk-HUBRLTY3.js";
40
40
 
41
41
  // src/index.ts
42
- import fs12 from "fs";
43
- import path6 from "path";
42
+ import fs13 from "fs";
43
+ import path7 from "path";
44
44
  import { Command as Command20 } from "commander";
45
45
 
46
46
  // src/commands/log.ts
@@ -1977,6 +1977,103 @@ cortexCommand.addCommand(autoCurateCommand);
1977
1977
  import { Command as Command10 } from "commander";
1978
1978
  import readline3 from "readline";
1979
1979
  import chalk10 from "chalk";
1980
+
1981
+ // src/lib/curate-lock.ts
1982
+ import fs10 from "fs";
1983
+ import path6 from "path";
1984
+ function getLockPath(cortex) {
1985
+ return path6.join(getThinkDir(), `curate-${cortex}.lock`);
1986
+ }
1987
+ function isProcessAlive(pid) {
1988
+ if (!Number.isFinite(pid) || pid <= 0) return false;
1989
+ try {
1990
+ process.kill(pid, 0);
1991
+ return true;
1992
+ } catch (err) {
1993
+ const code = err.code;
1994
+ return code === "EPERM";
1995
+ }
1996
+ }
1997
+ function acquireCurateLock(cortex) {
1998
+ const lockPath = getLockPath(cortex);
1999
+ fs10.mkdirSync(path6.dirname(lockPath), { recursive: true });
2000
+ try {
2001
+ fs10.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
2002
+ return makeAcquired(lockPath);
2003
+ } catch (err) {
2004
+ if (err.code !== "EEXIST") throw err;
2005
+ }
2006
+ let heldByPid = null;
2007
+ try {
2008
+ const raw = fs10.readFileSync(lockPath, "utf-8").trim();
2009
+ const parsed = parseInt(raw, 10);
2010
+ if (Number.isFinite(parsed) && parsed > 0) heldByPid = parsed;
2011
+ } catch {
2012
+ }
2013
+ if (heldByPid && isProcessAlive(heldByPid)) {
2014
+ return { acquired: false, heldByPid };
2015
+ }
2016
+ try {
2017
+ fs10.unlinkSync(lockPath);
2018
+ } catch {
2019
+ }
2020
+ try {
2021
+ fs10.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
2022
+ return makeAcquired(lockPath);
2023
+ } catch (err) {
2024
+ if (err.code === "EEXIST") {
2025
+ let nowHeldBy = null;
2026
+ try {
2027
+ const raw = fs10.readFileSync(lockPath, "utf-8").trim();
2028
+ const parsed = parseInt(raw, 10);
2029
+ if (Number.isFinite(parsed) && parsed > 0) nowHeldBy = parsed;
2030
+ } catch {
2031
+ }
2032
+ return { acquired: false, heldByPid: nowHeldBy };
2033
+ }
2034
+ throw err;
2035
+ }
2036
+ }
2037
+ function makeAcquired(lockPath) {
2038
+ let released = false;
2039
+ const unlinkIfHeld = () => {
2040
+ if (released) return;
2041
+ released = true;
2042
+ try {
2043
+ fs10.unlinkSync(lockPath);
2044
+ } catch {
2045
+ }
2046
+ };
2047
+ const exitHandler = () => {
2048
+ unlinkIfHeld();
2049
+ };
2050
+ const sigintHandler = () => {
2051
+ unlinkIfHeld();
2052
+ process.exit(130);
2053
+ };
2054
+ const sigtermHandler = () => {
2055
+ unlinkIfHeld();
2056
+ process.exit(143);
2057
+ };
2058
+ process.on("exit", exitHandler);
2059
+ process.on("SIGINT", sigintHandler);
2060
+ process.on("SIGTERM", sigtermHandler);
2061
+ const release = () => {
2062
+ if (released) {
2063
+ process.removeListener("exit", exitHandler);
2064
+ process.removeListener("SIGINT", sigintHandler);
2065
+ process.removeListener("SIGTERM", sigtermHandler);
2066
+ return;
2067
+ }
2068
+ unlinkIfHeld();
2069
+ process.removeListener("exit", exitHandler);
2070
+ process.removeListener("SIGINT", sigintHandler);
2071
+ process.removeListener("SIGTERM", sigtermHandler);
2072
+ };
2073
+ return { acquired: true, release };
2074
+ }
2075
+
2076
+ // src/commands/curate.ts
1980
2077
  var curateCommand = new Command10("curate").description("Run curation: evaluate pending engrams and promote to memories").option("--dry-run", "Preview what would be committed without saving").option("--consolidate", "Run long-term memory consolidation only (no curation)").option("--episode <key>", "Curate a specific episode into a narrative memory").option("--if-idle", "Only curate if the user appears idle (used by auto-curation scheduler)").action(async (opts) => {
1981
2078
  const config = getConfig();
1982
2079
  const cortex = config.cortex?.active;
@@ -1988,271 +2085,292 @@ var curateCommand = new Command10("curate").description("Run curation: evaluate
1988
2085
  process.exit(1);
1989
2086
  }
1990
2087
  const author = config.cortex.author;
1991
- if (opts.ifIdle && !opts.episode && !opts.consolidate) {
1992
- const shouldRun = shouldRunIdleCuration(cortex, config.cortex);
1993
- if (!shouldRun.run) {
1994
- if (process.env.THINK_IDLE_DEBUG) {
1995
- console.log(chalk10.dim(`[auto-curate] skipped: ${shouldRun.reason}`));
2088
+ let releaseLock = () => {
2089
+ };
2090
+ if (!opts.dryRun) {
2091
+ const lock = acquireCurateLock(cortex);
2092
+ if (!lock.acquired) {
2093
+ if (opts.ifIdle) {
2094
+ if (process.env.THINK_IDLE_DEBUG) {
2095
+ console.log(chalk10.dim(`[auto-curate] skipped: another curation is running (pid ${lock.heldByPid ?? "?"})`));
2096
+ }
2097
+ } else {
2098
+ console.log(chalk10.yellow(`Another curation is already running (pid ${lock.heldByPid ?? "?"}). Skipping.`));
1996
2099
  }
1997
2100
  closeCortexDb(cortex);
1998
2101
  return;
1999
2102
  }
2000
- if (process.env.THINK_IDLE_DEBUG) {
2001
- console.log(chalk10.dim(`[auto-curate] running: ${shouldRun.reason}`));
2002
- }
2103
+ releaseLock = lock.release;
2003
2104
  }
2004
- const adapter = getSyncAdapter();
2005
- if (adapter?.isAvailable()) {
2006
- try {
2007
- const pullResult = await adapter.pull(cortex);
2008
- if (pullResult.pulled > 0) {
2009
- console.log(chalk10.dim(` Pulled ${pullResult.pulled} memories from ${adapter.name}`));
2105
+ try {
2106
+ if (opts.ifIdle && !opts.episode && !opts.consolidate) {
2107
+ const shouldRun = shouldRunIdleCuration(cortex, config.cortex);
2108
+ if (!shouldRun.run) {
2109
+ if (process.env.THINK_IDLE_DEBUG) {
2110
+ console.log(chalk10.dim(`[auto-curate] skipped: ${shouldRun.reason}`));
2111
+ }
2112
+ closeCortexDb(cortex);
2113
+ return;
2114
+ }
2115
+ if (process.env.THINK_IDLE_DEBUG) {
2116
+ console.log(chalk10.dim(`[auto-curate] running: ${shouldRun.reason}`));
2010
2117
  }
2011
- } catch {
2012
- console.log(chalk10.dim(" Sync pull skipped (remote unavailable)"));
2013
2118
  }
2014
- }
2015
- if (opts.episode) {
2016
- const episodeEngrams = getPendingEpisodeEngrams(cortex, opts.episode);
2017
- if (episodeEngrams.length === 0) {
2018
- console.log(chalk10.dim(`No pending engrams for episode: ${opts.episode}`));
2019
- closeCortexDb(cortex);
2020
- return;
2119
+ const adapter = getSyncAdapter();
2120
+ if (adapter?.isAvailable()) {
2121
+ try {
2122
+ const pullResult = await adapter.pull(cortex);
2123
+ if (pullResult.pulled > 0) {
2124
+ console.log(chalk10.dim(` Pulled ${pullResult.pulled} memories from ${adapter.name}`));
2125
+ }
2126
+ } catch {
2127
+ console.log(chalk10.dim(" Sync pull skipped (remote unavailable)"));
2128
+ }
2021
2129
  }
2022
- const existingMemoryRow = getMemoryByEpisodeKey(cortex, opts.episode);
2023
- const existingMemory = existingMemoryRow ? {
2024
- ts: existingMemoryRow.ts,
2025
- author: existingMemoryRow.author,
2026
- content: existingMemoryRow.content,
2027
- source_ids: JSON.parse(existingMemoryRow.source_ids)
2028
- } : null;
2029
- console.log(chalk10.cyan(`Curating episode: ${opts.episode} (${episodeEngrams.length} engrams${existingMemory ? ", updating existing narrative" : ""})...`));
2030
- const prompt3 = assembleEpisodeCurationPrompt({
2031
- episodeKey: opts.episode,
2032
- pendingEngrams: episodeEngrams,
2033
- existingMemory,
2034
- author
2035
- });
2036
- if (opts.dryRun) {
2037
- console.log();
2038
- console.log(chalk10.cyan("Episode prompt would be sent to LLM:"));
2039
- console.log(chalk10.dim(` ${episodeEngrams.length} engrams, ${existingMemory ? "updating" : "creating"} narrative`));
2040
- for (const e of episodeEngrams) {
2041
- const ts = e.created_at.slice(0, 16).replace("T", " ");
2042
- console.log(chalk10.dim(` ${ts}: ${e.content.slice(0, 100)}${e.content.length > 100 ? "..." : ""}`));
2130
+ if (opts.episode) {
2131
+ const episodeEngrams = getPendingEpisodeEngrams(cortex, opts.episode);
2132
+ if (episodeEngrams.length === 0) {
2133
+ console.log(chalk10.dim(`No pending engrams for episode: ${opts.episode}`));
2134
+ closeCortexDb(cortex);
2135
+ return;
2136
+ }
2137
+ const existingMemoryRow = getMemoryByEpisodeKey(cortex, opts.episode);
2138
+ const existingMemory = existingMemoryRow ? {
2139
+ ts: existingMemoryRow.ts,
2140
+ author: existingMemoryRow.author,
2141
+ content: existingMemoryRow.content,
2142
+ source_ids: JSON.parse(existingMemoryRow.source_ids)
2143
+ } : null;
2144
+ console.log(chalk10.cyan(`Curating episode: ${opts.episode} (${episodeEngrams.length} engrams${existingMemory ? ", updating existing narrative" : ""})...`));
2145
+ const prompt3 = assembleEpisodeCurationPrompt({
2146
+ episodeKey: opts.episode,
2147
+ pendingEngrams: episodeEngrams,
2148
+ existingMemory,
2149
+ author
2150
+ });
2151
+ if (opts.dryRun) {
2152
+ console.log();
2153
+ console.log(chalk10.cyan("Episode prompt would be sent to LLM:"));
2154
+ console.log(chalk10.dim(` ${episodeEngrams.length} engrams, ${existingMemory ? "updating" : "creating"} narrative`));
2155
+ for (const e of episodeEngrams) {
2156
+ const ts = e.created_at.slice(0, 16).replace("T", " ");
2157
+ console.log(chalk10.dim(` ${ts}: ${e.content.slice(0, 100)}${e.content.length > 100 ? "..." : ""}`));
2158
+ }
2159
+ closeCortexDb(cortex);
2160
+ return;
2161
+ }
2162
+ let narrative;
2163
+ try {
2164
+ narrative = await runEpisodeCuration(prompt3);
2165
+ } catch (err) {
2166
+ const message = err instanceof Error ? err.message : String(err);
2167
+ console.error(chalk10.red(`Episode curation failed: ${message}`));
2168
+ closeCortexDb(cortex);
2169
+ process.exit(1);
2043
2170
  }
2171
+ if (existingMemoryRow) {
2172
+ tombstoneMemory(cortex, existingMemoryRow.id);
2173
+ }
2174
+ const allSourceIds = [
2175
+ ...existingMemory?.source_ids ?? [],
2176
+ ...episodeEngrams.map((e) => e.id)
2177
+ ];
2178
+ insertMemory(cortex, {
2179
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2180
+ author,
2181
+ content: narrative,
2182
+ source_ids: allSourceIds,
2183
+ episode_key: opts.episode
2184
+ });
2185
+ markPromoted(cortex, episodeEngrams.map((e) => e.id));
2186
+ if (adapter?.isAvailable()) {
2187
+ try {
2188
+ const pushResult = await adapter.push(cortex);
2189
+ if (pushResult.pushed > 0) {
2190
+ console.log(chalk10.dim(` Pushed ${pushResult.pushed} memories to ${adapter.name}`));
2191
+ }
2192
+ } catch {
2193
+ console.log(chalk10.dim(" Sync push skipped (remote unavailable)"));
2194
+ }
2195
+ }
2196
+ console.log();
2197
+ console.log(`${chalk10.green("\u2713")} Episode curated: ${opts.episode}`);
2198
+ console.log(` ${episodeEngrams.length} engrams synthesized into narrative`);
2044
2199
  closeCortexDb(cortex);
2045
2200
  return;
2046
2201
  }
2047
- let narrative;
2048
- try {
2049
- narrative = await runEpisodeCuration(prompt3);
2050
- } catch (err) {
2051
- const message = err instanceof Error ? err.message : String(err);
2052
- console.error(chalk10.red(`Episode curation failed: ${message}`));
2053
- closeCortexDb(cortex);
2054
- process.exit(1);
2055
- }
2056
- if (existingMemoryRow) {
2057
- tombstoneMemory(cortex, existingMemoryRow.id);
2058
- }
2059
- const allSourceIds = [
2060
- ...existingMemory?.source_ids ?? [],
2061
- ...episodeEngrams.map((e) => e.id)
2062
- ];
2063
- insertMemory(cortex, {
2064
- ts: (/* @__PURE__ */ new Date()).toISOString(),
2065
- author,
2066
- content: narrative,
2067
- source_ids: allSourceIds,
2068
- episode_key: opts.episode
2069
- });
2070
- markPromoted(cortex, episodeEngrams.map((e) => e.id));
2071
- if (adapter?.isAvailable()) {
2202
+ const allMemories = getMemories(cortex);
2203
+ const memoryEntries = allMemories.map((m) => ({
2204
+ ts: m.ts,
2205
+ author: m.author,
2206
+ content: m.content,
2207
+ source_ids: JSON.parse(m.source_ids)
2208
+ }));
2209
+ const { recent, older } = filterRecentMemories(memoryEntries);
2210
+ const longtermSummary = getLongtermSummary(cortex);
2211
+ if (opts.consolidate) {
2212
+ if (older.length === 0) {
2213
+ console.log(chalk10.dim("No memories older than 2 weeks to consolidate."));
2214
+ return;
2215
+ }
2216
+ console.log(chalk10.cyan(`Consolidating ${older.length} older memories into long-term summary...`));
2072
2217
  try {
2073
- const pushResult = await adapter.push(cortex);
2074
- if (pushResult.pushed > 0) {
2075
- console.log(chalk10.dim(` Pushed ${pushResult.pushed} memories to ${adapter.name}`));
2218
+ const newSummary = await runConsolidation(longtermSummary, older);
2219
+ if (opts.dryRun) {
2220
+ console.log();
2221
+ console.log(chalk10.cyan("Proposed long-term summary:"));
2222
+ console.log(newSummary);
2223
+ return;
2076
2224
  }
2077
- } catch {
2078
- console.log(chalk10.dim(" Sync push skipped (remote unavailable)"));
2225
+ setLongtermSummary(cortex, newSummary);
2226
+ console.log(chalk10.green("\u2713") + ` Long-term summary updated (${older.length} memories consolidated)`);
2227
+ } catch (err) {
2228
+ const message = err instanceof Error ? err.message : String(err);
2229
+ console.error(chalk10.red(`Consolidation failed: ${message}`));
2230
+ process.exit(1);
2079
2231
  }
2232
+ return;
2080
2233
  }
2081
- console.log();
2082
- console.log(`${chalk10.green("\u2713")} Episode curated: ${opts.episode}`);
2083
- console.log(` ${episodeEngrams.length} engrams synthesized into narrative`);
2084
- closeCortexDb(cortex);
2085
- return;
2086
- }
2087
- const allMemories = getMemories(cortex);
2088
- const memoryEntries = allMemories.map((m) => ({
2089
- ts: m.ts,
2090
- author: m.author,
2091
- content: m.content,
2092
- source_ids: JSON.parse(m.source_ids)
2093
- }));
2094
- const { recent, older } = filterRecentMemories(memoryEntries);
2095
- const longtermSummary = getLongtermSummary(cortex);
2096
- if (opts.consolidate) {
2097
- if (older.length === 0) {
2098
- console.log(chalk10.dim("No memories older than 2 weeks to consolidate."));
2234
+ const pending = getPendingEngrams(cortex);
2235
+ if (pending.length === 0) {
2236
+ console.log(chalk10.dim("No pending engrams to evaluate."));
2237
+ closeCortexDb(cortex);
2099
2238
  return;
2100
2239
  }
2101
- console.log(chalk10.cyan(`Consolidating ${older.length} older memories into long-term summary...`));
2240
+ console.log(chalk10.cyan(`Evaluating ${pending.length} engrams (${recent.length} recent memories, long-term summary ${longtermSummary ? "loaded" : "absent"})...`));
2241
+ const curatorMd = readCuratorMd();
2242
+ const curationPrompt = assembleCurationPrompt({
2243
+ recentMemories: recent,
2244
+ longtermSummary,
2245
+ curatorMd,
2246
+ pendingEngrams: pending,
2247
+ author,
2248
+ selectivity: config.cortex?.selectivity,
2249
+ granularity: config.cortex?.granularity,
2250
+ maxMemoriesPerRun: config.cortex?.maxMemoriesPerRun
2251
+ });
2252
+ let curationResult;
2102
2253
  try {
2103
- const newSummary = await runConsolidation(longtermSummary, older);
2104
- if (opts.dryRun) {
2105
- console.log();
2106
- console.log(chalk10.cyan("Proposed long-term summary:"));
2107
- console.log(newSummary);
2108
- return;
2109
- }
2110
- setLongtermSummary(cortex, newSummary);
2111
- console.log(chalk10.green("\u2713") + ` Long-term summary updated (${older.length} memories consolidated)`);
2254
+ curationResult = await runCuration(curationPrompt);
2112
2255
  } catch (err) {
2113
2256
  const message = err instanceof Error ? err.message : String(err);
2114
- console.error(chalk10.red(`Consolidation failed: ${message}`));
2257
+ console.error(chalk10.red(`Curation failed: ${message}`));
2258
+ closeCortexDb(cortex);
2115
2259
  process.exit(1);
2116
2260
  }
2117
- return;
2118
- }
2119
- const pending = getPendingEngrams(cortex);
2120
- if (pending.length === 0) {
2121
- console.log(chalk10.dim("No pending engrams to evaluate."));
2122
- closeCortexDb(cortex);
2123
- return;
2124
- }
2125
- console.log(chalk10.cyan(`Evaluating ${pending.length} engrams (${recent.length} recent memories, long-term summary ${longtermSummary ? "loaded" : "absent"})...`));
2126
- const curatorMd = readCuratorMd();
2127
- const curationPrompt = assembleCurationPrompt({
2128
- recentMemories: recent,
2129
- longtermSummary,
2130
- curatorMd,
2131
- pendingEngrams: pending,
2132
- author,
2133
- selectivity: config.cortex?.selectivity,
2134
- granularity: config.cortex?.granularity,
2135
- maxMemoriesPerRun: config.cortex?.maxMemoriesPerRun
2136
- });
2137
- let curationResult;
2138
- try {
2139
- curationResult = await runCuration(curationPrompt);
2140
- } catch (err) {
2141
- const message = err instanceof Error ? err.message : String(err);
2142
- console.error(chalk10.red(`Curation failed: ${message}`));
2143
- closeCortexDb(cortex);
2144
- process.exit(1);
2145
- }
2146
- const newEntries = curationResult.memories;
2147
- for (const entry of newEntries) {
2148
- entry.author = author;
2149
- if (!entry.ts) entry.ts = (/* @__PURE__ */ new Date()).toISOString();
2150
- }
2151
- const promotedIds = /* @__PURE__ */ new Set();
2152
- for (const entry of newEntries) {
2153
- for (const id of entry.source_ids) {
2154
- promotedIds.add(id);
2261
+ const newEntries = curationResult.memories;
2262
+ for (const entry of newEntries) {
2263
+ entry.author = author;
2264
+ if (!entry.ts) entry.ts = (/* @__PURE__ */ new Date()).toISOString();
2155
2265
  }
2156
- }
2157
- const pendingIdSet = new Set(pending.map((e) => e.id));
2158
- const purgedIds = curationResult.purgeIds.filter((id) => pendingIdSet.has(id) && !promotedIds.has(id));
2159
- const heldCount = pending.length - promotedIds.size - purgedIds.length;
2160
- if (opts.dryRun) {
2161
- console.log();
2162
- if (newEntries.length === 0) {
2163
- console.log(chalk10.dim("Curator would produce no new memories."));
2164
- } else {
2165
- console.log(chalk10.cyan("Would append:"));
2166
- for (const entry of newEntries) {
2167
- console.log(chalk10.green(` + `) + `[${entry.ts}] ${entry.content}`);
2266
+ const promotedIds = /* @__PURE__ */ new Set();
2267
+ for (const entry of newEntries) {
2268
+ for (const id of entry.source_ids) {
2269
+ promotedIds.add(id);
2168
2270
  }
2169
2271
  }
2170
- console.log();
2171
- console.log(`${pending.length} evaluated, ${newEntries.length} would promote, ${purgedIds.length} would purge, ${heldCount} would stay pending`);
2172
- closeCortexDb(cortex);
2173
- return;
2174
- }
2175
- if (config.cortex?.confirmBeforeCommit && newEntries.length > 0) {
2176
- console.log();
2177
- console.log(chalk10.cyan("Proposed memories:"));
2178
- for (let i = 0; i < newEntries.length; i++) {
2179
- console.log(chalk10.green(` ${i + 1}. `) + newEntries[i].content);
2180
- }
2181
- console.log();
2182
- const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
2183
- const answer = await new Promise((resolve) => {
2184
- rl.question(" Save these memories? [Y/n/edit] ", (ans) => {
2185
- rl.close();
2186
- resolve(ans.trim().toLowerCase());
2187
- });
2188
- });
2189
- if (answer === "n" || answer === "no") {
2190
- console.log(chalk10.dim(" Aborted. Engrams left as pending."));
2272
+ const pendingIdSet = new Set(pending.map((e) => e.id));
2273
+ const purgedIds = curationResult.purgeIds.filter((id) => pendingIdSet.has(id) && !promotedIds.has(id));
2274
+ const heldCount = pending.length - promotedIds.size - purgedIds.length;
2275
+ if (opts.dryRun) {
2276
+ console.log();
2277
+ if (newEntries.length === 0) {
2278
+ console.log(chalk10.dim("Curator would produce no new memories."));
2279
+ } else {
2280
+ console.log(chalk10.cyan("Would append:"));
2281
+ for (const entry of newEntries) {
2282
+ console.log(chalk10.green(` + `) + `[${entry.ts}] ${entry.content}`);
2283
+ }
2284
+ }
2285
+ console.log();
2286
+ console.log(`${pending.length} evaluated, ${newEntries.length} would promote, ${purgedIds.length} would purge, ${heldCount} would stay pending`);
2191
2287
  closeCortexDb(cortex);
2192
2288
  return;
2193
2289
  }
2194
- if (answer === "e" || answer === "edit") {
2290
+ if (config.cortex?.confirmBeforeCommit && newEntries.length > 0) {
2291
+ console.log();
2292
+ console.log(chalk10.cyan("Proposed memories:"));
2195
2293
  for (let i = 0; i < newEntries.length; i++) {
2196
- const editRl = readline3.createInterface({ input: process.stdin, output: process.stdout });
2197
- const edited = await new Promise((resolve) => {
2198
- editRl.question(` ${i + 1}. ${chalk10.dim("(enter to keep, or type replacement)")}
2294
+ console.log(chalk10.green(` ${i + 1}. `) + newEntries[i].content);
2295
+ }
2296
+ console.log();
2297
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
2298
+ const answer = await new Promise((resolve) => {
2299
+ rl.question(" Save these memories? [Y/n/edit] ", (ans) => {
2300
+ rl.close();
2301
+ resolve(ans.trim().toLowerCase());
2302
+ });
2303
+ });
2304
+ if (answer === "n" || answer === "no") {
2305
+ console.log(chalk10.dim(" Aborted. Engrams left as pending."));
2306
+ closeCortexDb(cortex);
2307
+ return;
2308
+ }
2309
+ if (answer === "e" || answer === "edit") {
2310
+ for (let i = 0; i < newEntries.length; i++) {
2311
+ const editRl = readline3.createInterface({ input: process.stdin, output: process.stdout });
2312
+ const edited = await new Promise((resolve) => {
2313
+ editRl.question(` ${i + 1}. ${chalk10.dim("(enter to keep, or type replacement)")}
2199
2314
  ${newEntries[i].content}
2200
2315
  > `, (ans) => {
2201
- editRl.close();
2202
- resolve(ans.trim());
2316
+ editRl.close();
2317
+ resolve(ans.trim());
2318
+ });
2203
2319
  });
2204
- });
2205
- if (edited) {
2206
- newEntries[i].content = edited;
2320
+ if (edited) {
2321
+ newEntries[i].content = edited;
2322
+ }
2207
2323
  }
2208
2324
  }
2209
2325
  }
2210
- }
2211
- if (newEntries.length > 0) {
2212
- for (const entry of newEntries) {
2213
- insertMemory(cortex, {
2214
- ts: entry.ts,
2215
- author: entry.author,
2216
- content: entry.content,
2217
- source_ids: entry.source_ids,
2218
- decisions: entry.decisions
2219
- });
2326
+ if (newEntries.length > 0) {
2327
+ for (const entry of newEntries) {
2328
+ insertMemory(cortex, {
2329
+ ts: entry.ts,
2330
+ author: entry.author,
2331
+ content: entry.content,
2332
+ source_ids: entry.source_ids,
2333
+ decisions: entry.decisions
2334
+ });
2335
+ }
2220
2336
  }
2221
- }
2222
- if (promotedIds.size > 0) {
2223
- markPromoted(cortex, [...promotedIds]);
2224
- }
2225
- if (purgedIds.length > 0) {
2226
- markPurged(cortex, purgedIds);
2227
- }
2228
- const pruned = pruneExpiredEngrams(cortex);
2229
- if (older.length > 0 && !longtermSummary) {
2230
- console.log(chalk10.dim(` Consolidating ${older.length} older memories into long-term summary...`));
2231
- try {
2232
- const newSummary = await runConsolidation(null, older);
2233
- setLongtermSummary(cortex, newSummary);
2234
- console.log(chalk10.dim(` Long-term summary created`));
2235
- } catch {
2236
- console.log(chalk10.dim(` Long-term consolidation skipped (will retry next run)`));
2337
+ if (promotedIds.size > 0) {
2338
+ markPromoted(cortex, [...promotedIds]);
2237
2339
  }
2238
- }
2239
- if (adapter?.isAvailable() && newEntries.length > 0) {
2240
- try {
2241
- const pushResult = await adapter.push(cortex);
2242
- if (pushResult.pushed > 0) {
2243
- console.log(chalk10.dim(` Pushed ${pushResult.pushed} memories to ${adapter.name}`));
2340
+ if (purgedIds.length > 0) {
2341
+ markPurged(cortex, purgedIds);
2342
+ }
2343
+ const pruned = pruneExpiredEngrams(cortex);
2344
+ if (older.length > 0 && !longtermSummary) {
2345
+ console.log(chalk10.dim(` Consolidating ${older.length} older memories into long-term summary...`));
2346
+ try {
2347
+ const newSummary = await runConsolidation(null, older);
2348
+ setLongtermSummary(cortex, newSummary);
2349
+ console.log(chalk10.dim(` Long-term summary created`));
2350
+ } catch {
2351
+ console.log(chalk10.dim(` Long-term consolidation skipped (will retry next run)`));
2244
2352
  }
2245
- } catch {
2246
- console.log(chalk10.dim(" Sync push skipped (remote unavailable) \u2014 will push on next sync"));
2247
2353
  }
2354
+ if (adapter?.isAvailable() && newEntries.length > 0) {
2355
+ try {
2356
+ const pushResult = await adapter.push(cortex);
2357
+ if (pushResult.pushed > 0) {
2358
+ console.log(chalk10.dim(` Pushed ${pushResult.pushed} memories to ${adapter.name}`));
2359
+ }
2360
+ } catch {
2361
+ console.log(chalk10.dim(" Sync push skipped (remote unavailable) \u2014 will push on next sync"));
2362
+ }
2363
+ }
2364
+ console.log();
2365
+ console.log(`${chalk10.green("\u2713")} Curation complete`);
2366
+ console.log(` ${pending.length} evaluated, ${newEntries.length} promoted, ${purgedIds.length} purged, ${heldCount} still pending`);
2367
+ if (pruned > 0) {
2368
+ console.log(` ${pruned} expired engrams pruned`);
2369
+ }
2370
+ closeCortexDb(cortex);
2371
+ } finally {
2372
+ releaseLock();
2248
2373
  }
2249
- console.log();
2250
- console.log(`${chalk10.green("\u2713")} Curation complete`);
2251
- console.log(` ${pending.length} evaluated, ${newEntries.length} promoted, ${purgedIds.length} purged, ${heldCount} still pending`);
2252
- if (pruned > 0) {
2253
- console.log(` ${pruned} expired engrams pruned`);
2254
- }
2255
- closeCortexDb(cortex);
2256
2374
  });
2257
2375
  var DEFAULT_IDLE_WINDOW_MINUTES = 3;
2258
2376
  var DEFAULT_STALE_WINDOW_MINUTES = 60;
@@ -2496,7 +2614,7 @@ memoryCommand.addCommand(addCommand);
2496
2614
  // src/commands/curator-cmd.ts
2497
2615
  import { Command as Command14 } from "commander";
2498
2616
  import { spawnSync } from "child_process";
2499
- import fs10 from "fs";
2617
+ import fs11 from "fs";
2500
2618
  import chalk14 from "chalk";
2501
2619
  var CURATOR_TEMPLATE = `# Curator Guidance
2502
2620
 
@@ -2515,8 +2633,8 @@ var curatorCommand = new Command14("curator").description("Manage personal curat
2515
2633
  curatorCommand.addCommand(new Command14("edit").description("Edit your curator guidance in $EDITOR").action(() => {
2516
2634
  ensureThinkDirs();
2517
2635
  const mdPath = getCuratorMdPath();
2518
- if (!fs10.existsSync(mdPath)) {
2519
- fs10.writeFileSync(mdPath, CURATOR_TEMPLATE, "utf-8");
2636
+ if (!fs11.existsSync(mdPath)) {
2637
+ fs11.writeFileSync(mdPath, CURATOR_TEMPLATE, "utf-8");
2520
2638
  }
2521
2639
  const editor = process.env.EDITOR || "vi";
2522
2640
  const result = spawnSync(editor, [mdPath], { stdio: "inherit" });
@@ -2528,8 +2646,8 @@ curatorCommand.addCommand(new Command14("edit").description("Edit your curator g
2528
2646
  }));
2529
2647
  curatorCommand.addCommand(new Command14("show").description("Print your current curator guidance").action(() => {
2530
2648
  const mdPath = getCuratorMdPath();
2531
- if (fs10.existsSync(mdPath)) {
2532
- console.log(fs10.readFileSync(mdPath, "utf-8"));
2649
+ if (fs11.existsSync(mdPath)) {
2650
+ console.log(fs11.readFileSync(mdPath, "utf-8"));
2533
2651
  } else {
2534
2652
  console.log(chalk14.dim("No curator guidance configured. Run: think curator edit"));
2535
2653
  }
@@ -2651,7 +2769,7 @@ var updateCommand = new Command18("update").description("Update think to the lat
2651
2769
 
2652
2770
  // src/commands/migrate-data.ts
2653
2771
  import { Command as Command19 } from "commander";
2654
- import fs11 from "fs";
2772
+ import fs12 from "fs";
2655
2773
  import chalk19 from "chalk";
2656
2774
  var migrateDataCommand = new Command19("migrate-data").description("Import existing memories from git into local SQLite (one-time migration)").action(async () => {
2657
2775
  const config = getConfig();
@@ -2689,8 +2807,8 @@ var migrateDataCommand = new Command19("migrate-data").description("Import exist
2689
2807
  if (wasInserted) inserted++;
2690
2808
  }
2691
2809
  const ltPath = getLongtermPath(cortex);
2692
- if (fs11.existsSync(ltPath)) {
2693
- const ltContent = fs11.readFileSync(ltPath, "utf-8").trim();
2810
+ if (fs12.existsSync(ltPath)) {
2811
+ const ltContent = fs12.readFileSync(ltPath, "utf-8").trim();
2694
2812
  if (ltContent) {
2695
2813
  setLongtermSummary(cortex, ltContent);
2696
2814
  console.log(chalk19.green(" \u2713") + " Long-term summary migrated");
@@ -2709,8 +2827,8 @@ var migrateDataCommand = new Command19("migrate-data").description("Import exist
2709
2827
  // src/index.ts
2710
2828
  function readPackageVersion() {
2711
2829
  try {
2712
- const pkgPath = path6.join(import.meta.dirname, "..", "package.json");
2713
- return JSON.parse(fs12.readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
2830
+ const pkgPath = path7.join(import.meta.dirname, "..", "package.json");
2831
+ return JSON.parse(fs13.readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
2714
2832
  } catch {
2715
2833
  return "0.0.0";
2716
2834
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-think",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "description": "Local-first CLI that gives AI agents persistent, curated memory",
6
6
  "bin": {