conare 0.1.6 → 0.2.1

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 +463 -85
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -148,8 +148,8 @@ var require_picocolors = __commonJS((exports, module) => {
148
148
  });
149
149
 
150
150
  // src/index.ts
151
- import { existsSync as existsSync7 } from "node:fs";
152
- import { join as join9 } from "node:path";
151
+ import { existsSync as existsSync8 } from "node:fs";
152
+ import { join as join10 } from "node:path";
153
153
 
154
154
  // src/detect.ts
155
155
  import { existsSync, readdirSync } from "node:fs";
@@ -1275,6 +1275,308 @@ function getSavedApiKey() {
1275
1275
  return readConfig().apiKey;
1276
1276
  }
1277
1277
 
1278
+ // src/sync.ts
1279
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync8, chmodSync, cpSync, rmSync } from "node:fs";
1280
+ import { join as join9, dirname as dirname2 } from "node:path";
1281
+ import { homedir as homedir7, platform as platform2 } from "node:os";
1282
+ import { execSync } from "node:child_process";
1283
+ var CONARE_DIR = join9(homedir7(), ".conare");
1284
+ var BIN_DIR = join9(CONARE_DIR, "bin");
1285
+ var CONFIG_PATH2 = join9(CONARE_DIR, "config.json");
1286
+ var PLIST_LABEL = "ai.conare.ingest";
1287
+ var PLIST_PATH = join9(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
1288
+ var SYSTEMD_DIR = join9(homedir7(), ".config", "systemd", "user");
1289
+ var SYSTEMD_SERVICE = join9(SYSTEMD_DIR, "conare-sync.service");
1290
+ var SYSTEMD_TIMER = join9(SYSTEMD_DIR, "conare-sync.timer");
1291
+ var RUN_SH = `#!/bin/bash
1292
+ # Conare Memory — background sync wrapper
1293
+ # Resolves node at runtime to survive nvm/fnm upgrades
1294
+
1295
+ CONARE_DIR="$HOME/.conare"
1296
+ LOCKFILE="$CONARE_DIR/sync.lock"
1297
+ LOG="$CONARE_DIR/ingest.log"
1298
+
1299
+ # Log rotation: truncate if > 1MB
1300
+ if [ -f "$LOG" ] && [ "$(wc -c < "$LOG" 2>/dev/null)" -gt 1048576 ]; then
1301
+ tail -100 "$LOG" > "$LOG.tmp" && mv "$LOG.tmp" "$LOG"
1302
+ fi
1303
+
1304
+ # File lock: prevent concurrent runs
1305
+ # macOS doesn't have flock — use mkdir as atomic lock
1306
+ if ! mkdir "$LOCKFILE.d" 2>/dev/null; then
1307
+ # Check if stale (older than 30 min)
1308
+ if [ "$(uname)" = "Darwin" ]; then
1309
+ LOCK_AGE=$(( $(date +%s) - $(stat -f %m "$LOCKFILE.d" 2>/dev/null || echo 0) ))
1310
+ else
1311
+ LOCK_AGE=$(( $(date +%s) - $(stat -c %Y "$LOCKFILE.d" 2>/dev/null || echo 0) ))
1312
+ fi
1313
+ if [ "$LOCK_AGE" -gt 1800 ]; then
1314
+ rm -rf "$LOCKFILE.d"
1315
+ mkdir "$LOCKFILE.d" 2>/dev/null || true
1316
+ else
1317
+ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) SKIP: another instance running" >> "$LOG"
1318
+ exit 0
1319
+ fi
1320
+ fi
1321
+ trap 'rm -rf "$LOCKFILE.d"' EXIT
1322
+
1323
+ # Resolve node binary
1324
+ resolve_node() {
1325
+ command -v node >/dev/null 2>&1 && { command -v node; return; }
1326
+ if [ -s "$HOME/.nvm/nvm.sh" ]; then
1327
+ . "$HOME/.nvm/nvm.sh" >/dev/null 2>&1
1328
+ command -v node >/dev/null 2>&1 && { command -v node; return; }
1329
+ fi
1330
+ if command -v fnm >/dev/null 2>&1; then
1331
+ eval "$(fnm env)" >/dev/null 2>&1
1332
+ command -v node >/dev/null 2>&1 && { command -v node; return; }
1333
+ fi
1334
+ for p in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do
1335
+ [ -x "$p" ] && { echo "$p"; return; }
1336
+ done
1337
+ return 1
1338
+ }
1339
+
1340
+ NODE=$(resolve_node)
1341
+ if [ -z "$NODE" ]; then
1342
+ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) ERROR: node not found" >> "$LOG"
1343
+ exit 1
1344
+ fi
1345
+
1346
+ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) START sync" >> "$LOG"
1347
+ "$NODE" "$CONARE_DIR/bin/conare-ingest.mjs" \\
1348
+ --config-file "$CONARE_DIR/config.json" \\
1349
+ --ingest-only \\
1350
+ --quiet \\
1351
+ 2>> "$LOG"
1352
+ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) DONE sync (exit $?)" >> "$LOG"
1353
+ `;
1354
+ function makePlist() {
1355
+ const home = homedir7();
1356
+ return `<?xml version="1.0" encoding="UTF-8"?>
1357
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
1358
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1359
+ <plist version="1.0">
1360
+ <dict>
1361
+ <key>Label</key>
1362
+ <string>${PLIST_LABEL}</string>
1363
+ <key>ProgramArguments</key>
1364
+ <array>
1365
+ <string>/bin/bash</string>
1366
+ <string>${home}/.conare/bin/run.sh</string>
1367
+ </array>
1368
+ <key>StartInterval</key>
1369
+ <integer>600</integer>
1370
+ <key>StandardOutPath</key>
1371
+ <string>${home}/.conare/ingest.log</string>
1372
+ <key>StandardErrorPath</key>
1373
+ <string>${home}/.conare/ingest.log</string>
1374
+ <key>RunAtLoad</key>
1375
+ <true/>
1376
+ </dict>
1377
+ </plist>`;
1378
+ }
1379
+ var SYSTEMD_SERVICE_CONTENT = `[Unit]
1380
+ Description=Conare Memory — background sync
1381
+
1382
+ [Service]
1383
+ Type=oneshot
1384
+ ExecStart=%h/.conare/bin/run.sh
1385
+ `;
1386
+ var SYSTEMD_TIMER_CONTENT = `[Unit]
1387
+ Description=Conare Memory — sync every 10 minutes
1388
+
1389
+ [Timer]
1390
+ OnBootSec=2min
1391
+ OnUnitActiveSec=10min
1392
+ Persistent=true
1393
+
1394
+ [Install]
1395
+ WantedBy=timers.target
1396
+ `;
1397
+ function hasSystemd() {
1398
+ try {
1399
+ execSync("systemctl --user status 2>/dev/null", { stdio: "ignore" });
1400
+ return true;
1401
+ } catch {
1402
+ return false;
1403
+ }
1404
+ }
1405
+ function uid() {
1406
+ try {
1407
+ return execSync("id -u", { encoding: "utf-8" }).trim();
1408
+ } catch {
1409
+ return "501";
1410
+ }
1411
+ }
1412
+ function persistBinary(apiKey) {
1413
+ mkdirSync4(BIN_DIR, { recursive: true });
1414
+ const cliEntry = findCliBundle();
1415
+ if (!cliEntry) {
1416
+ throw new Error("Could not locate CLI bundle. Run from an installed conare package.");
1417
+ }
1418
+ const dest = join9(BIN_DIR, "conare-ingest.mjs");
1419
+ const content = readFileSync8(cliEntry, "utf-8");
1420
+ writeFileSync4(dest, content);
1421
+ const sqlJsDir = findSqlJs();
1422
+ if (sqlJsDir) {
1423
+ const targetDir = join9(BIN_DIR, "node_modules", "sql.js");
1424
+ mkdirSync4(targetDir, { recursive: true });
1425
+ try {
1426
+ cpSync(sqlJsDir, targetDir, { recursive: true });
1427
+ } catch {}
1428
+ }
1429
+ const runShPath = join9(BIN_DIR, "run.sh");
1430
+ writeFileSync4(runShPath, RUN_SH);
1431
+ chmodSync(runShPath, 493);
1432
+ writeFileSync4(CONFIG_PATH2, JSON.stringify({ apiKey }, null, 2) + `
1433
+ `);
1434
+ }
1435
+ function findCliBundle() {
1436
+ const entry = process.argv[1];
1437
+ if (entry && existsSync7(entry))
1438
+ return entry;
1439
+ const candidates = [
1440
+ join9(dirname2(new URL(import.meta.url).pathname), "index.js"),
1441
+ join9(dirname2(new URL(import.meta.url).pathname), "..", "dist", "index.js")
1442
+ ];
1443
+ for (const c of candidates) {
1444
+ if (existsSync7(c))
1445
+ return c;
1446
+ }
1447
+ return null;
1448
+ }
1449
+ function findSqlJs() {
1450
+ const candidates = [
1451
+ join9(process.cwd(), "node_modules", "sql.js"),
1452
+ join9(dirname2(new URL(import.meta.url).pathname), "..", "node_modules", "sql.js"),
1453
+ join9(dirname2(new URL(import.meta.url).pathname), "..", "..", "node_modules", "sql.js")
1454
+ ];
1455
+ for (const c of candidates) {
1456
+ if (existsSync7(c))
1457
+ return c;
1458
+ }
1459
+ return null;
1460
+ }
1461
+ function setupMacOS() {
1462
+ const plistDir = dirname2(PLIST_PATH);
1463
+ mkdirSync4(plistDir, { recursive: true });
1464
+ writeFileSync4(PLIST_PATH, makePlist());
1465
+ const id = uid();
1466
+ try {
1467
+ execSync(`launchctl bootout gui/${id} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
1468
+ } catch {}
1469
+ try {
1470
+ execSync(`launchctl bootstrap gui/${id} "${PLIST_PATH}"`, { stdio: "ignore" });
1471
+ } catch {
1472
+ try {
1473
+ execSync(`launchctl load "${PLIST_PATH}"`, { stdio: "ignore" });
1474
+ } catch {
1475
+ throw new Error("Failed to load launchd agent. Try manually: launchctl load " + PLIST_PATH);
1476
+ }
1477
+ }
1478
+ }
1479
+ function setupLinuxSystemd() {
1480
+ mkdirSync4(SYSTEMD_DIR, { recursive: true });
1481
+ writeFileSync4(SYSTEMD_SERVICE, SYSTEMD_SERVICE_CONTENT);
1482
+ writeFileSync4(SYSTEMD_TIMER, SYSTEMD_TIMER_CONTENT);
1483
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
1484
+ execSync("systemctl --user enable --now conare-sync.timer", { stdio: "ignore" });
1485
+ }
1486
+ function setupLinuxCron() {
1487
+ const cronCmd = `${homedir7()}/.conare/bin/run.sh`;
1488
+ const cronLine = `*/10 * * * * ${cronCmd}`;
1489
+ try {
1490
+ const existing = execSync("crontab -l 2>/dev/null", { encoding: "utf-8" });
1491
+ const filtered = existing.split(`
1492
+ `).filter((l) => !l.includes("conare")).join(`
1493
+ `);
1494
+ const newCrontab = (filtered.trim() ? filtered.trim() + `
1495
+ ` : "") + cronLine + `
1496
+ `;
1497
+ execSync("crontab -", { input: newCrontab, stdio: ["pipe", "ignore", "ignore"] });
1498
+ } catch {
1499
+ execSync("crontab -", { input: cronLine + `
1500
+ `, stdio: ["pipe", "ignore", "ignore"] });
1501
+ }
1502
+ }
1503
+ function installSync(apiKey) {
1504
+ const messages = [];
1505
+ const os = platform2();
1506
+ persistBinary(apiKey);
1507
+ messages.push("Persisted CLI to ~/.conare/bin/");
1508
+ messages.push("Saved config to ~/.conare/config.json");
1509
+ if (os === "darwin") {
1510
+ setupMacOS();
1511
+ messages.push("Installed launchd agent (every 10 min)");
1512
+ messages.push(`Plist: ${PLIST_PATH}`);
1513
+ } else if (os === "linux") {
1514
+ if (hasSystemd()) {
1515
+ setupLinuxSystemd();
1516
+ messages.push("Installed systemd timer (every 10 min)");
1517
+ } else {
1518
+ setupLinuxCron();
1519
+ messages.push("Installed cron job (every 10 min)");
1520
+ }
1521
+ } else {
1522
+ messages.push(`Unsupported platform: ${os}. Run manually: ~/.conare/bin/run.sh`);
1523
+ }
1524
+ return messages;
1525
+ }
1526
+ function uninstallSync() {
1527
+ const messages = [];
1528
+ const os = platform2();
1529
+ if (os === "darwin") {
1530
+ try {
1531
+ execSync(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
1532
+ } catch {}
1533
+ if (existsSync7(PLIST_PATH)) {
1534
+ unlinkSync(PLIST_PATH);
1535
+ messages.push("Removed launchd agent");
1536
+ }
1537
+ } else if (os === "linux") {
1538
+ if (hasSystemd()) {
1539
+ try {
1540
+ execSync("systemctl --user disable --now conare-sync.timer 2>/dev/null", { stdio: "ignore" });
1541
+ } catch {}
1542
+ if (existsSync7(SYSTEMD_SERVICE))
1543
+ unlinkSync(SYSTEMD_SERVICE);
1544
+ if (existsSync7(SYSTEMD_TIMER))
1545
+ unlinkSync(SYSTEMD_TIMER);
1546
+ try {
1547
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
1548
+ } catch {}
1549
+ messages.push("Removed systemd timer");
1550
+ } else {
1551
+ try {
1552
+ const existing = execSync("crontab -l 2>/dev/null", { encoding: "utf-8" });
1553
+ const filtered = existing.split(`
1554
+ `).filter((l) => !l.includes("conare")).join(`
1555
+ `);
1556
+ execSync("crontab -", { input: filtered.trim() + `
1557
+ `, stdio: ["pipe", "ignore", "ignore"] });
1558
+ messages.push("Removed cron job");
1559
+ } catch {}
1560
+ }
1561
+ }
1562
+ const filesToRemove = [
1563
+ join9(CONARE_DIR, "config.json"),
1564
+ join9(CONARE_DIR, "sync.lock")
1565
+ ];
1566
+ for (const f of filesToRemove) {
1567
+ if (existsSync7(f))
1568
+ unlinkSync(f);
1569
+ }
1570
+ if (existsSync7(BIN_DIR)) {
1571
+ rmSync(BIN_DIR, { recursive: true, force: true });
1572
+ messages.push("Removed ~/.conare/bin/");
1573
+ }
1574
+ if (messages.length === 0) {
1575
+ messages.push("Nothing to uninstall (no sync timer found)");
1576
+ }
1577
+ return messages;
1578
+ }
1579
+
1278
1580
  // node_modules/@clack/prompts/dist/index.mjs
1279
1581
  import { stripVTControlCharacters as S2 } from "node:util";
1280
1582
 
@@ -1987,21 +2289,33 @@ function renderDiscoverySummary(discovered, filtered, deduped) {
1987
2289
  function parseArgs() {
1988
2290
  const args = process.argv.slice(2);
1989
2291
  let key = "";
2292
+ let configFile;
1990
2293
  let dryRun = false;
1991
2294
  let force = false;
1992
2295
  let ingestOnly = false;
1993
2296
  let configOnly = false;
1994
2297
  let interactive = false;
2298
+ let quiet = false;
2299
+ let installSyncFlag = false;
2300
+ let uninstallSyncFlag = false;
1995
2301
  let source;
1996
2302
  let wasmDir;
1997
2303
  let indexPath;
1998
2304
  for (let i = 0;i < args.length; i++) {
1999
2305
  if (args[i] === "--key" && args[i + 1]) {
2000
2306
  key = args[++i];
2307
+ } else if (args[i] === "--config-file" && args[i + 1]) {
2308
+ configFile = args[++i];
2001
2309
  } else if (args[i] === "--dry-run") {
2002
2310
  dryRun = true;
2003
2311
  } else if (args[i] === "--force") {
2004
2312
  force = true;
2313
+ } else if (args[i] === "--quiet") {
2314
+ quiet = true;
2315
+ } else if (args[i] === "--install-sync") {
2316
+ installSyncFlag = true;
2317
+ } else if (args[i] === "--uninstall-sync") {
2318
+ uninstallSyncFlag = true;
2005
2319
  } else if (args[i] === "--source" && args[i + 1]) {
2006
2320
  source = args[++i];
2007
2321
  } else if (args[i] === "--wasm-dir" && args[i + 1]) {
@@ -2024,9 +2338,13 @@ Usage:
2024
2338
 
2025
2339
  Options:
2026
2340
  --key <key> Your Conare API key (required, starts with cmem_)
2341
+ --config-file <path> Read API key from JSON config file (e.g. ~/.conare/config.json)
2027
2342
  --index [path] Index codebase at path (default: current directory)
2028
2343
  --dry-run Preview what would be uploaded
2029
2344
  --force Re-ingest all / re-index all (bypass dedup)
2345
+ --quiet Suppress all stdout output (for background timer runs)
2346
+ --install-sync Set up automatic background sync (every 10 min)
2347
+ --uninstall-sync Remove background sync timer and persisted files
2030
2348
  --ingest-only Ingest memories without MCP configuration
2031
2349
  --config-only Configure MCP only, skip ingestion
2032
2350
  --interactive Run guided setup prompts
@@ -2046,20 +2364,37 @@ Get your API key at https://mcp.conare.ai
2046
2364
  console.error("Error: --ingest-only and --config-only cannot be used together");
2047
2365
  process.exit(1);
2048
2366
  }
2049
- return { key, dryRun, force, ingestOnly, configOnly, interactive, source, wasmDir, indexPath };
2367
+ return { key, configFile, dryRun, force, ingestOnly, configOnly, interactive, quiet, installSync: installSyncFlag, uninstallSync: uninstallSyncFlag, source, wasmDir, indexPath };
2050
2368
  }
2051
2369
  async function main() {
2052
2370
  const opts = parseArgs();
2371
+ let configFileKey;
2372
+ if (opts.configFile) {
2373
+ try {
2374
+ const { readFileSync: readFileSync9 } = await import("node:fs");
2375
+ const raw = JSON.parse(readFileSync9(opts.configFile, "utf-8"));
2376
+ configFileKey = raw.apiKey || raw.key;
2377
+ if (!configFileKey) {
2378
+ console.error(`Error: no apiKey/key found in ${opts.configFile}`);
2379
+ process.exit(1);
2380
+ }
2381
+ } catch (e2) {
2382
+ console.error(`Error reading config file: ${e2.message}`);
2383
+ process.exit(1);
2384
+ }
2385
+ }
2386
+ const log = opts.quiet ? (..._args) => {} : console.log.bind(console);
2387
+ const write = opts.quiet ? (_s) => {} : (s) => process.stdout.write(s);
2053
2388
  const savedApiKey = getSavedApiKey();
2054
2389
  const hasTty = !!process.stdin.isTTY && !!process.stdout.isTTY;
2055
- const shouldRunInteractive = hasTty && !opts.dryRun && (opts.interactive || !opts.force && !opts.source && !opts.indexPath && !opts.configOnly && !opts.ingestOnly && !opts.wasmDir);
2390
+ const shouldRunInteractive = hasTty && !opts.dryRun && !opts.quiet && (opts.interactive || !opts.force && !opts.source && !opts.indexPath && !opts.configOnly && !opts.ingestOnly && !opts.wasmDir);
2056
2391
  let selectedTargets = MCP_TARGETS.map((target) => target.id);
2057
2392
  let effectiveConfigOnly = opts.configOnly;
2058
2393
  let effectiveIngestOnly = opts.ingestOnly;
2059
2394
  let effectiveIndexPath = opts.indexPath;
2060
2395
  let postIngestIndexPath;
2061
2396
  let selectedSources = opts.source ? [opts.source] : ["claude", "cursor", "codex"];
2062
- let apiKey = opts.key || process.env.CONARE_API_KEY || savedApiKey;
2397
+ let apiKey = opts.key || configFileKey || process.env.CONARE_API_KEY || savedApiKey;
2063
2398
  let interactiveTargets = MCP_TARGETS.map((target) => ({
2064
2399
  id: target.id,
2065
2400
  label: target.label,
@@ -2089,180 +2424,207 @@ async function main() {
2089
2424
  selectedSources = await selectChatSources(interactiveTargets);
2090
2425
  interactiveMode = true;
2091
2426
  }
2427
+ if (opts.uninstallSync) {
2428
+ const messages = uninstallSync();
2429
+ for (const msg of messages)
2430
+ console.log(` ${msg}`);
2431
+ return;
2432
+ }
2092
2433
  if (!apiKey) {
2093
2434
  printMissingKeyError();
2094
2435
  process.exit(1);
2095
2436
  }
2096
- console.log("");
2097
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2098
- console.log(" Conare Installer");
2099
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2100
- console.log("");
2101
- process.stdout.write("Validating API key... ");
2437
+ if (opts.installSync) {
2438
+ console.log("");
2439
+ console.log("Setting up background sync...");
2440
+ console.log("");
2441
+ const auth2 = await validateKey(apiKey);
2442
+ if (!auth2.valid) {
2443
+ console.error("INVALID key. Check your key at https://mcp.conare.ai");
2444
+ process.exit(1);
2445
+ }
2446
+ const messages = installSync(apiKey);
2447
+ for (const msg of messages)
2448
+ console.log(` ✓ ${msg}`);
2449
+ console.log("");
2450
+ console.log("Background sync active. New chats will be ingested every 10 minutes.");
2451
+ console.log("Logs: ~/.conare/ingest.log");
2452
+ console.log("Remove with: conare --uninstall-sync");
2453
+ console.log("");
2454
+ return;
2455
+ }
2456
+ log("");
2457
+ log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2458
+ log(" Conare Installer");
2459
+ log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2460
+ log("");
2461
+ write("Validating API key... ");
2102
2462
  const auth = await validateKey(apiKey);
2103
2463
  if (!auth.valid) {
2104
2464
  console.error("INVALID. Check your key at https://mcp.conare.ai");
2105
2465
  process.exit(1);
2106
2466
  }
2107
- console.log(auth.email ? `OK (${auth.email})` : "OK");
2467
+ log(auth.email ? `OK (${auth.email})` : "OK");
2108
2468
  saveApiKey(apiKey);
2109
- console.log();
2469
+ log();
2110
2470
  if (!opts.force && !opts.dryRun) {
2111
2471
  const remoteMemoryCount = await getRemoteMemoryCount(apiKey);
2112
2472
  const localManifest = getIngested();
2113
2473
  const localManifestCount = Object.values(localManifest).reduce((sum, entries) => sum + entries.length, 0);
2114
2474
  if (remoteMemoryCount === 0 && localManifestCount > 0) {
2115
2475
  clearIngested();
2116
- console.log("Remote account is empty; cleared stale local ingestion history.");
2117
- console.log("");
2476
+ log("Remote account is empty; cleared stale local ingestion history.");
2477
+ log("");
2118
2478
  }
2119
2479
  }
2120
- if (!opts.wasmDir && existsSync7(join9(process.cwd(), "node_modules", "sql.js"))) {
2121
- opts.wasmDir = join9(process.cwd(), "node_modules");
2480
+ if (!opts.wasmDir && existsSync8(join10(process.cwd(), "node_modules", "sql.js"))) {
2481
+ opts.wasmDir = join10(process.cwd(), "node_modules");
2122
2482
  }
2123
2483
  if (effectiveConfigOnly) {
2124
2484
  if (!opts.dryRun) {
2125
- console.log("─── Configuring MCP ───");
2126
- console.log("");
2485
+ log("─── Configuring MCP ───");
2486
+ log("");
2127
2487
  for (const line of configureMcp(apiKey, selectedTargets)) {
2128
- console.log(` ✓ ${line}`);
2488
+ log(` ✓ ${line}`);
2129
2489
  }
2130
- console.log("");
2490
+ log("");
2131
2491
  }
2132
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2133
- console.log(" ✓ Done! Conare MCP is configured.");
2134
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2135
- console.log("");
2492
+ log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2493
+ log(" ✓ Done! Conare MCP is configured.");
2494
+ log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2495
+ log("");
2136
2496
  return;
2137
2497
  }
2138
2498
  if (effectiveIndexPath !== undefined) {
2139
2499
  const { resolve: resolve2 } = await import("node:path");
2140
2500
  const absPath = resolve2(effectiveIndexPath);
2141
- console.log(`Indexing codebase: ${absPath}`);
2501
+ log(`Indexing codebase: ${absPath}`);
2142
2502
  if (opts.force) {
2143
2503
  clearIngested("codebase");
2144
- process.stdout.write("Clearing existing codebase index... ");
2504
+ write("Clearing existing codebase index... ");
2145
2505
  try {
2146
2506
  await fetch("https://mcp.conare.ai/api/containers/codebase", {
2147
2507
  method: "DELETE",
2148
2508
  headers: { Authorization: `Bearer ${apiKey}` }
2149
2509
  });
2150
- console.log("done");
2510
+ log("done");
2151
2511
  } catch {
2152
- console.log("skipped (no existing index)");
2512
+ log("skipped (no existing index)");
2153
2513
  }
2154
2514
  }
2155
- process.stdout.write("Scanning files... ");
2515
+ write("Scanning files... ");
2156
2516
  const { memories, fileCount, skipped } = indexCodebase(absPath);
2157
- console.log(`${fileCount} files found, ${skipped} skipped`);
2517
+ log(`${fileCount} files found, ${skipped} skipped`);
2158
2518
  if (memories.length === 0) {
2159
- console.log(`
2519
+ log(`
2160
2520
  Nothing new to index.`);
2161
2521
  } else if (opts.dryRun) {
2162
- console.log(`
2522
+ log(`
2163
2523
  [DRY RUN] Would upload ${memories.length} files`);
2164
2524
  for (const m2 of memories.slice(0, 10)) {
2165
2525
  const meta = m2.metadata;
2166
- console.log(` ${meta.language.padEnd(12)} ${meta.filePath}`);
2526
+ log(` ${meta.language.padEnd(12)} ${meta.filePath}`);
2167
2527
  }
2168
2528
  if (memories.length > 10)
2169
- console.log(` ... and ${memories.length - 10} more`);
2529
+ log(` ... and ${memories.length - 10} more`);
2170
2530
  } else {
2171
2531
  const barWidth = 20;
2172
2532
  const { success, failed, results } = await uploadBulk(apiKey, memories, (uploaded, total, _failed) => {
2173
2533
  const pct = (uploaded / total * 100).toFixed(1);
2174
2534
  const filled = Math.round(uploaded / total * barWidth);
2175
2535
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
2176
- process.stdout.write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2536
+ write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2177
2537
  });
2178
- process.stdout.write(renderProgressSummary(success, failed, "indexed"));
2538
+ write(renderProgressSummary(success, failed, "indexed"));
2179
2539
  const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
2180
2540
  markIngested("codebase", fileHashes);
2181
- printFailureSummary(results, memories);
2541
+ if (!opts.quiet)
2542
+ printFailureSummary(results, memories);
2182
2543
  }
2183
- console.log();
2544
+ log();
2184
2545
  if (!opts.dryRun && !effectiveIngestOnly) {
2185
- console.log("─── Configuring MCP ───");
2186
- console.log("");
2546
+ log("─── Configuring MCP ───");
2547
+ log("");
2187
2548
  for (const line of configureMcp(apiKey, selectedTargets)) {
2188
- console.log(` ✓ ${line}`);
2549
+ log(` ✓ ${line}`);
2189
2550
  }
2190
- console.log("");
2551
+ log("");
2191
2552
  }
2192
- console.log("Done! Your codebase is now searchable via recall.");
2553
+ log("Done! Your codebase is now searchable via recall.");
2193
2554
  return;
2194
2555
  }
2195
2556
  if (opts.force) {
2196
2557
  if (opts.source) {
2197
2558
  clearIngested(opts.source);
2198
- console.log(`Cleared ingestion history for ${opts.source}`);
2559
+ log(`Cleared ingestion history for ${opts.source}`);
2199
2560
  } else {
2200
2561
  clearIngested();
2201
- console.log("Cleared all ingestion history");
2562
+ log("Cleared all ingestion history");
2202
2563
  }
2203
- console.log();
2564
+ log();
2204
2565
  }
2205
2566
  const tools = detect();
2206
2567
  if (!interactiveMode) {
2207
- console.log("Detected AI tools:");
2568
+ log("Detected AI tools:");
2208
2569
  for (const t of tools) {
2209
2570
  if (t.available) {
2210
- console.log(` + ${t.name}${t.sessionCount || t.sessionCount === 0 ? ` — ${t.sessionCount} sessions` : ""}`);
2571
+ log(` + ${t.name}${t.sessionCount || t.sessionCount === 0 ? ` — ${t.sessionCount} sessions` : ""}`);
2211
2572
  } else {
2212
- console.log(` - ${t.name} (not found)`);
2573
+ log(` - ${t.name} (not found)`);
2213
2574
  }
2214
2575
  }
2215
- console.log();
2576
+ log();
2216
2577
  }
2217
2578
  const allMemories = [];
2218
2579
  const shouldIngest = (name) => selectedSources.includes(name);
2219
2580
  if (shouldIngest("claude") && tools.find((t) => t.name === "Claude Code")?.available) {
2220
- process.stdout.write("Ingesting Claude Code... ");
2581
+ write("Ingesting Claude Code... ");
2221
2582
  const { memories, filtered, deduped } = ingestClaude();
2222
2583
  allMemories.push(...memories);
2223
- console.log(renderDiscoverySummary(memories.length, filtered, deduped));
2584
+ log(renderDiscoverySummary(memories.length, filtered, deduped));
2224
2585
  }
2225
2586
  if (shouldIngest("codex") && tools.find((t) => t.name === "Codex")?.available) {
2226
- process.stdout.write("Ingesting Codex... ");
2587
+ write("Ingesting Codex... ");
2227
2588
  const { memories, filtered, deduped } = ingestCodex();
2228
2589
  allMemories.push(...memories);
2229
- console.log(renderDiscoverySummary(memories.length, filtered, deduped));
2590
+ log(renderDiscoverySummary(memories.length, filtered, deduped));
2230
2591
  }
2231
2592
  if (shouldIngest("cursor") && tools.find((t) => t.name === "Cursor")?.available) {
2232
- process.stdout.write("Ingesting Cursor... ");
2593
+ write("Ingesting Cursor... ");
2233
2594
  const cursorTool = tools.find((t) => t.name === "Cursor");
2234
2595
  const { memories, filtered, deduped } = await ingestCursor(cursorTool.path, opts.wasmDir);
2235
2596
  allMemories.push(...memories);
2236
- console.log(renderDiscoverySummary(memories.length, filtered, deduped));
2597
+ log(renderDiscoverySummary(memories.length, filtered, deduped));
2237
2598
  }
2238
2599
  allMemories.sort((a, b3) => {
2239
2600
  const da = a.metadata?.date || "0000";
2240
2601
  const db = b3.metadata?.date || "0000";
2241
2602
  return db.localeCompare(da);
2242
2603
  });
2243
- console.log();
2604
+ log();
2244
2605
  if (allMemories.length === 0) {
2245
- console.log("Nothing new to upload.");
2606
+ log("Nothing new to upload.");
2246
2607
  } else if (opts.dryRun) {
2247
- console.log(`[DRY RUN] Would upload ${allMemories.length} memories`);
2608
+ log(`[DRY RUN] Would upload ${allMemories.length} memories`);
2248
2609
  for (const m2 of allMemories.slice(0, 5)) {
2249
- console.log(` ${m2.containerTag} | ${m2.content.length} chars | ${m2.metadata?.sessionId?.slice(0, 12) || "?"}`);
2610
+ log(` ${m2.containerTag} | ${m2.content.length} chars | ${m2.metadata?.sessionId?.slice(0, 12) || "?"}`);
2250
2611
  }
2251
2612
  if (allMemories.length > 5)
2252
- console.log(` ... and ${allMemories.length - 5} more`);
2613
+ log(` ... and ${allMemories.length - 5} more`);
2253
2614
  } else {
2254
2615
  const barWidth = 20;
2255
2616
  const { success, failed, results } = await uploadBulk(apiKey, allMemories, (uploaded, total, _failed) => {
2256
2617
  const pct = (uploaded / total * 100).toFixed(1);
2257
2618
  const filled = Math.round(uploaded / total * barWidth);
2258
2619
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
2259
- process.stdout.write(`\r Uploading [\x1B[33m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2620
+ write(`\r Uploading [\x1B[33m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2260
2621
  });
2261
- process.stdout.write(renderProgressSummary(success, failed, "uploaded"));
2622
+ write(renderProgressSummary(success, failed, "uploaded"));
2262
2623
  if (failed > 0) {
2263
- console.log(` Re-run with --force to retry failed memories.`);
2624
+ log(` Re-run with --force to retry failed memories.`);
2264
2625
  }
2265
- printFailureSummary(results, allMemories);
2626
+ if (!opts.quiet)
2627
+ printFailureSummary(results, allMemories);
2266
2628
  const successfulKeysBySource = {
2267
2629
  claude: [],
2268
2630
  codex: [],
@@ -2292,7 +2654,7 @@ Nothing new to index.`);
2292
2654
  markIngested(source, ids);
2293
2655
  }
2294
2656
  }
2295
- console.log();
2657
+ log();
2296
2658
  if (interactiveMode) {
2297
2659
  selectedTargets = await selectMcpTargets(interactiveTargets);
2298
2660
  const shouldIndexCurrentCodebase = await confirmIndexCodebase();
@@ -2308,23 +2670,23 @@ Nothing new to index.`);
2308
2670
  finishSetup();
2309
2671
  }
2310
2672
  if (!opts.dryRun && !effectiveIngestOnly) {
2311
- console.log("─── Configuring MCP ───");
2312
- console.log("");
2673
+ log("─── Configuring MCP ───");
2674
+ log("");
2313
2675
  for (const line of configureMcp(apiKey, selectedTargets)) {
2314
- console.log(` ✓ ${line}`);
2676
+ log(` ✓ ${line}`);
2315
2677
  }
2316
- console.log("");
2678
+ log("");
2317
2679
  }
2318
2680
  if (postIngestIndexPath) {
2319
2681
  const { resolve: resolve2 } = await import("node:path");
2320
2682
  const absPath = resolve2(postIngestIndexPath);
2321
- console.log("─── Indexing codebase ───");
2322
- console.log("");
2323
- process.stdout.write("Scanning files... ");
2683
+ log("─── Indexing codebase ───");
2684
+ log("");
2685
+ write("Scanning files... ");
2324
2686
  const { memories, fileCount, skipped } = indexCodebase(absPath);
2325
- console.log(`${fileCount} files found, ${skipped} skipped`);
2687
+ log(`${fileCount} files found, ${skipped} skipped`);
2326
2688
  if (memories.length === 0) {
2327
- console.log(`
2689
+ log(`
2328
2690
  Nothing new to index.`);
2329
2691
  } else {
2330
2692
  const barWidth = 20;
@@ -2332,20 +2694,36 @@ Nothing new to index.`);
2332
2694
  const pct = (uploaded / total * 100).toFixed(1);
2333
2695
  const filled = Math.round(uploaded / total * barWidth);
2334
2696
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
2335
- process.stdout.write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2697
+ write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2336
2698
  });
2337
- process.stdout.write(renderProgressSummary(success, failed, "indexed"));
2699
+ write(renderProgressSummary(success, failed, "indexed"));
2338
2700
  const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
2339
2701
  markIngested("codebase", fileHashes);
2340
- printFailureSummary(results, memories);
2702
+ if (!opts.quiet)
2703
+ printFailureSummary(results, memories);
2341
2704
  }
2342
- console.log("");
2705
+ log("");
2343
2706
  }
2344
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2345
- console.log(" Done! Every new conversation now starts with context.");
2346
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2347
- console.log(" Next time you can run `conare` after a global install.");
2348
- console.log("");
2707
+ if (!opts.dryRun && !opts.quiet) {
2708
+ log("─── Background Sync ───");
2709
+ log("");
2710
+ try {
2711
+ const syncMessages = installSync(apiKey);
2712
+ for (const msg of syncMessages)
2713
+ log(` ✓ ${msg}`);
2714
+ log("");
2715
+ log(" New chats will be auto-ingested every 10 minutes.");
2716
+ log(" Logs: ~/.conare/ingest.log | Remove: conare --uninstall-sync");
2717
+ } catch (e2) {
2718
+ log(` ⚠ Could not set up background sync: ${e2.message}`);
2719
+ log(" Run manually later: conare --install-sync");
2720
+ }
2721
+ log("");
2722
+ }
2723
+ log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2724
+ log(" ✓ Done! Every new conversation now starts with context.");
2725
+ log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
2726
+ log("");
2349
2727
  }
2350
2728
  main().catch((e2) => {
2351
2729
  console.error("Error:", e2.message || e2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conare",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "description": "Conare CLI for ingesting AI chat history and configuring memory at conare.ai",
5
5
  "type": "module",
6
6
  "bin": {