conare 0.4.4 → 0.4.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 +124 -126
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34,6 +34,27 @@ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync
34
34
  import { createHash } from "node:crypto";
35
35
  import { join as join2 } from "node:path";
36
36
  import { homedir as homedir2 } from "node:os";
37
+ function fitContent(header, rounds) {
38
+ const buildContent = (maxUser) => {
39
+ const body = rounds.map((r) => {
40
+ const user = maxUser > 0 && r.user.length > maxUser ? r.user.slice(0, maxUser) + "..." : r.user;
41
+ return `## Q: ${user}
42
+
43
+ ${r.assistant}`;
44
+ }).join(`
45
+
46
+ ---
47
+
48
+ `);
49
+ return `${header}
50
+
51
+ ${body}`;
52
+ };
53
+ const full = buildContent(0);
54
+ if (full.length <= MAX_MEMORY_CONTENT)
55
+ return full;
56
+ return buildContent(TRUNCATED_USER_MSG);
57
+ }
37
58
  function isNarration(text) {
38
59
  const stripped = text.trim();
39
60
  if (stripped.length < MIN_SUBSTANTIVE)
@@ -97,7 +118,7 @@ function clearIngested(source) {
97
118
  mkdirSync(dir, { recursive: true });
98
119
  writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
99
120
  }
100
- var MANIFEST_PATH, MIN_SUBSTANTIVE = 200, NARRATION_RE;
121
+ var MANIFEST_PATH, MAX_MEMORY_CONTENT = 200000, TRUNCATED_USER_MSG = 3000, MIN_SUBSTANTIVE = 200, NARRATION_RE;
101
122
  var init_shared = __esm(() => {
102
123
  MANIFEST_PATH = join2(homedir2(), ".conare", "ingested.json");
103
124
  NARRATION_RE = /^[\s\n]*(Let me |Now let me |Now I['\u2019]|Now add |Now fix |Now replace |Now integrate |Now update |Now pass |Now clean |Now build|Update the |Builds clean|Deployed\.|Wait, I |Let['\u2019]s test |Good —|Great\.|Perfect\.|Alright|OK,? let me|I[''\u2019]ll |Starting |I need to |Need |I found |I read |I[''\u2019]ve (loaded|confirmed|verified)|Context loaded|Next (I[''\u2019]|step)|Deps confirm|Diff check|Still missing|I[''\u2019]ll (do|check|inspect|trace|run|grab|pull|read|verify))/;
@@ -2088,7 +2109,6 @@ init_shared();
2088
2109
  import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
2089
2110
  import { join as join3, basename } from "node:path";
2090
2111
  import { homedir as homedir3, platform as platform3 } from "node:os";
2091
- var MAX_CONTENT = 48000;
2092
2112
  var MIN_TURN_LEN = 50;
2093
2113
  function resolveProjectName(dirName) {
2094
2114
  const segments = dirName.replace(/^-/, "").split("-");
@@ -2231,22 +2251,7 @@ function ingestClaude() {
2231
2251
  continue;
2232
2252
  }
2233
2253
  const header = `# Chat: ${project}${date ? ` | ${date}` : ""}`;
2234
- const body = turns.map((t) => {
2235
- return `## Q: ${t.user}
2236
-
2237
- ${t.assistant}`;
2238
- }).join(`
2239
-
2240
- ---
2241
-
2242
- `);
2243
- let content = `${header}
2244
-
2245
- ${body}`;
2246
- if (content.length > MAX_CONTENT)
2247
- content = content.slice(0, MAX_CONTENT) + `
2248
-
2249
- [truncated]`;
2254
+ const content = fitContent(header, turns);
2250
2255
  const contentHash = createContentHash(content);
2251
2256
  const dedupKey = `claude:${sessionId}`;
2252
2257
  const fingerprint = `${dedupKey}:${contentHash}`;
@@ -2277,7 +2282,6 @@ init_shared();
2277
2282
  import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
2278
2283
  import { join as join4, basename as basename2 } from "node:path";
2279
2284
  import { homedir as homedir4 } from "node:os";
2280
- var MAX_CONTENT2 = 48000;
2281
2285
  function isCodexBoilerplate(text) {
2282
2286
  return text.startsWith("# AGENTS.md instructions for") || text.startsWith("<INSTRUCTIONS>") || text.startsWith("<user_instructions>") || text.startsWith("<user_action>");
2283
2287
  }
@@ -2385,25 +2389,14 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
2385
2389
  stats.filtered++;
2386
2390
  continue;
2387
2391
  }
2388
- const body = rounds.map((r) => {
2389
- const assistant = r.assistantParts.join(`
2390
-
2391
- `);
2392
- return `## Q: ${r.user}
2393
-
2394
- ${assistant}`;
2395
- }).join(`
2392
+ const header = `# Codex Session${project ? `: ${project}` : ""} | ${date || "unknown"}`;
2393
+ const turns = rounds.map((r) => ({
2394
+ user: r.user,
2395
+ assistant: r.assistantParts.join(`
2396
2396
 
2397
- ---
2398
-
2399
- `);
2400
- let content = `# Codex Session${project ? `: ${project}` : ""} | ${date || "unknown"}
2401
-
2402
- ${body}`;
2403
- if (content.length > MAX_CONTENT2)
2404
- content = content.slice(0, MAX_CONTENT2) + `
2405
-
2406
- [truncated]`;
2397
+ `)
2398
+ }));
2399
+ const content = fitContent(header, turns);
2407
2400
  const contentHash = createContentHash(content);
2408
2401
  const dedupKey = `codex:${sessionId}`;
2409
2402
  const fingerprint = `${dedupKey}:${contentHash}`;
@@ -2435,7 +2428,6 @@ init_shared();
2435
2428
  import { readFileSync as readFileSync5, statSync } from "node:fs";
2436
2429
  import { join as join5 } from "node:path";
2437
2430
  import { createRequire as createRequire3 } from "node:module";
2438
- var MAX_CONTENT3 = 48000;
2439
2431
  var MAX_DB_SIZE = 2 * 1024 * 1024 * 1024;
2440
2432
  var WARN_DB_SIZE = 500 * 1024 * 1024;
2441
2433
  var MIN_TURN_LEN2 = 50;
@@ -2551,22 +2543,7 @@ async function ingestCursor(dbPath, wasmDir) {
2551
2543
  const sessionName = parsed.name || "Cursor Chat";
2552
2544
  const date = parsed.createdAt ? new Date(parsed.createdAt).toISOString().slice(0, 10) : "unknown";
2553
2545
  const header = `# ${sessionName} | ${date}`;
2554
- const body = turns.map((t) => {
2555
- return `## Q: ${t.user}
2556
-
2557
- ${t.assistant}`;
2558
- }).join(`
2559
-
2560
- ---
2561
-
2562
- `);
2563
- let content = `${header}
2564
-
2565
- ${body}`;
2566
- if (content.length > MAX_CONTENT3)
2567
- content = content.slice(0, MAX_CONTENT3) + `
2568
-
2569
- [truncated]`;
2546
+ const content = fitContent(header, turns);
2570
2547
  const contentHash = createContentHash(content);
2571
2548
  const dedupKey = `cursor:${composerId}`;
2572
2549
  const fingerprint = `${dedupKey}:${contentHash}`;
@@ -3115,31 +3092,6 @@ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) START sync" >> "$LOG"
3115
3092
  2>> "$LOG"
3116
3093
  echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) DONE sync (exit $?)" >> "$LOG"
3117
3094
  `;
3118
- function makePlist(intervalMinutes) {
3119
- const home = homedir7();
3120
- return `<?xml version="1.0" encoding="UTF-8"?>
3121
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
3122
- "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3123
- <plist version="1.0">
3124
- <dict>
3125
- <key>Label</key>
3126
- <string>${PLIST_LABEL}</string>
3127
- <key>ProgramArguments</key>
3128
- <array>
3129
- <string>/bin/bash</string>
3130
- <string>${home}/.conare/bin/run.sh</string>
3131
- </array>
3132
- <key>StartInterval</key>
3133
- <integer>${intervalMinutes * 60}</integer>
3134
- <key>StandardOutPath</key>
3135
- <string>${home}/.conare/ingest.log</string>
3136
- <key>StandardErrorPath</key>
3137
- <string>${home}/.conare/ingest.log</string>
3138
- <key>RunAtLoad</key>
3139
- <true/>
3140
- </dict>
3141
- </plist>`;
3142
- }
3143
3095
  var SYSTEMD_SERVICE_CONTENT = `[Unit]
3144
3096
  Description=Conare Memory — background sync
3145
3097
 
@@ -3241,22 +3193,12 @@ function findSqlJs() {
3241
3193
  }
3242
3194
  return null;
3243
3195
  }
3244
- function setupMacOS(intervalMinutes) {
3245
- const plistDir = dirname2(PLIST_PATH);
3246
- mkdirSync4(plistDir, { recursive: true });
3247
- writeFileSync4(PLIST_PATH, makePlist(intervalMinutes));
3248
- const id = uid();
3196
+ function cleanupOldLaunchAgent() {
3249
3197
  try {
3250
- execSync3(`launchctl bootout gui/${id} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
3198
+ execSync3(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
3251
3199
  } catch {}
3252
- try {
3253
- execSync3(`launchctl bootstrap gui/${id} "${PLIST_PATH}"`, { stdio: "ignore" });
3254
- } catch {
3255
- try {
3256
- execSync3(`launchctl load "${PLIST_PATH}"`, { stdio: "ignore" });
3257
- } catch {
3258
- throw new Error("Failed to load launchd agent. Try manually: launchctl load " + PLIST_PATH);
3259
- }
3200
+ if (existsSync8(PLIST_PATH)) {
3201
+ unlinkSync(PLIST_PATH);
3260
3202
  }
3261
3203
  }
3262
3204
  function setupLinuxSystemd(intervalMinutes) {
@@ -3266,13 +3208,30 @@ function setupLinuxSystemd(intervalMinutes) {
3266
3208
  execSync3("systemctl --user daemon-reload", { stdio: "ignore" });
3267
3209
  execSync3("systemctl --user enable --now conare-sync.timer", { stdio: "ignore" });
3268
3210
  }
3269
- function setupLinuxCron(intervalMinutes) {
3270
- const cronCmd = `${homedir7()}/.conare/bin/run.sh`;
3271
- const cronLine = `*/${intervalMinutes} * * * * ${cronCmd}`;
3211
+ var CRON_SAFE_INTERVALS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30];
3212
+ function clampCronInterval(intervalMinutes) {
3213
+ const n = Number.isFinite(intervalMinutes) ? Math.round(intervalMinutes) : 10;
3214
+ if (n <= 0)
3215
+ return 10;
3216
+ let best = 10;
3217
+ let bestDist = Infinity;
3218
+ for (const safe of CRON_SAFE_INTERVALS) {
3219
+ const dist = Math.abs(n - safe);
3220
+ if (dist < bestDist) {
3221
+ bestDist = dist;
3222
+ best = safe;
3223
+ }
3224
+ }
3225
+ return best;
3226
+ }
3227
+ function setupCron(intervalMinutes) {
3228
+ const clamped = clampCronInterval(intervalMinutes);
3229
+ const cronCmd = `"${homedir7()}/.conare/bin/run.sh"`;
3230
+ const cronLine = `*/${clamped} * * * * ${cronCmd} # conare-sync`;
3272
3231
  try {
3273
3232
  const existing = execSync3("crontab -l 2>/dev/null", { encoding: "utf-8" });
3274
3233
  const filtered = existing.split(`
3275
- `).filter((l) => !l.includes("conare")).join(`
3234
+ `).filter((l) => !l.endsWith("# conare-sync") && !l.includes(".conare/bin/run.sh")).join(`
3276
3235
  `);
3277
3236
  const newCrontab = (filtered.trim() ? filtered.trim() + `
3278
3237
  ` : "") + cronLine + `
@@ -3283,6 +3242,47 @@ function setupLinuxCron(intervalMinutes) {
3283
3242
  `, stdio: ["pipe", "ignore", "ignore"] });
3284
3243
  }
3285
3244
  }
3245
+ function removeCronEntry() {
3246
+ try {
3247
+ const existing = execSync3("crontab -l 2>/dev/null", { encoding: "utf-8" });
3248
+ const lines = existing.split(`
3249
+ `);
3250
+ const filtered = lines.filter((l) => !l.endsWith("# conare-sync") && !l.includes(".conare/bin/run.sh"));
3251
+ if (filtered.length === lines.length)
3252
+ return false;
3253
+ const newCrontab = filtered.join(`
3254
+ `).trim();
3255
+ if (newCrontab) {
3256
+ execSync3("crontab -", { input: newCrontab + `
3257
+ `, stdio: ["pipe", "ignore", "ignore"] });
3258
+ } else {
3259
+ execSync3("crontab -", { input: `
3260
+ `, stdio: ["pipe", "ignore", "ignore"] });
3261
+ }
3262
+ return true;
3263
+ } catch {
3264
+ return false;
3265
+ }
3266
+ }
3267
+ function runSyncNow() {
3268
+ const os = platform6();
3269
+ try {
3270
+ if (os === "win32") {
3271
+ const runCmd = join9(BIN_DIR, "run.cmd");
3272
+ if (existsSync8(runCmd)) {
3273
+ execSync3(`"${runCmd}"`, { stdio: "ignore", timeout: 60000 });
3274
+ return true;
3275
+ }
3276
+ } else {
3277
+ const runSh = join9(BIN_DIR, "run.sh");
3278
+ if (existsSync8(runSh)) {
3279
+ execSync3(`/bin/bash "${runSh}"`, { stdio: "ignore", timeout: 60000 });
3280
+ return true;
3281
+ }
3282
+ }
3283
+ } catch {}
3284
+ return false;
3285
+ }
3286
3286
  function installGlobalCommand() {
3287
3287
  const isWindows = platform6() === "win32";
3288
3288
  if (isWindows) {
@@ -3398,13 +3398,20 @@ function persistAndInstallGlobal(apiKey) {
3398
3398
  messages.push(globalMsg);
3399
3399
  return messages;
3400
3400
  }
3401
+ function sanitizeInterval(n) {
3402
+ if (!Number.isFinite(n) || n <= 0)
3403
+ return 10;
3404
+ return Math.max(1, Math.round(n));
3405
+ }
3401
3406
  function installSync(apiKey, intervalMinutes = 10) {
3402
3407
  const messages = persistAndInstallGlobal(apiKey);
3403
3408
  const os = platform6();
3409
+ intervalMinutes = sanitizeInterval(intervalMinutes);
3404
3410
  if (os === "darwin") {
3405
- setupMacOS(intervalMinutes);
3406
- messages.push(`Installed launchd agent (every ${intervalMinutes} min)`);
3407
- messages.push(`Plist: ${PLIST_PATH}`);
3411
+ cleanupOldLaunchAgent();
3412
+ const actual = clampCronInterval(intervalMinutes);
3413
+ setupCron(intervalMinutes);
3414
+ messages.push(`Installed cron job (every ${actual} min)`);
3408
3415
  } else if (os === "win32") {
3409
3416
  setupWindows(intervalMinutes);
3410
3417
  messages.push(`Installed Windows Task Scheduler (every ${intervalMinutes} min)`);
@@ -3413,25 +3420,23 @@ function installSync(apiKey, intervalMinutes = 10) {
3413
3420
  setupLinuxSystemd(intervalMinutes);
3414
3421
  messages.push(`Installed systemd timer (every ${intervalMinutes} min)`);
3415
3422
  } else {
3416
- setupLinuxCron(intervalMinutes);
3417
- messages.push(`Installed cron job (every ${intervalMinutes} min)`);
3423
+ const actual = clampCronInterval(intervalMinutes);
3424
+ setupCron(intervalMinutes);
3425
+ messages.push(`Installed cron job (every ${actual} min)`);
3418
3426
  }
3419
3427
  } else {
3420
3428
  messages.push(`Unsupported platform: ${os}. Run manually: ~/.conare/bin/run.sh`);
3421
3429
  }
3430
+ const syncOk = runSyncNow();
3431
+ messages.push(syncOk ? "First sync completed" : "First sync deferred to next timer tick");
3422
3432
  return messages;
3423
3433
  }
3424
3434
  function uninstallSync() {
3425
3435
  const messages = [];
3426
3436
  const os = platform6();
3427
3437
  if (os === "darwin") {
3428
- try {
3429
- execSync3(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
3430
- } catch {}
3431
- if (existsSync8(PLIST_PATH)) {
3432
- unlinkSync(PLIST_PATH);
3433
- messages.push("Removed launchd agent");
3434
- }
3438
+ removeCronEntry() && messages.push("Removed cron job");
3439
+ cleanupOldLaunchAgent();
3435
3440
  } else if (os === "win32") {
3436
3441
  try {
3437
3442
  execSync3(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
@@ -3451,25 +3456,18 @@ function uninstallSync() {
3451
3456
  } catch {}
3452
3457
  messages.push("Removed systemd timer");
3453
3458
  } else {
3454
- try {
3455
- const existing = execSync3("crontab -l 2>/dev/null", { encoding: "utf-8" });
3456
- const filtered = existing.split(`
3457
- `).filter((l) => !l.includes("conare")).join(`
3458
- `);
3459
- execSync3("crontab -", { input: filtered.trim() + `
3460
- `, stdio: ["pipe", "ignore", "ignore"] });
3461
- messages.push("Removed cron job");
3462
- } catch {}
3459
+ removeCronEntry() && messages.push("Removed cron job");
3463
3460
  }
3464
3461
  }
3465
- const filesToRemove = [
3466
- join9(CONARE_DIR, "config.json"),
3467
- join9(CONARE_DIR, "sync.lock")
3468
- ];
3469
- for (const f of filesToRemove) {
3470
- if (existsSync8(f))
3471
- unlinkSync(f);
3472
- }
3462
+ const configPath = join9(CONARE_DIR, "config.json");
3463
+ if (existsSync8(configPath))
3464
+ unlinkSync(configPath);
3465
+ const lockDir = join9(CONARE_DIR, "sync.lock.d");
3466
+ if (existsSync8(lockDir))
3467
+ rmSync2(lockDir, { recursive: true, force: true });
3468
+ const lockFile = join9(CONARE_DIR, "sync.lock");
3469
+ if (existsSync8(lockFile))
3470
+ unlinkSync(lockFile);
3473
3471
  if (existsSync8(BIN_DIR)) {
3474
3472
  rmSync2(BIN_DIR, { recursive: true, force: true });
3475
3473
  messages.push("Removed ~/.conare/bin/");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conare",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Conare CLI for ingesting AI chat history and configuring memory at conare.ai",
5
5
  "type": "module",
6
6
  "bin": {