conare 0.5.19 → 0.5.21
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 +365 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,10 +48,37 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
48
48
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
49
|
|
|
50
50
|
// src/ingest/shared.ts
|
|
51
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
51
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
52
52
|
import { createHash } from "node:crypto";
|
|
53
53
|
import { join } from "node:path";
|
|
54
54
|
import { homedir } from "node:os";
|
|
55
|
+
function conareDir() {
|
|
56
|
+
const override = process.env.CONARE_HOME;
|
|
57
|
+
return override ? join(override, ".conare") : join(homedir(), ".conare");
|
|
58
|
+
}
|
|
59
|
+
function globalManifestPath() {
|
|
60
|
+
return join(conareDir(), "ingested.json");
|
|
61
|
+
}
|
|
62
|
+
function scopedManifestPath(userId) {
|
|
63
|
+
const slug = createHash("sha256").update(userId).digest("hex").slice(0, 16);
|
|
64
|
+
return join(conareDir(), `ingested.${slug}.json`);
|
|
65
|
+
}
|
|
66
|
+
function manifestPath() {
|
|
67
|
+
return activeUserId ? scopedManifestPath(activeUserId) : globalManifestPath();
|
|
68
|
+
}
|
|
69
|
+
function setManifestAccount(userId) {
|
|
70
|
+
activeUserId = userId || null;
|
|
71
|
+
if (!userId)
|
|
72
|
+
return;
|
|
73
|
+
const scoped = scopedManifestPath(userId);
|
|
74
|
+
if (!existsSync(scoped) && existsSync(globalManifestPath())) {
|
|
75
|
+
try {
|
|
76
|
+
if (!existsSync(conareDir()))
|
|
77
|
+
mkdirSync(conareDir(), { recursive: true });
|
|
78
|
+
renameSync(globalManifestPath(), scoped);
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
55
82
|
function hardCapContent(content) {
|
|
56
83
|
if (content.length <= MAX_MEMORY_CONTENT)
|
|
57
84
|
return content;
|
|
@@ -80,12 +107,22 @@ ${body}`;
|
|
|
80
107
|
return full;
|
|
81
108
|
return hardCapContent(buildContent(TRUNCATED_USER_MSG));
|
|
82
109
|
}
|
|
110
|
+
function normalizeEpochMs(value) {
|
|
111
|
+
return value < 10000000000 ? value * 1000 : value;
|
|
112
|
+
}
|
|
83
113
|
function parseTimestampMs(value) {
|
|
84
114
|
if (typeof value === "number" && Number.isFinite(value))
|
|
85
|
-
return value;
|
|
86
|
-
if (typeof value !== "string"
|
|
115
|
+
return normalizeEpochMs(value);
|
|
116
|
+
if (typeof value !== "string")
|
|
117
|
+
return null;
|
|
118
|
+
const trimmed = value.trim();
|
|
119
|
+
if (trimmed.length === 0)
|
|
87
120
|
return null;
|
|
88
|
-
|
|
121
|
+
if (/^\d+$/.test(trimmed)) {
|
|
122
|
+
const n = Number(trimmed);
|
|
123
|
+
return Number.isFinite(n) ? normalizeEpochMs(n) : null;
|
|
124
|
+
}
|
|
125
|
+
const parsed = Date.parse(trimmed);
|
|
89
126
|
return Number.isFinite(parsed) ? parsed : null;
|
|
90
127
|
}
|
|
91
128
|
function isNarration(text) {
|
|
@@ -115,10 +152,16 @@ function cleanText(raw) {
|
|
|
115
152
|
function createContentHash(content) {
|
|
116
153
|
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
117
154
|
}
|
|
155
|
+
function writeManifest(manifest) {
|
|
156
|
+
if (!existsSync(conareDir()))
|
|
157
|
+
mkdirSync(conareDir(), { recursive: true });
|
|
158
|
+
writeFileSync(manifestPath(), JSON.stringify(manifest, null, 2));
|
|
159
|
+
}
|
|
118
160
|
function getIngested() {
|
|
119
161
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
162
|
+
const path = manifestPath();
|
|
163
|
+
if (existsSync(path)) {
|
|
164
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
122
165
|
}
|
|
123
166
|
} catch {}
|
|
124
167
|
return {};
|
|
@@ -129,10 +172,22 @@ function markIngested(source, sessionIds) {
|
|
|
129
172
|
for (const id of sessionIds)
|
|
130
173
|
existing.add(id);
|
|
131
174
|
manifest[source] = [...existing];
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
175
|
+
writeManifest(manifest);
|
|
176
|
+
}
|
|
177
|
+
function mergeIngested(bySource) {
|
|
178
|
+
const manifest = getIngested();
|
|
179
|
+
let added = 0;
|
|
180
|
+
for (const [source, fingerprints] of Object.entries(bySource)) {
|
|
181
|
+
const existing = new Set(manifest[source] || []);
|
|
182
|
+
const before = existing.size;
|
|
183
|
+
for (const fp of fingerprints)
|
|
184
|
+
existing.add(fp);
|
|
185
|
+
added += existing.size - before;
|
|
186
|
+
manifest[source] = [...existing];
|
|
187
|
+
}
|
|
188
|
+
if (added > 0)
|
|
189
|
+
writeManifest(manifest);
|
|
190
|
+
return added;
|
|
136
191
|
}
|
|
137
192
|
function isIngested(source, sessionId) {
|
|
138
193
|
const manifest = getIngested();
|
|
@@ -146,16 +201,12 @@ function clearIngested(source) {
|
|
|
146
201
|
for (const key of Object.keys(manifest))
|
|
147
202
|
delete manifest[key];
|
|
148
203
|
}
|
|
149
|
-
|
|
150
|
-
if (!existsSync(dir))
|
|
151
|
-
mkdirSync(dir, { recursive: true });
|
|
152
|
-
writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
204
|
+
writeManifest(manifest);
|
|
153
205
|
}
|
|
154
|
-
var
|
|
206
|
+
var activeUserId = null, MAX_MEMORY_CONTENT = 200000, TRUNCATED_USER_MSG = 3000, TRUNCATED_MARKER = `
|
|
155
207
|
|
|
156
208
|
...[truncated to fit Conare upload limit]`, MIN_SUBSTANTIVE = 200, NARRATION_RE;
|
|
157
209
|
var init_shared = __esm(() => {
|
|
158
|
-
MANIFEST_PATH = join(homedir(), ".conare", "ingested.json");
|
|
159
210
|
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))/;
|
|
160
211
|
});
|
|
161
212
|
|
|
@@ -484,10 +535,12 @@ var exports_api = {};
|
|
|
484
535
|
__export(exports_api, {
|
|
485
536
|
validateKey: () => validateKey,
|
|
486
537
|
uploadBulk: () => uploadBulk,
|
|
538
|
+
recordSyncCheckIn: () => recordSyncCheckIn,
|
|
487
539
|
listRemoteMemories: () => listRemoteMemories,
|
|
488
540
|
getRemoteMemoryCount: () => getRemoteMemoryCount,
|
|
489
541
|
getRemoteChatMemoryCount: () => getRemoteChatMemoryCount,
|
|
490
542
|
getBillingStatus: () => getBillingStatus,
|
|
543
|
+
fetchServerDedupFingerprints: () => fetchServerDedupFingerprints,
|
|
491
544
|
deleteMemory: () => deleteMemory,
|
|
492
545
|
deleteMemories: () => deleteMemories,
|
|
493
546
|
createUploadBatches: () => createUploadBatches,
|
|
@@ -568,11 +621,23 @@ function createUploadBatches(memories, maxItems = 10, maxChars = 500000) {
|
|
|
568
621
|
async function validateKey(apiKey) {
|
|
569
622
|
try {
|
|
570
623
|
const data = await apiRequest("/api/auth/me", apiKey);
|
|
571
|
-
return { valid: true, email: data.user.email, name: data.user.name };
|
|
624
|
+
return { valid: true, id: data.user.id, email: data.user.email, name: data.user.name };
|
|
572
625
|
} catch {
|
|
573
626
|
return { valid: false };
|
|
574
627
|
}
|
|
575
628
|
}
|
|
629
|
+
async function fetchServerDedupFingerprints(apiKey) {
|
|
630
|
+
const bySource = { claude: [], codex: [], cursor: [] };
|
|
631
|
+
await Promise.all(Object.entries(CONTAINER_TO_SOURCE).map(async ([containerTag, source]) => {
|
|
632
|
+
const data = await apiRequest(`/api/memories/dedup-keys?containerTag=${encodeURIComponent(containerTag)}`, apiKey);
|
|
633
|
+
for (const k of data.keys || []) {
|
|
634
|
+
if (!k.dedupKey)
|
|
635
|
+
continue;
|
|
636
|
+
bySource[source].push(k.contentHash ? `${k.dedupKey}:${k.contentHash}` : k.dedupKey);
|
|
637
|
+
}
|
|
638
|
+
}));
|
|
639
|
+
return bySource;
|
|
640
|
+
}
|
|
576
641
|
async function getRemoteMemoryCount(apiKey) {
|
|
577
642
|
try {
|
|
578
643
|
const data = await apiRequest("/api/containers", apiKey);
|
|
@@ -601,6 +666,17 @@ async function getBillingStatus(apiKey) {
|
|
|
601
666
|
return null;
|
|
602
667
|
}
|
|
603
668
|
}
|
|
669
|
+
async function recordSyncCheckIn(apiKey, source = "cli", memoryDelta = 0) {
|
|
670
|
+
try {
|
|
671
|
+
const data = await apiRequest("/api/sync/check-in", apiKey, {
|
|
672
|
+
method: "POST",
|
|
673
|
+
body: JSON.stringify({ source, memoryDelta })
|
|
674
|
+
});
|
|
675
|
+
return typeof data.lastSyncAt === "number" ? data.lastSyncAt : null;
|
|
676
|
+
} catch {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
604
680
|
async function uploadItems(apiKey, items) {
|
|
605
681
|
let retries = 4;
|
|
606
682
|
while (retries > 0) {
|
|
@@ -618,7 +694,7 @@ async function uploadItems(apiKey, items) {
|
|
|
618
694
|
const message = error.message || "Plan limit reached";
|
|
619
695
|
return items.map(() => ({
|
|
620
696
|
success: false,
|
|
621
|
-
error: message.includes("conare.ai") ? message : `${message}. Upgrade at
|
|
697
|
+
error: message.includes("conare.ai") ? message : `${message}. Upgrade at ${UPGRADE_URL}`
|
|
622
698
|
}));
|
|
623
699
|
}
|
|
624
700
|
if (error instanceof ApiError && error.statusCode === 429) {
|
|
@@ -679,7 +755,7 @@ async function uploadBulk(apiKey, memories, onProgress) {
|
|
|
679
755
|
}
|
|
680
756
|
return { success, failed, results };
|
|
681
757
|
}
|
|
682
|
-
var API_URL2 = "https://conare.ai", ApiError, PAGE_SIZE = 200, DELETE_CONCURRENCY = 5;
|
|
758
|
+
var API_URL2 = "https://conare.ai", UPGRADE_URL = "https://conare.ai/#pricing", ApiError, PAGE_SIZE = 200, DELETE_CONCURRENCY = 5, CONTAINER_TO_SOURCE;
|
|
683
759
|
var init_api = __esm(() => {
|
|
684
760
|
ApiError = class ApiError extends Error {
|
|
685
761
|
statusCode;
|
|
@@ -691,6 +767,11 @@ var init_api = __esm(() => {
|
|
|
691
767
|
this.name = "ApiError";
|
|
692
768
|
}
|
|
693
769
|
};
|
|
770
|
+
CONTAINER_TO_SOURCE = {
|
|
771
|
+
"claude-chats": "claude",
|
|
772
|
+
"codex-chats": "codex",
|
|
773
|
+
"cursor-chats": "cursor"
|
|
774
|
+
};
|
|
694
775
|
});
|
|
695
776
|
|
|
696
777
|
// node_modules/sisteransi/src/index.js
|
|
@@ -1898,7 +1979,7 @@ async function promptAuth(options) {
|
|
|
1898
1979
|
}
|
|
1899
1980
|
async function selectChatSources(targets) {
|
|
1900
1981
|
return ensureValue(await fe({
|
|
1901
|
-
message: "Select chat sources to index
|
|
1982
|
+
message: "Select chat sources to index",
|
|
1902
1983
|
required: false,
|
|
1903
1984
|
initialValues: targets.filter((target) => target.available !== false && (target.detectedCount ?? 0) > 0).map((target) => target.id),
|
|
1904
1985
|
options: targets.map((target) => ({
|
|
@@ -1910,7 +1991,7 @@ async function selectChatSources(targets) {
|
|
|
1910
1991
|
}
|
|
1911
1992
|
async function selectMcpTargets(targets) {
|
|
1912
1993
|
return ensureValue(await fe({
|
|
1913
|
-
message: "Select where to install the MCP
|
|
1994
|
+
message: "Select where to install the MCP",
|
|
1914
1995
|
required: false,
|
|
1915
1996
|
initialValues: targets.filter((target) => target.available !== false && (target.detectedCount ?? 0) > 0).map((target) => target.id),
|
|
1916
1997
|
options: targets.map((target) => ({
|
|
@@ -3728,13 +3809,72 @@ function copyNodeModule(name) {
|
|
|
3728
3809
|
mkdirSync4(dirname3(targetDir), { recursive: true });
|
|
3729
3810
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
3730
3811
|
}
|
|
3731
|
-
function
|
|
3812
|
+
function bootoutLaunchAgent() {
|
|
3732
3813
|
try {
|
|
3733
|
-
execSync3(`launchctl bootout gui/${uid()}
|
|
3814
|
+
execSync3(`launchctl bootout gui/${uid()}/${PLIST_LABEL} 2>/dev/null`, { stdio: "ignore" });
|
|
3815
|
+
} catch {}
|
|
3816
|
+
}
|
|
3817
|
+
function removeLegacyCron() {
|
|
3818
|
+
try {
|
|
3819
|
+
const existing = execSync3("crontab -l 2>/dev/null", { encoding: "utf-8" });
|
|
3820
|
+
const lines = existing.split(`
|
|
3821
|
+
`);
|
|
3822
|
+
const filtered = lines.filter((l) => !l.endsWith("# conare-sync") && !l.includes(".conare/bin/run.sh"));
|
|
3823
|
+
if (filtered.length === lines.length)
|
|
3824
|
+
return;
|
|
3825
|
+
const next = filtered.join(`
|
|
3826
|
+
`).trim();
|
|
3827
|
+
execSync3("crontab -", { input: next ? next + `
|
|
3828
|
+
` : `
|
|
3829
|
+
`, stdio: ["pipe", "ignore", "ignore"] });
|
|
3734
3830
|
} catch {}
|
|
3735
|
-
|
|
3736
|
-
|
|
3831
|
+
}
|
|
3832
|
+
function makeLaunchdPlist(intervalSeconds) {
|
|
3833
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3834
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3835
|
+
<plist version="1.0">
|
|
3836
|
+
<dict>
|
|
3837
|
+
<key>Label</key>
|
|
3838
|
+
<string>${PLIST_LABEL}</string>
|
|
3839
|
+
<key>ProgramArguments</key>
|
|
3840
|
+
<array>
|
|
3841
|
+
<string>/bin/bash</string>
|
|
3842
|
+
<string>${join9(BIN_DIR, "run.sh")}</string>
|
|
3843
|
+
</array>
|
|
3844
|
+
<key>StartInterval</key>
|
|
3845
|
+
<integer>${intervalSeconds}</integer>
|
|
3846
|
+
<key>RunAtLoad</key>
|
|
3847
|
+
<true/>
|
|
3848
|
+
<key>ProcessType</key>
|
|
3849
|
+
<string>Background</string>
|
|
3850
|
+
<key>StandardErrorPath</key>
|
|
3851
|
+
<string>${join9(CONARE_DIR, "ingest.log")}</string>
|
|
3852
|
+
</dict>
|
|
3853
|
+
</plist>
|
|
3854
|
+
`;
|
|
3855
|
+
}
|
|
3856
|
+
function setupLaunchd(intervalMinutes) {
|
|
3857
|
+
removeLegacyCron();
|
|
3858
|
+
const dir = dirname3(PLIST_PATH);
|
|
3859
|
+
mkdirSync4(dir, { recursive: true });
|
|
3860
|
+
writeFileSync4(PLIST_PATH, makeLaunchdPlist(intervalMinutes * 60));
|
|
3861
|
+
bootoutLaunchAgent();
|
|
3862
|
+
let registered = false;
|
|
3863
|
+
try {
|
|
3864
|
+
execSync3(`launchctl bootstrap gui/${uid()} "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
3865
|
+
registered = true;
|
|
3866
|
+
} catch {
|
|
3867
|
+
try {
|
|
3868
|
+
execSync3(`launchctl load -w "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
3869
|
+
registered = true;
|
|
3870
|
+
} catch {}
|
|
3737
3871
|
}
|
|
3872
|
+
if (!registered) {
|
|
3873
|
+
throw new Error(`launchd registration failed — plist written to ${PLIST_PATH} but the agent could not be loaded. Try: launchctl bootstrap gui/${uid()} "${PLIST_PATH}"`);
|
|
3874
|
+
}
|
|
3875
|
+
try {
|
|
3876
|
+
execSync3(`launchctl kickstart gui/${uid()}/${PLIST_LABEL}`, { stdio: "ignore" });
|
|
3877
|
+
} catch {}
|
|
3738
3878
|
}
|
|
3739
3879
|
function setupLinuxSystemd(intervalMinutes) {
|
|
3740
3880
|
mkdirSync4(SYSTEMD_DIR, { recursive: true });
|
|
@@ -3924,10 +4064,8 @@ function installSync(apiKey, intervalMinutes = 10) {
|
|
|
3924
4064
|
const os = platform6();
|
|
3925
4065
|
intervalMinutes = sanitizeInterval(intervalMinutes);
|
|
3926
4066
|
if (os === "darwin") {
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
setupCron(intervalMinutes);
|
|
3930
|
-
messages.push(`Installed cron job (every ${actual} min)`);
|
|
4067
|
+
setupLaunchd(intervalMinutes);
|
|
4068
|
+
messages.push(`Installed launchd agent (every ${intervalMinutes} min)`);
|
|
3931
4069
|
} else if (os === "win32") {
|
|
3932
4070
|
setupWindows(intervalMinutes);
|
|
3933
4071
|
messages.push(`Installed Windows Task Scheduler (every ${intervalMinutes} min)`);
|
|
@@ -3946,12 +4084,126 @@ function installSync(apiKey, intervalMinutes = 10) {
|
|
|
3946
4084
|
messages.push("First sync scheduled");
|
|
3947
4085
|
return messages;
|
|
3948
4086
|
}
|
|
4087
|
+
function cronHasConareEntry() {
|
|
4088
|
+
try {
|
|
4089
|
+
const cron = execSync3("crontab -l 2>/dev/null", { encoding: "utf-8" });
|
|
4090
|
+
return /# conare-sync|\.conare\/bin\/run\.sh/.test(cron);
|
|
4091
|
+
} catch {
|
|
4092
|
+
return false;
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
function syncTimerInstalled() {
|
|
4096
|
+
const os = platform6();
|
|
4097
|
+
if (os === "darwin") {
|
|
4098
|
+
try {
|
|
4099
|
+
execSync3(`launchctl print gui/${uid()}/${PLIST_LABEL}`, { stdio: "ignore" });
|
|
4100
|
+
return true;
|
|
4101
|
+
} catch {}
|
|
4102
|
+
return cronHasConareEntry();
|
|
4103
|
+
}
|
|
4104
|
+
if (os === "win32") {
|
|
4105
|
+
try {
|
|
4106
|
+
execSync3(`schtasks /Query /TN "${TASK_NAME}"`, { stdio: "ignore" });
|
|
4107
|
+
return true;
|
|
4108
|
+
} catch {
|
|
4109
|
+
return false;
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
if (os === "linux") {
|
|
4113
|
+
try {
|
|
4114
|
+
execSync3("systemctl --user is-enabled conare-sync.timer", { stdio: "ignore" });
|
|
4115
|
+
execSync3("systemctl --user is-active conare-sync.timer", { stdio: "ignore" });
|
|
4116
|
+
return true;
|
|
4117
|
+
} catch {}
|
|
4118
|
+
return cronHasConareEntry();
|
|
4119
|
+
}
|
|
4120
|
+
return false;
|
|
4121
|
+
}
|
|
4122
|
+
function ensureSyncInstalled(apiKey, intervalMinutes = 10) {
|
|
4123
|
+
if (syncTimerInstalled())
|
|
4124
|
+
return false;
|
|
4125
|
+
installSync(apiKey, intervalMinutes);
|
|
4126
|
+
return true;
|
|
4127
|
+
}
|
|
4128
|
+
function parseLastSync(logText) {
|
|
4129
|
+
const lines = logText.trim().split(`
|
|
4130
|
+
`).filter(Boolean);
|
|
4131
|
+
let lastSyncIso = null;
|
|
4132
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
4133
|
+
const m = lines[i].match(/^(\d{4}-\d{2}-\d{2}T[\d:]+Z)\s+(START|DONE)/);
|
|
4134
|
+
if (m) {
|
|
4135
|
+
lastSyncIso = m[1];
|
|
4136
|
+
break;
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
let lastStartIdx = -1;
|
|
4140
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
4141
|
+
if (/\bSTART sync\b/.test(lines[i])) {
|
|
4142
|
+
lastStartIdx = i;
|
|
4143
|
+
break;
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
let lastSyncError = null;
|
|
4147
|
+
if (lastStartIdx >= 0) {
|
|
4148
|
+
const errMatch = lines.slice(lastStartIdx).join(`
|
|
4149
|
+
`).match(/.*(Invalid API key|ERROR:).*/);
|
|
4150
|
+
if (errMatch)
|
|
4151
|
+
lastSyncError = errMatch[0].trim();
|
|
4152
|
+
}
|
|
4153
|
+
return { lastSyncIso, lastSyncError };
|
|
4154
|
+
}
|
|
4155
|
+
function detectMechanism() {
|
|
4156
|
+
const os = platform6();
|
|
4157
|
+
if (os === "win32")
|
|
4158
|
+
return "Task Scheduler";
|
|
4159
|
+
if (os === "darwin") {
|
|
4160
|
+
try {
|
|
4161
|
+
execSync3(`launchctl print gui/${uid()}/${PLIST_LABEL}`, { stdio: "ignore" });
|
|
4162
|
+
return "launchd";
|
|
4163
|
+
} catch {}
|
|
4164
|
+
if (cronHasConareEntry())
|
|
4165
|
+
return "cron (legacy)";
|
|
4166
|
+
return "launchd";
|
|
4167
|
+
}
|
|
4168
|
+
if (os === "linux") {
|
|
4169
|
+
try {
|
|
4170
|
+
execSync3("systemctl --user is-active conare-sync.timer", { stdio: "ignore" });
|
|
4171
|
+
return "systemd";
|
|
4172
|
+
} catch {}
|
|
4173
|
+
if (cronHasConareEntry())
|
|
4174
|
+
return "cron";
|
|
4175
|
+
return hasSystemd() ? "systemd" : "cron";
|
|
4176
|
+
}
|
|
4177
|
+
return "unsupported";
|
|
4178
|
+
}
|
|
4179
|
+
function syncStatus() {
|
|
4180
|
+
const mechanism = detectMechanism();
|
|
4181
|
+
const logPath = join9(CONARE_DIR, "ingest.log");
|
|
4182
|
+
let parsed = { lastSyncIso: null, lastSyncError: null };
|
|
4183
|
+
if (existsSync9(logPath)) {
|
|
4184
|
+
try {
|
|
4185
|
+
parsed = parseLastSync(readFileSync8(logPath, "utf-8"));
|
|
4186
|
+
} catch {}
|
|
4187
|
+
}
|
|
4188
|
+
return {
|
|
4189
|
+
timerInstalled: syncTimerInstalled(),
|
|
4190
|
+
mechanism,
|
|
4191
|
+
binaryPersisted: existsSync9(join9(BIN_DIR, "conare-ingest.mjs")),
|
|
4192
|
+
configPresent: existsSync9(CONFIG_PATH),
|
|
4193
|
+
lastSyncIso: parsed.lastSyncIso,
|
|
4194
|
+
lastSyncError: parsed.lastSyncError
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
3949
4197
|
function removeSyncTimer() {
|
|
3950
4198
|
const messages = [];
|
|
3951
4199
|
const os = platform6();
|
|
3952
4200
|
if (os === "darwin") {
|
|
3953
|
-
|
|
3954
|
-
|
|
4201
|
+
bootoutLaunchAgent();
|
|
4202
|
+
if (existsSync9(PLIST_PATH)) {
|
|
4203
|
+
unlinkSync2(PLIST_PATH);
|
|
4204
|
+
messages.push("Removed launchd agent");
|
|
4205
|
+
}
|
|
4206
|
+
removeCronEntry() && messages.push("Removed legacy cron job");
|
|
3955
4207
|
} else if (os === "win32") {
|
|
3956
4208
|
try {
|
|
3957
4209
|
execSync3(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
|
|
@@ -4140,6 +4392,7 @@ Usage:
|
|
|
4140
4392
|
conare Interactive setup with browser auth
|
|
4141
4393
|
conare install Just install the MCP (all detected clients)
|
|
4142
4394
|
conare logout Clear saved API key and local hashes; keep conare installed
|
|
4395
|
+
conare doctor Check sync/auth health and repair a missing timer
|
|
4143
4396
|
conare --key <api_key> Index chat history (key optional with browser auth)
|
|
4144
4397
|
conare --key <api_key> --index [path] Index codebase
|
|
4145
4398
|
|
|
@@ -4237,8 +4490,6 @@ async function runLogout() {
|
|
|
4237
4490
|
clearSyncLocks();
|
|
4238
4491
|
const hadApiKey = clearSavedApiKey();
|
|
4239
4492
|
messages.push(hadApiKey ? "Cleared saved API key" : "No saved API key found");
|
|
4240
|
-
clearIngested();
|
|
4241
|
-
messages.push("Reset local hashes");
|
|
4242
4493
|
console.log("");
|
|
4243
4494
|
for (const msg of messages)
|
|
4244
4495
|
console.log(` ${msg}`);
|
|
@@ -4246,6 +4497,47 @@ async function runLogout() {
|
|
|
4246
4497
|
console.log(" \x1B[32m✓\x1B[0m Logged out. The conare command is still installed.");
|
|
4247
4498
|
console.log("");
|
|
4248
4499
|
}
|
|
4500
|
+
async function runDoctor() {
|
|
4501
|
+
const ok = "\x1B[32m✓\x1B[0m";
|
|
4502
|
+
const bad = "\x1B[31m✗\x1B[0m";
|
|
4503
|
+
const dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
4504
|
+
const apiKey = getSavedApiKey() || process.env.CONARE_API_KEY;
|
|
4505
|
+
const status = syncStatus();
|
|
4506
|
+
console.log("");
|
|
4507
|
+
console.log(" Conare doctor");
|
|
4508
|
+
console.log("");
|
|
4509
|
+
const auth = apiKey ? await validateKey(apiKey) : { valid: false };
|
|
4510
|
+
if (apiKey) {
|
|
4511
|
+
console.log(` ${auth.valid ? ok : bad} API key: ${auth.valid ? `valid (${auth.email})` : "INVALID — run `conare` to sign in again"}`);
|
|
4512
|
+
} else {
|
|
4513
|
+
console.log(` ${bad} API key: not configured — run \`conare\` to sign in`);
|
|
4514
|
+
}
|
|
4515
|
+
console.log(` ${status.timerInstalled ? ok : bad} Background sync: ${status.timerInstalled ? `installed (${status.mechanism})` : "NOT installed"}`);
|
|
4516
|
+
console.log(` ${status.binaryPersisted ? ok : bad} CLI binary: ${status.binaryPersisted ? "persisted (~/.conare/bin/)" : "missing"}`);
|
|
4517
|
+
if (status.lastSyncIso) {
|
|
4518
|
+
console.log(` ${dim(`Last sync run: ${status.lastSyncIso}`)}`);
|
|
4519
|
+
}
|
|
4520
|
+
if (status.lastSyncError) {
|
|
4521
|
+
console.log(` ${bad} Last logged error: ${status.lastSyncError}`);
|
|
4522
|
+
}
|
|
4523
|
+
if (!status.timerInstalled) {
|
|
4524
|
+
console.log("");
|
|
4525
|
+
if (!auth.valid) {
|
|
4526
|
+
console.log(` ${bad} Not repairing background sync: ${apiKey ? "API key is invalid" : "no API key"}. Run \`conare\` to sign in, which reinstalls sync.`);
|
|
4527
|
+
} else {
|
|
4528
|
+
console.log(" Repairing: installing background sync...");
|
|
4529
|
+
try {
|
|
4530
|
+
ensureSyncInstalled(apiKey);
|
|
4531
|
+
console.log(` ${ok} Background sync installed.`);
|
|
4532
|
+
} catch (e2) {
|
|
4533
|
+
console.log(` ${bad} Could not install background sync: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
console.log("");
|
|
4538
|
+
console.log(` ${dim(`Dashboard: ${CONARE_URL2}/dashboard`)}`);
|
|
4539
|
+
console.log("");
|
|
4540
|
+
}
|
|
4249
4541
|
async function main() {
|
|
4250
4542
|
if (process.argv[2] === "install") {
|
|
4251
4543
|
return runInstall();
|
|
@@ -4253,6 +4545,9 @@ async function main() {
|
|
|
4253
4545
|
if (process.argv[2] === "logout") {
|
|
4254
4546
|
return runLogout();
|
|
4255
4547
|
}
|
|
4548
|
+
if (process.argv[2] === "doctor") {
|
|
4549
|
+
return runDoctor();
|
|
4550
|
+
}
|
|
4256
4551
|
const opts = parseArgs();
|
|
4257
4552
|
let configFileKey;
|
|
4258
4553
|
if (opts.configFile) {
|
|
@@ -4372,6 +4667,8 @@ async function main() {
|
|
|
4372
4667
|
process.exit(1);
|
|
4373
4668
|
}
|
|
4374
4669
|
saveApiKey(apiKey);
|
|
4670
|
+
if (auth.id)
|
|
4671
|
+
setManifestAccount(auth.id);
|
|
4375
4672
|
if (!opts.force && !opts.dryRun) {
|
|
4376
4673
|
const remoteMemoryCount = await getRemoteMemoryCount(apiKey);
|
|
4377
4674
|
const localManifest = getIngested();
|
|
@@ -4380,6 +4677,17 @@ async function main() {
|
|
|
4380
4677
|
clearIngested();
|
|
4381
4678
|
log("Remote account is empty; cleared stale local index history.");
|
|
4382
4679
|
log("");
|
|
4680
|
+
} else if (remoteMemoryCount && remoteMemoryCount > 0) {
|
|
4681
|
+
const localChatCount = (localManifest.claude?.length || 0) + (localManifest.codex?.length || 0) + (localManifest.cursor?.length || 0);
|
|
4682
|
+
const remoteChatCount = localChatCount === 0 ? await getRemoteChatMemoryCount(apiKey) : 0;
|
|
4683
|
+
if (remoteChatCount && remoteChatCount > 0) {
|
|
4684
|
+
try {
|
|
4685
|
+
const serverFingerprints = await fetchServerDedupFingerprints(apiKey);
|
|
4686
|
+
const added = mergeIngested(serverFingerprints);
|
|
4687
|
+
if (added > 0)
|
|
4688
|
+
log(`Synced ${added.toLocaleString()} already-indexed chats from your account.`);
|
|
4689
|
+
} catch {}
|
|
4690
|
+
}
|
|
4383
4691
|
}
|
|
4384
4692
|
}
|
|
4385
4693
|
if (!opts.wasmDir && existsSync10(join10(process.cwd(), "node_modules", "sql.js"))) {
|
|
@@ -4452,6 +4760,7 @@ async function main() {
|
|
|
4452
4760
|
log(" Continuing with upload (stale files may remain)...");
|
|
4453
4761
|
}
|
|
4454
4762
|
}
|
|
4763
|
+
let indexedCount = 0;
|
|
4455
4764
|
if (memories.length === 0) {
|
|
4456
4765
|
log(`
|
|
4457
4766
|
Nothing new to index.`);
|
|
@@ -4473,11 +4782,15 @@ Nothing new to index.`);
|
|
|
4473
4782
|
write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
|
|
4474
4783
|
});
|
|
4475
4784
|
write(renderProgressSummary(success, failed, "indexed"));
|
|
4785
|
+
indexedCount = results.filter((result) => result.success && !result.deduped).length;
|
|
4476
4786
|
const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
|
|
4477
4787
|
markIngested("codebase", fileHashes);
|
|
4478
4788
|
if (!opts.quiet)
|
|
4479
4789
|
printFailureSummary(results, memories);
|
|
4480
4790
|
}
|
|
4791
|
+
if (!opts.dryRun) {
|
|
4792
|
+
await recordSyncCheckIn(apiKey, "codebase", indexedCount);
|
|
4793
|
+
}
|
|
4481
4794
|
log("");
|
|
4482
4795
|
if (!opts.dryRun && !effectiveIngestOnly) {
|
|
4483
4796
|
const mcpSpinner = Y2();
|
|
@@ -4575,11 +4888,17 @@ Nothing new to index.`);
|
|
|
4575
4888
|
} else {
|
|
4576
4889
|
log(`\x1B[33m⚠\x1B[0m Free plan: chat upload limit already reached (${knownRemoteChatCount}/${chatLimit}); ${skipped} new local sessions skipped`);
|
|
4577
4890
|
}
|
|
4578
|
-
log(` Upgrade to Pro for unlimited uploads → \x1B[
|
|
4891
|
+
log(` Upgrade to Pro for unlimited uploads → \x1B[4m${CONARE_URL2}/#pricing\x1B[0m`);
|
|
4579
4892
|
log();
|
|
4580
4893
|
}
|
|
4581
4894
|
}
|
|
4582
4895
|
log();
|
|
4896
|
+
if (apiKey && !opts.dryRun && !interactiveMode) {
|
|
4897
|
+
try {
|
|
4898
|
+
ensureSyncInstalled(apiKey, opts.syncInterval);
|
|
4899
|
+
} catch {}
|
|
4900
|
+
}
|
|
4901
|
+
let skipUpload = false;
|
|
4583
4902
|
if (allMemories.length > 2000 && !opts.dryRun && !opts.quiet) {
|
|
4584
4903
|
if (hasTty) {
|
|
4585
4904
|
const { confirm: confirm2 } = await Promise.resolve().then(() => (init_dist2(), exports_dist));
|
|
@@ -4587,17 +4906,20 @@ Nothing new to index.`);
|
|
|
4587
4906
|
message: `That's ${allMemories.length.toLocaleString()} memories — this may take a while and use significant resources. Continue?`
|
|
4588
4907
|
});
|
|
4589
4908
|
if (!proceed || typeof proceed === "symbol") {
|
|
4590
|
-
log("
|
|
4591
|
-
|
|
4909
|
+
log("Skipping upload for now. You can re-run `conare` anytime to upload.");
|
|
4910
|
+
log();
|
|
4911
|
+
skipUpload = true;
|
|
4592
4912
|
}
|
|
4593
4913
|
} else {
|
|
4594
4914
|
log(`Warning: ${allMemories.length.toLocaleString()} memories detected. Skipping large upload in non-interactive mode.`);
|
|
4595
4915
|
log("Re-run interactively or with --force to proceed.");
|
|
4596
|
-
|
|
4916
|
+
skipUpload = true;
|
|
4597
4917
|
}
|
|
4598
4918
|
}
|
|
4599
|
-
|
|
4600
|
-
|
|
4919
|
+
let uploadedCount = 0;
|
|
4920
|
+
if (allMemories.length === 0 || skipUpload) {
|
|
4921
|
+
if (allMemories.length === 0)
|
|
4922
|
+
log("Nothing new to upload.");
|
|
4601
4923
|
} else if (opts.dryRun) {
|
|
4602
4924
|
log(`[DRY RUN] Would upload ${allMemories.length} memories`);
|
|
4603
4925
|
for (const m2 of allMemories.slice(0, 5)) {
|
|
@@ -4614,6 +4936,7 @@ Nothing new to index.`);
|
|
|
4614
4936
|
write(`\r Uploading [\x1B[33m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
|
|
4615
4937
|
});
|
|
4616
4938
|
write(renderProgressSummary(success, failed, "uploaded"));
|
|
4939
|
+
uploadedCount = results.filter((result) => result.success && !result.deduped).length;
|
|
4617
4940
|
if (failed > 0) {
|
|
4618
4941
|
log(` Re-run with --force to retry failed memories.`);
|
|
4619
4942
|
}
|
|
@@ -4648,6 +4971,9 @@ Nothing new to index.`);
|
|
|
4648
4971
|
markIngested(source, ids);
|
|
4649
4972
|
}
|
|
4650
4973
|
}
|
|
4974
|
+
if (!opts.dryRun) {
|
|
4975
|
+
await recordSyncCheckIn(apiKey, "cli", uploadedCount);
|
|
4976
|
+
}
|
|
4651
4977
|
log();
|
|
4652
4978
|
if (interactiveMode) {
|
|
4653
4979
|
selectedTargets = await selectMcpTargets(interactiveTargets);
|