conare 0.4.4 → 0.4.6
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 +142 -144
- 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))/;
|
|
@@ -525,13 +546,13 @@ async function getRemoteMemoryCount(apiKey) {
|
|
|
525
546
|
return null;
|
|
526
547
|
}
|
|
527
548
|
}
|
|
528
|
-
async function uploadItems(apiKey, items
|
|
549
|
+
async function uploadItems(apiKey, items) {
|
|
529
550
|
let retries = 4;
|
|
530
551
|
while (retries > 0) {
|
|
531
552
|
try {
|
|
532
553
|
const data = await apiRequest("/api/memories/bulk", apiKey, {
|
|
533
554
|
method: "POST",
|
|
534
|
-
body: JSON.stringify(
|
|
555
|
+
body: JSON.stringify({ items })
|
|
535
556
|
});
|
|
536
557
|
if (!Array.isArray(data.results) || data.results.length !== items.length) {
|
|
537
558
|
throw new Error("Unexpected bulk upload response");
|
|
@@ -562,7 +583,7 @@ async function uploadBulk(apiKey, memories, onProgress) {
|
|
|
562
583
|
const results = [];
|
|
563
584
|
const batches = createUploadBatches(memories);
|
|
564
585
|
for (const batch of batches) {
|
|
565
|
-
let batchResults = await uploadItems(apiKey, batch.items
|
|
586
|
+
let batchResults = await uploadItems(apiKey, batch.items);
|
|
566
587
|
if (batch.items.length > 1 && batchResults.some((result) => !result.success)) {
|
|
567
588
|
const retriedResults = [];
|
|
568
589
|
for (let i = 0;i < batch.items.length; i++) {
|
|
@@ -571,7 +592,7 @@ async function uploadBulk(apiKey, memories, onProgress) {
|
|
|
571
592
|
retriedResults.push(result);
|
|
572
593
|
continue;
|
|
573
594
|
}
|
|
574
|
-
const [singleResult] = await uploadItems(apiKey, [batch.items[i]]
|
|
595
|
+
const [singleResult] = await uploadItems(apiKey, [batch.items[i]]);
|
|
575
596
|
retriedResults.push(singleResult);
|
|
576
597
|
}
|
|
577
598
|
batchResults = retriedResults;
|
|
@@ -593,7 +614,7 @@ async function uploadBulk(apiKey, memories, onProgress) {
|
|
|
593
614
|
}
|
|
594
615
|
return { success, failed, results };
|
|
595
616
|
}
|
|
596
|
-
var API_URL2 = "https://
|
|
617
|
+
var API_URL2 = "https://conare.ai", ApiError, PAGE_SIZE = 200, DELETE_CONCURRENCY = 5;
|
|
597
618
|
var init_api = __esm(() => {
|
|
598
619
|
ApiError = class ApiError extends Error {
|
|
599
620
|
statusCode;
|
|
@@ -1743,7 +1764,7 @@ async function promptApiKey(options) {
|
|
|
1743
1764
|
validate(value) {
|
|
1744
1765
|
const resolved = value.trim() || options.savedApiKey || "";
|
|
1745
1766
|
if (!resolved)
|
|
1746
|
-
return "Enter an API key from https://
|
|
1767
|
+
return "Enter an API key from https://conare.ai";
|
|
1747
1768
|
if (!resolved.startsWith("cmem_"))
|
|
1748
1769
|
return "API keys start with cmem_";
|
|
1749
1770
|
return;
|
|
@@ -1775,7 +1796,7 @@ async function promptAuth(options) {
|
|
|
1775
1796
|
validate(value) {
|
|
1776
1797
|
const resolved = value.trim();
|
|
1777
1798
|
if (!resolved)
|
|
1778
|
-
return "Enter an API key from https://
|
|
1799
|
+
return "Enter an API key from https://conare.ai";
|
|
1779
1800
|
if (!resolved.startsWith("cmem_"))
|
|
1780
1801
|
return "API keys start with cmem_";
|
|
1781
1802
|
return;
|
|
@@ -2007,7 +2028,7 @@ async function detect() {
|
|
|
2007
2028
|
// src/auth.ts
|
|
2008
2029
|
import { execSync } from "node:child_process";
|
|
2009
2030
|
import { platform as platform2 } from "node:os";
|
|
2010
|
-
var API_URL = "https://
|
|
2031
|
+
var API_URL = "https://conare.ai";
|
|
2011
2032
|
async function browserAuth() {
|
|
2012
2033
|
const stateBytes = new Uint8Array(16);
|
|
2013
2034
|
crypto.getRandomValues(stateBytes);
|
|
@@ -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
|
|
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
|
|
2389
|
-
|
|
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
|
|
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}`;
|
|
@@ -2606,7 +2583,7 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as rea
|
|
|
2606
2583
|
import { dirname, join as join7 } from "node:path";
|
|
2607
2584
|
import { homedir as homedir5, platform as platform5 } from "node:os";
|
|
2608
2585
|
import { spawnSync } from "node:child_process";
|
|
2609
|
-
var CONARE_URL = "https://
|
|
2586
|
+
var CONARE_URL = "https://conare.ai";
|
|
2610
2587
|
var SERVER_NAME = "conare";
|
|
2611
2588
|
var MCP_TARGETS = [
|
|
2612
2589
|
{ id: "claude", label: "Claude Code", defaultSelected: true },
|
|
@@ -2919,7 +2896,7 @@ Expected outcome: future \`recall\` calls surface the information automatically
|
|
|
2919
2896
|
|
|
2920
2897
|
- Use \`after\`/\`before\` (Unix ms) for time-scoped searches: "what did we work on this week?"
|
|
2921
2898
|
- Use \`containerTag\` to filter by source: \`claude-chats\`, \`codex-chats\`, \`cursor-chats\`, \`codebase\`, \`preferences\`
|
|
2922
|
-
- Use \`project\` for cross-project filtering (partial names work: "conare" matches "fun/conare
|
|
2899
|
+
- Use \`project\` for cross-project filtering (partial names work: "conare" matches "fun/conare")
|
|
2923
2900
|
- Keep \`limit\` low (3-5) for focused results, higher (10-15) for broad exploration
|
|
2924
2901
|
- When user asks to "remember" something, save it with \`save\` — it goes to the \`preferences\` container and gets surfaced by \`recall\` in future sessions
|
|
2925
2902
|
|
|
@@ -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
|
|
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/${
|
|
3198
|
+
execSync3(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
|
|
3251
3199
|
} catch {}
|
|
3252
|
-
|
|
3253
|
-
|
|
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
|
-
|
|
3270
|
-
|
|
3271
|
-
const
|
|
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
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
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
|
-
|
|
3417
|
-
|
|
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
|
-
|
|
3429
|
-
|
|
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
|
-
|
|
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
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
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/");
|
|
@@ -3493,14 +3491,14 @@ function printMissingKeyError() {
|
|
|
3493
3491
|
console.error("Error: no API key configured.");
|
|
3494
3492
|
console.error("");
|
|
3495
3493
|
console.error("Run one of these:");
|
|
3496
|
-
console.error("
|
|
3497
|
-
console.error("
|
|
3494
|
+
console.error(" npx conare@latest");
|
|
3495
|
+
console.error(" bunx conare@latest");
|
|
3498
3496
|
console.error("");
|
|
3499
3497
|
console.error("For a built-in command, install globally:");
|
|
3500
3498
|
console.error(" bun add -g conare");
|
|
3501
3499
|
console.error(" npm i -g conare");
|
|
3502
3500
|
console.error("");
|
|
3503
|
-
console.error("Get your API key at https://
|
|
3501
|
+
console.error("Get your API key at https://conare.ai");
|
|
3504
3502
|
}
|
|
3505
3503
|
function printFailureSummary(results, memories) {
|
|
3506
3504
|
const failures = results.map((result, index) => ({ ...result, memory: memories[index] })).filter((result) => !result.success);
|
|
@@ -3619,7 +3617,7 @@ Options:
|
|
|
3619
3617
|
--source <name> Only ingest from: claude, codex, cursor
|
|
3620
3618
|
--wasm-dir <path> Path to sql.js module (for Cursor ingestion)
|
|
3621
3619
|
|
|
3622
|
-
Get your API key at https://
|
|
3620
|
+
Get your API key at https://conare.ai
|
|
3623
3621
|
`);
|
|
3624
3622
|
process.exit(0);
|
|
3625
3623
|
}
|
|
@@ -3671,7 +3669,7 @@ async function runInstall() {
|
|
|
3671
3669
|
}
|
|
3672
3670
|
const auth = await validateKey(apiKey);
|
|
3673
3671
|
if (!auth.valid) {
|
|
3674
|
-
console.error("Invalid API key. Check your key at https://
|
|
3672
|
+
console.error("Invalid API key. Check your key at https://conare.ai");
|
|
3675
3673
|
process.exit(1);
|
|
3676
3674
|
}
|
|
3677
3675
|
saveApiKey(apiKey);
|
|
@@ -3788,7 +3786,7 @@ async function main() {
|
|
|
3788
3786
|
if (opts.installSync) {
|
|
3789
3787
|
const auth2 = await validateKey(apiKey);
|
|
3790
3788
|
if (!auth2.valid) {
|
|
3791
|
-
console.error("Invalid API key. Check your key at https://
|
|
3789
|
+
console.error("Invalid API key. Check your key at https://conare.ai");
|
|
3792
3790
|
process.exit(1);
|
|
3793
3791
|
}
|
|
3794
3792
|
const syncSpinner = Y2();
|
|
@@ -3801,7 +3799,7 @@ async function main() {
|
|
|
3801
3799
|
log("");
|
|
3802
3800
|
const auth = await validateKey(apiKey);
|
|
3803
3801
|
if (!auth.valid) {
|
|
3804
|
-
console.error("Invalid API key. Check your key at https://
|
|
3802
|
+
console.error("Invalid API key. Check your key at https://conare.ai");
|
|
3805
3803
|
process.exit(1);
|
|
3806
3804
|
}
|
|
3807
3805
|
saveApiKey(apiKey);
|
|
@@ -3838,7 +3836,7 @@ async function main() {
|
|
|
3838
3836
|
if (opts.force) {
|
|
3839
3837
|
clearIngested("codebase");
|
|
3840
3838
|
try {
|
|
3841
|
-
await fetch("https://
|
|
3839
|
+
await fetch("https://conare.ai/api/containers/codebase", {
|
|
3842
3840
|
method: "DELETE",
|
|
3843
3841
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
3844
3842
|
});
|