conare 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +448 -85
- 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
|
|
152
|
-
import { join as
|
|
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
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
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
|
-
|
|
2467
|
+
log(auth.email ? `OK (${auth.email})` : "OK");
|
|
2108
2468
|
saveApiKey(apiKey);
|
|
2109
|
-
|
|
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
|
-
|
|
2117
|
-
|
|
2476
|
+
log("Remote account is empty; cleared stale local ingestion history.");
|
|
2477
|
+
log("");
|
|
2118
2478
|
}
|
|
2119
2479
|
}
|
|
2120
|
-
if (!opts.wasmDir &&
|
|
2121
|
-
opts.wasmDir =
|
|
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
|
-
|
|
2126
|
-
|
|
2485
|
+
log("─── Configuring MCP ───");
|
|
2486
|
+
log("");
|
|
2127
2487
|
for (const line of configureMcp(apiKey, selectedTargets)) {
|
|
2128
|
-
|
|
2488
|
+
log(` ✓ ${line}`);
|
|
2129
2489
|
}
|
|
2130
|
-
|
|
2490
|
+
log("");
|
|
2131
2491
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
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
|
-
|
|
2501
|
+
log(`Indexing codebase: ${absPath}`);
|
|
2142
2502
|
if (opts.force) {
|
|
2143
2503
|
clearIngested("codebase");
|
|
2144
|
-
|
|
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
|
-
|
|
2510
|
+
log("done");
|
|
2151
2511
|
} catch {
|
|
2152
|
-
|
|
2512
|
+
log("skipped (no existing index)");
|
|
2153
2513
|
}
|
|
2154
2514
|
}
|
|
2155
|
-
|
|
2515
|
+
write("Scanning files... ");
|
|
2156
2516
|
const { memories, fileCount, skipped } = indexCodebase(absPath);
|
|
2157
|
-
|
|
2517
|
+
log(`${fileCount} files found, ${skipped} skipped`);
|
|
2158
2518
|
if (memories.length === 0) {
|
|
2159
|
-
|
|
2519
|
+
log(`
|
|
2160
2520
|
Nothing new to index.`);
|
|
2161
2521
|
} else if (opts.dryRun) {
|
|
2162
|
-
|
|
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
|
-
|
|
2526
|
+
log(` ${meta.language.padEnd(12)} ${meta.filePath}`);
|
|
2167
2527
|
}
|
|
2168
2528
|
if (memories.length > 10)
|
|
2169
|
-
|
|
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
|
-
|
|
2536
|
+
write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
|
|
2177
2537
|
});
|
|
2178
|
-
|
|
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
|
-
|
|
2541
|
+
if (!opts.quiet)
|
|
2542
|
+
printFailureSummary(results, memories);
|
|
2182
2543
|
}
|
|
2183
|
-
|
|
2544
|
+
log();
|
|
2184
2545
|
if (!opts.dryRun && !effectiveIngestOnly) {
|
|
2185
|
-
|
|
2186
|
-
|
|
2546
|
+
log("─── Configuring MCP ───");
|
|
2547
|
+
log("");
|
|
2187
2548
|
for (const line of configureMcp(apiKey, selectedTargets)) {
|
|
2188
|
-
|
|
2549
|
+
log(` ✓ ${line}`);
|
|
2189
2550
|
}
|
|
2190
|
-
|
|
2551
|
+
log("");
|
|
2191
2552
|
}
|
|
2192
|
-
|
|
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
|
-
|
|
2559
|
+
log(`Cleared ingestion history for ${opts.source}`);
|
|
2199
2560
|
} else {
|
|
2200
2561
|
clearIngested();
|
|
2201
|
-
|
|
2562
|
+
log("Cleared all ingestion history");
|
|
2202
2563
|
}
|
|
2203
|
-
|
|
2564
|
+
log();
|
|
2204
2565
|
}
|
|
2205
2566
|
const tools = detect();
|
|
2206
2567
|
if (!interactiveMode) {
|
|
2207
|
-
|
|
2568
|
+
log("Detected AI tools:");
|
|
2208
2569
|
for (const t of tools) {
|
|
2209
2570
|
if (t.available) {
|
|
2210
|
-
|
|
2571
|
+
log(` + ${t.name}${t.sessionCount || t.sessionCount === 0 ? ` — ${t.sessionCount} sessions` : ""}`);
|
|
2211
2572
|
} else {
|
|
2212
|
-
|
|
2573
|
+
log(` - ${t.name} (not found)`);
|
|
2213
2574
|
}
|
|
2214
2575
|
}
|
|
2215
|
-
|
|
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
|
-
|
|
2581
|
+
write("Ingesting Claude Code... ");
|
|
2221
2582
|
const { memories, filtered, deduped } = ingestClaude();
|
|
2222
2583
|
allMemories.push(...memories);
|
|
2223
|
-
|
|
2584
|
+
log(renderDiscoverySummary(memories.length, filtered, deduped));
|
|
2224
2585
|
}
|
|
2225
2586
|
if (shouldIngest("codex") && tools.find((t) => t.name === "Codex")?.available) {
|
|
2226
|
-
|
|
2587
|
+
write("Ingesting Codex... ");
|
|
2227
2588
|
const { memories, filtered, deduped } = ingestCodex();
|
|
2228
2589
|
allMemories.push(...memories);
|
|
2229
|
-
|
|
2590
|
+
log(renderDiscoverySummary(memories.length, filtered, deduped));
|
|
2230
2591
|
}
|
|
2231
2592
|
if (shouldIngest("cursor") && tools.find((t) => t.name === "Cursor")?.available) {
|
|
2232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2604
|
+
log();
|
|
2244
2605
|
if (allMemories.length === 0) {
|
|
2245
|
-
|
|
2606
|
+
log("Nothing new to upload.");
|
|
2246
2607
|
} else if (opts.dryRun) {
|
|
2247
|
-
|
|
2608
|
+
log(`[DRY RUN] Would upload ${allMemories.length} memories`);
|
|
2248
2609
|
for (const m2 of allMemories.slice(0, 5)) {
|
|
2249
|
-
|
|
2610
|
+
log(` ${m2.containerTag} | ${m2.content.length} chars | ${m2.metadata?.sessionId?.slice(0, 12) || "?"}`);
|
|
2250
2611
|
}
|
|
2251
2612
|
if (allMemories.length > 5)
|
|
2252
|
-
|
|
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
|
-
|
|
2620
|
+
write(`\r Uploading [\x1B[33m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
|
|
2260
2621
|
});
|
|
2261
|
-
|
|
2622
|
+
write(renderProgressSummary(success, failed, "uploaded"));
|
|
2262
2623
|
if (failed > 0) {
|
|
2263
|
-
|
|
2624
|
+
log(` Re-run with --force to retry failed memories.`);
|
|
2264
2625
|
}
|
|
2265
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2312
|
-
|
|
2673
|
+
log("─── Configuring MCP ───");
|
|
2674
|
+
log("");
|
|
2313
2675
|
for (const line of configureMcp(apiKey, selectedTargets)) {
|
|
2314
|
-
|
|
2676
|
+
log(` ✓ ${line}`);
|
|
2315
2677
|
}
|
|
2316
|
-
|
|
2678
|
+
log("");
|
|
2317
2679
|
}
|
|
2318
2680
|
if (postIngestIndexPath) {
|
|
2319
2681
|
const { resolve: resolve2 } = await import("node:path");
|
|
2320
2682
|
const absPath = resolve2(postIngestIndexPath);
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2683
|
+
log("─── Indexing codebase ───");
|
|
2684
|
+
log("");
|
|
2685
|
+
write("Scanning files... ");
|
|
2324
2686
|
const { memories, fileCount, skipped } = indexCodebase(absPath);
|
|
2325
|
-
|
|
2687
|
+
log(`${fileCount} files found, ${skipped} skipped`);
|
|
2326
2688
|
if (memories.length === 0) {
|
|
2327
|
-
|
|
2689
|
+
log(`
|
|
2328
2690
|
Nothing new to index.`);
|
|
2329
2691
|
} else {
|
|
2330
2692
|
const barWidth = 20;
|
|
@@ -2332,20 +2694,21 @@ 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
|
-
|
|
2697
|
+
write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
|
|
2336
2698
|
});
|
|
2337
|
-
|
|
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
|
-
|
|
2702
|
+
if (!opts.quiet)
|
|
2703
|
+
printFailureSummary(results, memories);
|
|
2341
2704
|
}
|
|
2342
|
-
|
|
2705
|
+
log("");
|
|
2343
2706
|
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2707
|
+
log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
2708
|
+
log(" ✓ Done! Every new conversation now starts with context.");
|
|
2709
|
+
log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
2710
|
+
log(" Next time you can run `conare` after a global install.");
|
|
2711
|
+
log("");
|
|
2349
2712
|
}
|
|
2350
2713
|
main().catch((e2) => {
|
|
2351
2714
|
console.error("Error:", e2.message || e2);
|