conare 0.5.5 → 0.5.7
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 +699 -475
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,35 +5,53 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __returnValue = (v) => v;
|
|
35
|
+
function __exportSetter(name, newValue) {
|
|
36
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
+
}
|
|
20
38
|
var __export = (target, all) => {
|
|
21
39
|
for (var name in all)
|
|
22
40
|
__defProp(target, name, {
|
|
23
41
|
get: all[name],
|
|
24
42
|
enumerable: true,
|
|
25
43
|
configurable: true,
|
|
26
|
-
set: (
|
|
44
|
+
set: __exportSetter.bind(all, name)
|
|
27
45
|
});
|
|
28
46
|
};
|
|
29
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
30
48
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
31
49
|
|
|
32
50
|
// src/ingest/shared.ts
|
|
33
|
-
import { existsSync
|
|
51
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
34
52
|
import { createHash } from "node:crypto";
|
|
35
|
-
import { join
|
|
36
|
-
import { homedir
|
|
53
|
+
import { join } from "node:path";
|
|
54
|
+
import { homedir } from "node:os";
|
|
37
55
|
function fitContent(header, rounds) {
|
|
38
56
|
const buildContent = (maxUser) => {
|
|
39
57
|
const body = rounds.map((r) => {
|
|
@@ -57,6 +75,14 @@ ${body}`;
|
|
|
57
75
|
return full;
|
|
58
76
|
return buildContent(TRUNCATED_USER_MSG);
|
|
59
77
|
}
|
|
78
|
+
function parseTimestampMs(value) {
|
|
79
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
80
|
+
return value;
|
|
81
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
82
|
+
return null;
|
|
83
|
+
const parsed = Date.parse(value);
|
|
84
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
85
|
+
}
|
|
60
86
|
function isNarration(text) {
|
|
61
87
|
const stripped = text.trim();
|
|
62
88
|
if (stripped.length < MIN_SUBSTANTIVE)
|
|
@@ -86,8 +112,8 @@ function createContentHash(content) {
|
|
|
86
112
|
}
|
|
87
113
|
function getIngested() {
|
|
88
114
|
try {
|
|
89
|
-
if (
|
|
90
|
-
return JSON.parse(
|
|
115
|
+
if (existsSync(MANIFEST_PATH)) {
|
|
116
|
+
return JSON.parse(readFileSync(MANIFEST_PATH, "utf-8"));
|
|
91
117
|
}
|
|
92
118
|
} catch {}
|
|
93
119
|
return {};
|
|
@@ -98,8 +124,8 @@ function markIngested(source, sessionIds) {
|
|
|
98
124
|
for (const id of sessionIds)
|
|
99
125
|
existing.add(id);
|
|
100
126
|
manifest[source] = [...existing];
|
|
101
|
-
const dir =
|
|
102
|
-
if (!
|
|
127
|
+
const dir = join(homedir(), ".conare");
|
|
128
|
+
if (!existsSync(dir))
|
|
103
129
|
mkdirSync(dir, { recursive: true });
|
|
104
130
|
writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
105
131
|
}
|
|
@@ -115,14 +141,14 @@ function clearIngested(source) {
|
|
|
115
141
|
for (const key of Object.keys(manifest))
|
|
116
142
|
delete manifest[key];
|
|
117
143
|
}
|
|
118
|
-
const dir =
|
|
119
|
-
if (!
|
|
144
|
+
const dir = join(homedir(), ".conare");
|
|
145
|
+
if (!existsSync(dir))
|
|
120
146
|
mkdirSync(dir, { recursive: true });
|
|
121
147
|
writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
122
148
|
}
|
|
123
149
|
var MANIFEST_PATH, MAX_MEMORY_CONTENT = 200000, TRUNCATED_USER_MSG = 3000, MIN_SUBSTANTIVE = 200, NARRATION_RE;
|
|
124
150
|
var init_shared = __esm(() => {
|
|
125
|
-
MANIFEST_PATH =
|
|
151
|
+
MANIFEST_PATH = join(homedir(), ".conare", "ingested.json");
|
|
126
152
|
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))/;
|
|
127
153
|
});
|
|
128
154
|
|
|
@@ -135,16 +161,16 @@ __export(exports_codebase, {
|
|
|
135
161
|
detectProjectName: () => detectProjectName
|
|
136
162
|
});
|
|
137
163
|
import { createHash as createHash2 } from "node:crypto";
|
|
138
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
164
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync2, existsSync as existsSync6 } from "node:fs";
|
|
139
165
|
import { join as join6, relative, extname, resolve, basename as basename3 } from "node:path";
|
|
140
166
|
import { execSync as execSync2 } from "node:child_process";
|
|
141
167
|
function parseGitignore(rootPath) {
|
|
142
168
|
const patterns = new Set;
|
|
143
169
|
const gitignorePath = join6(rootPath, ".gitignore");
|
|
144
|
-
if (!
|
|
170
|
+
if (!existsSync6(gitignorePath))
|
|
145
171
|
return patterns;
|
|
146
172
|
try {
|
|
147
|
-
const content =
|
|
173
|
+
const content = readFileSync5(gitignorePath, "utf-8");
|
|
148
174
|
for (const line of content.split(`
|
|
149
175
|
`)) {
|
|
150
176
|
const trimmed = line.trim();
|
|
@@ -180,15 +206,15 @@ ${content}
|
|
|
180
206
|
function detectProjectName(rootPath) {
|
|
181
207
|
const readers = [
|
|
182
208
|
() => {
|
|
183
|
-
const pkg = JSON.parse(
|
|
209
|
+
const pkg = JSON.parse(readFileSync5(join6(rootPath, "package.json"), "utf-8"));
|
|
184
210
|
return typeof pkg.name === "string" ? pkg.name : null;
|
|
185
211
|
},
|
|
186
212
|
() => {
|
|
187
|
-
const content =
|
|
213
|
+
const content = readFileSync5(join6(rootPath, "Cargo.toml"), "utf-8");
|
|
188
214
|
return content.match(/^name\s*=\s*"([^"]+)"/m)?.[1] ?? null;
|
|
189
215
|
},
|
|
190
216
|
() => {
|
|
191
|
-
const content =
|
|
217
|
+
const content = readFileSync5(join6(rootPath, "pyproject.toml"), "utf-8");
|
|
192
218
|
return content.match(/^name\s*=\s*"([^"]+)"/m)?.[1] ?? null;
|
|
193
219
|
}
|
|
194
220
|
];
|
|
@@ -285,7 +311,7 @@ function indexCodebase(rootPath, options = {}) {
|
|
|
285
311
|
}
|
|
286
312
|
let raw;
|
|
287
313
|
try {
|
|
288
|
-
raw =
|
|
314
|
+
raw = readFileSync5(fullPath, "utf-8");
|
|
289
315
|
} catch {
|
|
290
316
|
skipped++;
|
|
291
317
|
continue;
|
|
@@ -453,6 +479,7 @@ __export(exports_api, {
|
|
|
453
479
|
uploadBulk: () => uploadBulk,
|
|
454
480
|
listRemoteMemories: () => listRemoteMemories,
|
|
455
481
|
getRemoteMemoryCount: () => getRemoteMemoryCount,
|
|
482
|
+
getRemoteChatMemoryCount: () => getRemoteChatMemoryCount,
|
|
456
483
|
getBillingStatus: () => getBillingStatus,
|
|
457
484
|
deleteMemory: () => deleteMemory,
|
|
458
485
|
deleteMemories: () => deleteMemories,
|
|
@@ -549,6 +576,17 @@ async function getRemoteMemoryCount(apiKey) {
|
|
|
549
576
|
return null;
|
|
550
577
|
}
|
|
551
578
|
}
|
|
579
|
+
async function getRemoteChatMemoryCount(apiKey) {
|
|
580
|
+
try {
|
|
581
|
+
const data = await apiRequest("/api/containers", apiKey);
|
|
582
|
+
if (!Array.isArray(data.containers))
|
|
583
|
+
return 0;
|
|
584
|
+
const chatContainers = new Set(["claude-chats", "codex-chats", "cursor-chats"]);
|
|
585
|
+
return data.containers.filter((c) => c.tag && chatContainers.has(c.tag)).reduce((sum, c) => sum + (c.count || 0), 0);
|
|
586
|
+
} catch {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
552
590
|
async function getBillingStatus(apiKey) {
|
|
553
591
|
try {
|
|
554
592
|
return await apiRequest("/api/billing/status", apiKey);
|
|
@@ -569,13 +607,14 @@ async function uploadItems(apiKey, items) {
|
|
|
569
607
|
}
|
|
570
608
|
return data.results;
|
|
571
609
|
} catch (error) {
|
|
610
|
+
if (error instanceof ApiError && (error.statusCode === 402 || error.message.includes("quota_exceeded"))) {
|
|
611
|
+
const message = error.message || "Plan limit reached";
|
|
612
|
+
return items.map(() => ({
|
|
613
|
+
success: false,
|
|
614
|
+
error: message.includes("conare.ai") ? message : `${message}. Upgrade at https://conare.ai/pricing`
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
572
617
|
if (error instanceof ApiError && error.statusCode === 429) {
|
|
573
|
-
if (error.message.includes("quota_exceeded")) {
|
|
574
|
-
return items.map(() => ({
|
|
575
|
-
success: false,
|
|
576
|
-
error: "Memory limit reached. Upgrade at https://conare.ai/pricing"
|
|
577
|
-
}));
|
|
578
|
-
}
|
|
579
618
|
retries--;
|
|
580
619
|
await new Promise((r) => setTimeout(r, 5000));
|
|
581
620
|
continue;
|
|
@@ -592,6 +631,9 @@ async function uploadItems(apiKey, items) {
|
|
|
592
631
|
}
|
|
593
632
|
return items.map(() => ({ success: false, error: "Upload failed" }));
|
|
594
633
|
}
|
|
634
|
+
function isPlanLimitError(error) {
|
|
635
|
+
return Boolean(error && /(quota|limit reached|upgrade your plan|pricing)/i.test(error));
|
|
636
|
+
}
|
|
595
637
|
async function uploadBulk(apiKey, memories, onProgress) {
|
|
596
638
|
let success = 0;
|
|
597
639
|
let failed = 0;
|
|
@@ -600,11 +642,11 @@ async function uploadBulk(apiKey, memories, onProgress) {
|
|
|
600
642
|
const batches = createUploadBatches(memories);
|
|
601
643
|
for (const batch of batches) {
|
|
602
644
|
let batchResults = await uploadItems(apiKey, batch.items);
|
|
603
|
-
if (batch.items.length > 1 && batchResults.some((result) => !result.success)) {
|
|
645
|
+
if (batch.items.length > 1 && batchResults.some((result) => !result.success && !isPlanLimitError(result.error))) {
|
|
604
646
|
const retriedResults = [];
|
|
605
647
|
for (let i = 0;i < batch.items.length; i++) {
|
|
606
648
|
const result = batchResults[i];
|
|
607
|
-
if (result.success) {
|
|
649
|
+
if (result.success || isPlanLimitError(result.error)) {
|
|
608
650
|
retriedResults.push(result);
|
|
609
651
|
continue;
|
|
610
652
|
}
|
|
@@ -1748,10 +1790,10 @@ __export(exports_interactive, {
|
|
|
1748
1790
|
finishSetup: () => finishSetup,
|
|
1749
1791
|
confirmBackgroundSync: () => confirmBackgroundSync
|
|
1750
1792
|
});
|
|
1751
|
-
function formatDetectedCount(count) {
|
|
1793
|
+
function formatDetectedCount(count, approximate = false) {
|
|
1752
1794
|
if (count === undefined)
|
|
1753
1795
|
return "available";
|
|
1754
|
-
return `${count.toLocaleString()} chats`;
|
|
1796
|
+
return `${approximate ? "~" : ""}${count.toLocaleString()} chats`;
|
|
1755
1797
|
}
|
|
1756
1798
|
function ensureValue(value) {
|
|
1757
1799
|
if (pD(value)) {
|
|
@@ -1767,7 +1809,7 @@ function finishSetup() {
|
|
|
1767
1809
|
Se("Starting setup...");
|
|
1768
1810
|
}
|
|
1769
1811
|
function showDetectedApps(targets) {
|
|
1770
|
-
Me(targets.map((target) => `• ${target.label}: ${target.available === false ? "not detected" : formatDetectedCount(target.detectedCount)}`).join(`
|
|
1812
|
+
Me(targets.map((target) => `• ${target.label}: ${target.available === false ? "not detected" : formatDetectedCount(target.detectedCount, target.detectedCountApproximate)}`).join(`
|
|
1771
1813
|
`), "Detected apps");
|
|
1772
1814
|
}
|
|
1773
1815
|
async function promptApiKey(options) {
|
|
@@ -1832,7 +1874,7 @@ async function selectChatSources(targets) {
|
|
|
1832
1874
|
options: targets.map((target) => ({
|
|
1833
1875
|
value: target.id,
|
|
1834
1876
|
label: target.label,
|
|
1835
|
-
hint: target.available === false ? "not detected" : formatDetectedCount(target.detectedCount)
|
|
1877
|
+
hint: target.available === false ? "not detected" : formatDetectedCount(target.detectedCount, target.detectedCountApproximate)
|
|
1836
1878
|
}))
|
|
1837
1879
|
}));
|
|
1838
1880
|
}
|
|
@@ -1864,271 +1906,23 @@ var init_interactive = __esm(() => {
|
|
|
1864
1906
|
});
|
|
1865
1907
|
|
|
1866
1908
|
// src/index.ts
|
|
1867
|
-
import { existsSync as
|
|
1909
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
1868
1910
|
import { join as join10 } from "node:path";
|
|
1869
1911
|
|
|
1870
1912
|
// src/detect.ts
|
|
1871
|
-
import { existsSync, readdirSync
|
|
1872
|
-
import { join } from "node:path";
|
|
1873
|
-
import { homedir, platform } from "node:os";
|
|
1874
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
1875
|
-
function countJsonlFiles(dir) {
|
|
1876
|
-
let count = 0;
|
|
1877
|
-
try {
|
|
1878
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1879
|
-
if (entry.isDirectory()) {
|
|
1880
|
-
count += countJsonlFiles(join(dir, entry.name));
|
|
1881
|
-
} else if (entry.name.endsWith(".jsonl")) {
|
|
1882
|
-
count++;
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
} catch {}
|
|
1886
|
-
return count;
|
|
1887
|
-
}
|
|
1888
|
-
async function countCursorSessions(dbPath) {
|
|
1889
|
-
try {
|
|
1890
|
-
const require2 = createRequire2(import.meta.url);
|
|
1891
|
-
const initSqlJs = require2("sql.js");
|
|
1892
|
-
const SQL = await initSqlJs();
|
|
1893
|
-
const buffer = readFileSync(dbPath);
|
|
1894
|
-
const db = new SQL.Database(buffer);
|
|
1895
|
-
try {
|
|
1896
|
-
const results = db.exec("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'");
|
|
1897
|
-
if (results.length === 0)
|
|
1898
|
-
return 0;
|
|
1899
|
-
let count = 0;
|
|
1900
|
-
for (const [, value] of results[0].values) {
|
|
1901
|
-
try {
|
|
1902
|
-
const parsed = JSON.parse(value);
|
|
1903
|
-
const headers = parsed.fullConversationHeadersOnly;
|
|
1904
|
-
if (!Array.isArray(headers) || headers.length < 2)
|
|
1905
|
-
continue;
|
|
1906
|
-
const hasUser = headers.some((h) => h.type === 1);
|
|
1907
|
-
const hasAssistant = headers.some((h) => h.type === 2);
|
|
1908
|
-
if (hasUser && hasAssistant)
|
|
1909
|
-
count++;
|
|
1910
|
-
} catch {
|
|
1911
|
-
continue;
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
return count;
|
|
1915
|
-
} finally {
|
|
1916
|
-
db.close();
|
|
1917
|
-
}
|
|
1918
|
-
} catch {
|
|
1919
|
-
return 0;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
async function detect() {
|
|
1923
|
-
const home = homedir();
|
|
1924
|
-
const os = platform();
|
|
1925
|
-
const tools = [];
|
|
1926
|
-
const claudeDir = join(home, ".claude", "projects");
|
|
1927
|
-
if (existsSync(claudeDir)) {
|
|
1928
|
-
const sessionCount = countJsonlFiles(claudeDir);
|
|
1929
|
-
tools.push({ name: "Claude Code", id: "claude", available: sessionCount > 0, path: claudeDir, sessionCount });
|
|
1930
|
-
} else {
|
|
1931
|
-
tools.push({ name: "Claude Code", id: "claude", available: false, path: claudeDir, sessionCount: 0 });
|
|
1932
|
-
}
|
|
1933
|
-
const codexConfig = join(home, ".codex", "config.toml");
|
|
1934
|
-
const codexHistory = join(home, ".codex", "history.jsonl");
|
|
1935
|
-
const codexSessions = join(home, ".codex", "sessions");
|
|
1936
|
-
if (existsSync(codexConfig) || existsSync(codexHistory) || existsSync(codexSessions)) {
|
|
1937
|
-
let sessionCount = 0;
|
|
1938
|
-
if (existsSync(codexHistory)) {
|
|
1939
|
-
try {
|
|
1940
|
-
const lines = readFileSync(codexHistory, "utf-8").split(`
|
|
1941
|
-
`).filter(Boolean);
|
|
1942
|
-
const sessions = new Set(lines.map((l) => {
|
|
1943
|
-
try {
|
|
1944
|
-
return JSON.parse(l).session_id;
|
|
1945
|
-
} catch {
|
|
1946
|
-
return null;
|
|
1947
|
-
}
|
|
1948
|
-
}));
|
|
1949
|
-
sessions.delete(null);
|
|
1950
|
-
sessionCount = sessions.size;
|
|
1951
|
-
} catch {}
|
|
1952
|
-
}
|
|
1953
|
-
tools.push({ name: "Codex", id: "codex", available: true, path: codexConfig, sessionCount });
|
|
1954
|
-
} else {
|
|
1955
|
-
tools.push({ name: "Codex", id: "codex", available: false, path: codexConfig, sessionCount: 0 });
|
|
1956
|
-
}
|
|
1957
|
-
let cursorDbPath;
|
|
1958
|
-
if (os === "darwin") {
|
|
1959
|
-
cursorDbPath = join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
1960
|
-
} else if (os === "win32") {
|
|
1961
|
-
cursorDbPath = join(process.env.APPDATA || join(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
|
|
1962
|
-
} else {
|
|
1963
|
-
cursorDbPath = join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
1964
|
-
}
|
|
1965
|
-
tools.push({
|
|
1966
|
-
name: "Cursor",
|
|
1967
|
-
id: "cursor",
|
|
1968
|
-
available: existsSync(cursorDbPath),
|
|
1969
|
-
path: cursorDbPath,
|
|
1970
|
-
sessionCount: existsSync(cursorDbPath) ? await countCursorSessions(cursorDbPath) : 0
|
|
1971
|
-
});
|
|
1972
|
-
const windsurfDir = join(home, ".codeium", "windsurf");
|
|
1973
|
-
tools.push({
|
|
1974
|
-
name: "Windsurf",
|
|
1975
|
-
id: "windsurf",
|
|
1976
|
-
available: existsSync(windsurfDir),
|
|
1977
|
-
path: join(windsurfDir, "mcp_config.json"),
|
|
1978
|
-
sessionCount: 0
|
|
1979
|
-
});
|
|
1980
|
-
let vscodePath;
|
|
1981
|
-
if (os === "darwin") {
|
|
1982
|
-
vscodePath = join(home, "Library", "Application Support", "Code");
|
|
1983
|
-
} else if (os === "win32") {
|
|
1984
|
-
vscodePath = join(process.env.APPDATA || join(home, "AppData", "Roaming"), "Code");
|
|
1985
|
-
} else {
|
|
1986
|
-
vscodePath = join(home, ".config", "Code");
|
|
1987
|
-
}
|
|
1988
|
-
tools.push({
|
|
1989
|
-
name: "VS Code Copilot",
|
|
1990
|
-
id: "vscode",
|
|
1991
|
-
available: existsSync(vscodePath),
|
|
1992
|
-
path: join(vscodePath, "User", "mcp.json"),
|
|
1993
|
-
sessionCount: 0
|
|
1994
|
-
});
|
|
1995
|
-
let clinePath;
|
|
1996
|
-
if (os === "darwin") {
|
|
1997
|
-
clinePath = join(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev");
|
|
1998
|
-
} else if (os === "win32") {
|
|
1999
|
-
clinePath = join(process.env.APPDATA || join(home, "AppData", "Roaming"), "Code", "User", "globalStorage", "saoudrizwan.claude-dev");
|
|
2000
|
-
} else {
|
|
2001
|
-
clinePath = join(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev");
|
|
2002
|
-
}
|
|
2003
|
-
tools.push({
|
|
2004
|
-
name: "Cline",
|
|
2005
|
-
id: "cline",
|
|
2006
|
-
available: existsSync(clinePath),
|
|
2007
|
-
path: join(clinePath, "settings", "cline_mcp_settings.json"),
|
|
2008
|
-
sessionCount: 0
|
|
2009
|
-
});
|
|
2010
|
-
const zedPath = os === "darwin" ? join(home, ".zed") : join(home, ".config", "zed");
|
|
2011
|
-
tools.push({
|
|
2012
|
-
name: "Zed",
|
|
2013
|
-
id: "zed",
|
|
2014
|
-
available: existsSync(zedPath),
|
|
2015
|
-
path: join(zedPath, "settings.json"),
|
|
2016
|
-
sessionCount: 0
|
|
2017
|
-
});
|
|
2018
|
-
const openclawDir = join(home, ".openclaw");
|
|
2019
|
-
tools.push({
|
|
2020
|
-
name: "OpenClaw",
|
|
2021
|
-
id: "openclaw",
|
|
2022
|
-
available: existsSync(openclawDir),
|
|
2023
|
-
path: join(openclawDir, "openclaw.json"),
|
|
2024
|
-
sessionCount: 0
|
|
2025
|
-
});
|
|
2026
|
-
const antigravityDir = join(home, ".gemini", "antigravity");
|
|
2027
|
-
const antigravityConvDir = join(antigravityDir, "conversations");
|
|
2028
|
-
let antigravityCount = 0;
|
|
2029
|
-
if (existsSync(antigravityConvDir)) {
|
|
2030
|
-
try {
|
|
2031
|
-
antigravityCount = readdirSync(antigravityConvDir).filter((f) => f.endsWith(".pb")).length;
|
|
2032
|
-
} catch {}
|
|
2033
|
-
}
|
|
2034
|
-
tools.push({
|
|
2035
|
-
name: "Antigravity",
|
|
2036
|
-
id: "antigravity",
|
|
2037
|
-
available: existsSync(antigravityDir),
|
|
2038
|
-
path: join(antigravityDir, "mcp_config.json"),
|
|
2039
|
-
sessionCount: antigravityCount
|
|
2040
|
-
});
|
|
2041
|
-
return tools;
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
// src/auth.ts
|
|
2045
|
-
import { execSync } from "node:child_process";
|
|
2046
|
-
import { platform as platform2 } from "node:os";
|
|
2047
|
-
var API_URL = "https://conare.ai";
|
|
2048
|
-
async function browserAuth() {
|
|
2049
|
-
const stateBytes = new Uint8Array(16);
|
|
2050
|
-
crypto.getRandomValues(stateBytes);
|
|
2051
|
-
const state = Array.from(stateBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2052
|
-
const sessionRes = await fetch(`${API_URL}/api/auth/cli-session`, {
|
|
2053
|
-
method: "POST",
|
|
2054
|
-
headers: { "Content-Type": "application/json" },
|
|
2055
|
-
body: JSON.stringify({ state })
|
|
2056
|
-
});
|
|
2057
|
-
if (!sessionRes.ok) {
|
|
2058
|
-
throw new Error(`Failed to create auth session: HTTP ${sessionRes.status}`);
|
|
2059
|
-
}
|
|
2060
|
-
const { code, expiresAt } = await sessionRes.json();
|
|
2061
|
-
const authUrl = `${API_URL}/cli-auth?code=${code}&state=${state}`;
|
|
2062
|
-
const opened = openBrowser(authUrl);
|
|
2063
|
-
if (!opened) {
|
|
2064
|
-
console.log("");
|
|
2065
|
-
console.log(" Open this URL in your browser to sign in:");
|
|
2066
|
-
console.log("");
|
|
2067
|
-
console.log(` ${authUrl}`);
|
|
2068
|
-
console.log("");
|
|
2069
|
-
}
|
|
2070
|
-
const timeout = Math.max(expiresAt - Date.now(), 0);
|
|
2071
|
-
const deadline = Date.now() + timeout;
|
|
2072
|
-
while (Date.now() < deadline) {
|
|
2073
|
-
await sleep(2000);
|
|
2074
|
-
const exchangeRes = await fetch(`${API_URL}/api/auth/cli-exchange`, {
|
|
2075
|
-
method: "POST",
|
|
2076
|
-
headers: { "Content-Type": "application/json" },
|
|
2077
|
-
body: JSON.stringify({ code, state })
|
|
2078
|
-
});
|
|
2079
|
-
if (exchangeRes.status === 202) {
|
|
2080
|
-
continue;
|
|
2081
|
-
}
|
|
2082
|
-
if (exchangeRes.ok) {
|
|
2083
|
-
const data = await exchangeRes.json();
|
|
2084
|
-
return data.apiKey;
|
|
2085
|
-
}
|
|
2086
|
-
if (exchangeRes.status === 410) {
|
|
2087
|
-
throw new Error("Authentication code was already used. Please try again.");
|
|
2088
|
-
}
|
|
2089
|
-
if (exchangeRes.status === 404) {
|
|
2090
|
-
throw new Error("Authentication session expired. Please try again.");
|
|
2091
|
-
}
|
|
2092
|
-
throw new Error(`Authentication failed: HTTP ${exchangeRes.status}`);
|
|
2093
|
-
}
|
|
2094
|
-
throw new Error("Authentication timed out. Please try again.");
|
|
2095
|
-
}
|
|
2096
|
-
function openBrowser(url) {
|
|
2097
|
-
try {
|
|
2098
|
-
const os = platform2();
|
|
2099
|
-
if (os === "darwin") {
|
|
2100
|
-
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
2101
|
-
} else if (os === "win32") {
|
|
2102
|
-
execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
2103
|
-
} else {
|
|
2104
|
-
try {
|
|
2105
|
-
execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
2106
|
-
} catch {
|
|
2107
|
-
try {
|
|
2108
|
-
execSync(`wslview "${url}"`, { stdio: "ignore" });
|
|
2109
|
-
} catch {
|
|
2110
|
-
return false;
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
return true;
|
|
2115
|
-
} catch {
|
|
2116
|
-
return false;
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
function sleep(ms) {
|
|
2120
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2121
|
-
}
|
|
1913
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3 } from "node:fs";
|
|
1914
|
+
import { join as join5 } from "node:path";
|
|
1915
|
+
import { homedir as homedir5, platform as platform3 } from "node:os";
|
|
2122
1916
|
|
|
2123
1917
|
// src/ingest/claude.ts
|
|
2124
1918
|
init_shared();
|
|
2125
|
-
import { readdirSync
|
|
2126
|
-
import { join as
|
|
2127
|
-
import { homedir as
|
|
1919
|
+
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
1920
|
+
import { join as join2, basename } from "node:path";
|
|
1921
|
+
import { homedir as homedir2, platform } from "node:os";
|
|
2128
1922
|
var MIN_TURN_LEN = 50;
|
|
2129
1923
|
function resolveProjectName(dirName) {
|
|
2130
1924
|
const segments = dirName.replace(/^-/, "").split("-");
|
|
2131
|
-
const isWindows =
|
|
1925
|
+
const isWindows = platform() === "win32";
|
|
2132
1926
|
let resolved;
|
|
2133
1927
|
let startIdx;
|
|
2134
1928
|
if (isWindows && segments.length > 0 && /^[A-Za-z]$/.test(segments[0])) {
|
|
@@ -2143,8 +1937,8 @@ function resolveProjectName(dirName) {
|
|
|
2143
1937
|
let found = false;
|
|
2144
1938
|
for (let end = segments.length;end > i; end--) {
|
|
2145
1939
|
const candidate = segments.slice(i, end).join("-");
|
|
2146
|
-
const candidatePath =
|
|
2147
|
-
if (
|
|
1940
|
+
const candidatePath = join2(resolved, candidate);
|
|
1941
|
+
if (existsSync2(candidatePath)) {
|
|
2148
1942
|
resolved = candidatePath;
|
|
2149
1943
|
i = end;
|
|
2150
1944
|
found = true;
|
|
@@ -2152,11 +1946,11 @@ function resolveProjectName(dirName) {
|
|
|
2152
1946
|
}
|
|
2153
1947
|
}
|
|
2154
1948
|
if (!found) {
|
|
2155
|
-
resolved =
|
|
1949
|
+
resolved = join2(resolved, segments[i]);
|
|
2156
1950
|
i++;
|
|
2157
1951
|
}
|
|
2158
1952
|
}
|
|
2159
|
-
const home =
|
|
1953
|
+
const home = homedir2();
|
|
2160
1954
|
const sep = isWindows ? "\\" : "/";
|
|
2161
1955
|
if (resolved.startsWith(home + sep)) {
|
|
2162
1956
|
return resolved.slice(home.length + 1).replace(/\\/g, "/");
|
|
@@ -2174,6 +1968,9 @@ function extractText(content) {
|
|
|
2174
1968
|
function parseSession(lines) {
|
|
2175
1969
|
const rounds = [];
|
|
2176
1970
|
let date = null;
|
|
1971
|
+
let startedAt = null;
|
|
1972
|
+
let updatedAt = null;
|
|
1973
|
+
let sourceTimestamp = null;
|
|
2177
1974
|
let currentUser = null;
|
|
2178
1975
|
let currentAssistant = [];
|
|
2179
1976
|
for (const line of lines) {
|
|
@@ -2185,8 +1982,15 @@ function parseSession(lines) {
|
|
|
2185
1982
|
} catch {
|
|
2186
1983
|
continue;
|
|
2187
1984
|
}
|
|
2188
|
-
if (
|
|
1985
|
+
if (obj.timestamp) {
|
|
1986
|
+
if (!startedAt)
|
|
1987
|
+
startedAt = obj.timestamp;
|
|
1988
|
+
updatedAt = obj.timestamp;
|
|
1989
|
+
const parsed = parseTimestampMs(obj.timestamp);
|
|
1990
|
+
if (parsed !== null)
|
|
1991
|
+
sourceTimestamp = parsed;
|
|
2189
1992
|
date = obj.timestamp.slice(0, 10);
|
|
1993
|
+
}
|
|
2190
1994
|
if (obj.type === "user") {
|
|
2191
1995
|
const text = cleanText(extractText(obj.message?.content));
|
|
2192
1996
|
if (text.length >= MIN_TURN_LEN) {
|
|
@@ -2212,7 +2016,7 @@ function parseSession(lines) {
|
|
|
2212
2016
|
|
|
2213
2017
|
`)
|
|
2214
2018
|
})).filter((t) => t.assistant.length >= MIN_TURN_LEN);
|
|
2215
|
-
return { turns, date };
|
|
2019
|
+
return { turns, date, sourceTimestamp, startedAt, updatedAt };
|
|
2216
2020
|
}
|
|
2217
2021
|
function getParentUuid(lines) {
|
|
2218
2022
|
for (const line of lines) {
|
|
@@ -2230,30 +2034,65 @@ function getParentUuid(lines) {
|
|
|
2230
2034
|
}
|
|
2231
2035
|
return;
|
|
2232
2036
|
}
|
|
2037
|
+
function countImportableClaudeSessions() {
|
|
2038
|
+
const projectsDir = join2(homedir2(), ".claude", "projects");
|
|
2039
|
+
let count = 0;
|
|
2040
|
+
let projectDirs;
|
|
2041
|
+
try {
|
|
2042
|
+
projectDirs = readdirSync(projectsDir);
|
|
2043
|
+
} catch {
|
|
2044
|
+
return 0;
|
|
2045
|
+
}
|
|
2046
|
+
for (const projDir of projectDirs) {
|
|
2047
|
+
const projPath = join2(projectsDir, projDir);
|
|
2048
|
+
let files;
|
|
2049
|
+
try {
|
|
2050
|
+
files = readdirSync(projPath).filter((f) => f.endsWith(".jsonl"));
|
|
2051
|
+
} catch {
|
|
2052
|
+
continue;
|
|
2053
|
+
}
|
|
2054
|
+
for (const file of files) {
|
|
2055
|
+
try {
|
|
2056
|
+
const raw = readFileSync2(join2(projPath, file), "utf-8");
|
|
2057
|
+
const lines = raw.split(`
|
|
2058
|
+
`);
|
|
2059
|
+
const parentUuid = getParentUuid(lines);
|
|
2060
|
+
if (parentUuid != null)
|
|
2061
|
+
continue;
|
|
2062
|
+
const { turns } = parseSession(lines);
|
|
2063
|
+
if (turns.length > 0)
|
|
2064
|
+
count++;
|
|
2065
|
+
} catch {
|
|
2066
|
+
continue;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
return count;
|
|
2071
|
+
}
|
|
2233
2072
|
function ingestClaude() {
|
|
2234
|
-
const projectsDir =
|
|
2073
|
+
const projectsDir = join2(homedir2(), ".claude", "projects");
|
|
2235
2074
|
const memories = [];
|
|
2236
2075
|
const sessionIds = [];
|
|
2237
2076
|
let filtered = 0;
|
|
2238
2077
|
let deduped = 0;
|
|
2239
2078
|
let projectDirs;
|
|
2240
2079
|
try {
|
|
2241
|
-
projectDirs =
|
|
2080
|
+
projectDirs = readdirSync(projectsDir);
|
|
2242
2081
|
} catch {
|
|
2243
2082
|
return { memories, sessionIds, skipped: 0, filtered, deduped };
|
|
2244
2083
|
}
|
|
2245
2084
|
for (const projDir of projectDirs) {
|
|
2246
|
-
const projPath =
|
|
2085
|
+
const projPath = join2(projectsDir, projDir);
|
|
2247
2086
|
const project = resolveProjectName(projDir);
|
|
2248
2087
|
let files;
|
|
2249
2088
|
try {
|
|
2250
|
-
files =
|
|
2089
|
+
files = readdirSync(projPath).filter((f) => f.endsWith(".jsonl"));
|
|
2251
2090
|
} catch {
|
|
2252
2091
|
continue;
|
|
2253
2092
|
}
|
|
2254
2093
|
for (const file of files) {
|
|
2255
2094
|
const sessionId = basename(file, ".jsonl");
|
|
2256
|
-
const raw =
|
|
2095
|
+
const raw = readFileSync2(join2(projPath, file), "utf-8");
|
|
2257
2096
|
const lines = raw.split(`
|
|
2258
2097
|
`);
|
|
2259
2098
|
const parentUuid = getParentUuid(lines);
|
|
@@ -2261,7 +2100,7 @@ function ingestClaude() {
|
|
|
2261
2100
|
filtered++;
|
|
2262
2101
|
continue;
|
|
2263
2102
|
}
|
|
2264
|
-
const { turns, date } = parseSession(lines);
|
|
2103
|
+
const { turns, date, sourceTimestamp, startedAt, updatedAt } = parseSession(lines);
|
|
2265
2104
|
if (turns.length === 0) {
|
|
2266
2105
|
filtered++;
|
|
2267
2106
|
continue;
|
|
@@ -2284,8 +2123,12 @@ function ingestClaude() {
|
|
|
2284
2123
|
source: "claude-code",
|
|
2285
2124
|
sessionId,
|
|
2286
2125
|
project,
|
|
2287
|
-
date: date || "unknown"
|
|
2288
|
-
|
|
2126
|
+
date: date || "unknown",
|
|
2127
|
+
...sourceTimestamp ? { sourceTimestamp } : {},
|
|
2128
|
+
...startedAt ? { sessionStartedAt: startedAt } : {},
|
|
2129
|
+
...updatedAt ? { sessionUpdatedAt: updatedAt } : {}
|
|
2130
|
+
},
|
|
2131
|
+
...sourceTimestamp ? { created_at: sourceTimestamp, updated_at: sourceTimestamp } : {}
|
|
2289
2132
|
});
|
|
2290
2133
|
sessionIds.push(sessionId);
|
|
2291
2134
|
}
|
|
@@ -2295,9 +2138,9 @@ function ingestClaude() {
|
|
|
2295
2138
|
|
|
2296
2139
|
// src/ingest/codex.ts
|
|
2297
2140
|
init_shared();
|
|
2298
|
-
import { existsSync as
|
|
2299
|
-
import { join as
|
|
2300
|
-
import { homedir as
|
|
2141
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
2142
|
+
import { join as join3, basename as basename2 } from "node:path";
|
|
2143
|
+
import { homedir as homedir3 } from "node:os";
|
|
2301
2144
|
function isCodexBoilerplate(text) {
|
|
2302
2145
|
return text.startsWith("# AGENTS.md instructions for") || text.startsWith("<INSTRUCTIONS>") || text.startsWith("<user_instructions>") || text.startsWith("<user_action>");
|
|
2303
2146
|
}
|
|
@@ -2306,7 +2149,7 @@ function extractCwd(text) {
|
|
|
2306
2149
|
return match ? match[1] : null;
|
|
2307
2150
|
}
|
|
2308
2151
|
function projectFromCwd(cwd) {
|
|
2309
|
-
const home =
|
|
2152
|
+
const home = homedir3();
|
|
2310
2153
|
const normalized = cwd.replace(/\\/g, "/");
|
|
2311
2154
|
const normalizedHome = home.replace(/\\/g, "/");
|
|
2312
2155
|
if (normalized.startsWith(normalizedHome + "/")) {
|
|
@@ -2319,8 +2162,8 @@ function ingestCodex() {
|
|
|
2319
2162
|
const sessionIds = [];
|
|
2320
2163
|
let filtered = 0;
|
|
2321
2164
|
let deduped = 0;
|
|
2322
|
-
const sessionsDir =
|
|
2323
|
-
if (
|
|
2165
|
+
const sessionsDir = join3(homedir3(), ".codex", "sessions");
|
|
2166
|
+
if (existsSync3(sessionsDir)) {
|
|
2324
2167
|
try {
|
|
2325
2168
|
const stats = { filtered: 0, deduped: 0 };
|
|
2326
2169
|
walkCodexSessions(sessionsDir, memories, sessionIds, stats);
|
|
@@ -2330,77 +2173,119 @@ function ingestCodex() {
|
|
|
2330
2173
|
}
|
|
2331
2174
|
return { memories, sessionIds, skipped: filtered + deduped, filtered, deduped };
|
|
2332
2175
|
}
|
|
2176
|
+
function parseCodexSession(lines) {
|
|
2177
|
+
let date = null;
|
|
2178
|
+
let startedAt = null;
|
|
2179
|
+
let updatedAt = null;
|
|
2180
|
+
let sourceTimestamp = null;
|
|
2181
|
+
let project = null;
|
|
2182
|
+
const rounds = [];
|
|
2183
|
+
let currentUser = null;
|
|
2184
|
+
let currentAssistant = [];
|
|
2185
|
+
for (const line of lines) {
|
|
2186
|
+
try {
|
|
2187
|
+
const obj = JSON.parse(line);
|
|
2188
|
+
if (typeof obj.timestamp === "string") {
|
|
2189
|
+
if (!startedAt)
|
|
2190
|
+
startedAt = obj.timestamp;
|
|
2191
|
+
updatedAt = obj.timestamp;
|
|
2192
|
+
const parsed = parseTimestampMs(obj.timestamp);
|
|
2193
|
+
if (parsed !== null)
|
|
2194
|
+
sourceTimestamp = parsed;
|
|
2195
|
+
date = obj.timestamp.slice(0, 10);
|
|
2196
|
+
}
|
|
2197
|
+
if (obj.type === "session_meta" && obj.payload?.cwd) {
|
|
2198
|
+
project = projectFromCwd(obj.payload.cwd);
|
|
2199
|
+
}
|
|
2200
|
+
let role;
|
|
2201
|
+
let msgContent;
|
|
2202
|
+
if (obj.type === "response_item" && obj.payload?.type === "message") {
|
|
2203
|
+
role = obj.payload.role;
|
|
2204
|
+
msgContent = Array.isArray(obj.payload.content) ? obj.payload.content : undefined;
|
|
2205
|
+
} else if (obj.type === "message" && obj.role) {
|
|
2206
|
+
role = obj.role;
|
|
2207
|
+
msgContent = Array.isArray(obj.content) ? obj.content : undefined;
|
|
2208
|
+
}
|
|
2209
|
+
if (!role || !msgContent)
|
|
2210
|
+
continue;
|
|
2211
|
+
if (role === "user") {
|
|
2212
|
+
for (const block of msgContent) {
|
|
2213
|
+
if (block.type === "input_text" && block.text) {
|
|
2214
|
+
if (!project) {
|
|
2215
|
+
const cwd = extractCwd(block.text);
|
|
2216
|
+
if (cwd)
|
|
2217
|
+
project = projectFromCwd(cwd);
|
|
2218
|
+
}
|
|
2219
|
+
const text = cleanText(block.text);
|
|
2220
|
+
if (isCodexBoilerplate(text))
|
|
2221
|
+
continue;
|
|
2222
|
+
if (text.length >= 50) {
|
|
2223
|
+
if (currentUser !== null && currentAssistant.length > 0) {
|
|
2224
|
+
rounds.push({ user: currentUser, assistantParts: currentAssistant });
|
|
2225
|
+
}
|
|
2226
|
+
currentUser = text;
|
|
2227
|
+
currentAssistant = [];
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
} else if (role === "assistant") {
|
|
2232
|
+
for (const block of msgContent) {
|
|
2233
|
+
if (block.type === "output_text" && block.text) {
|
|
2234
|
+
const text = cleanText(block.text);
|
|
2235
|
+
if (!isNarration(text)) {
|
|
2236
|
+
currentAssistant.push(text);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
} catch {
|
|
2242
|
+
continue;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
if (currentUser !== null && currentAssistant.length > 0) {
|
|
2246
|
+
rounds.push({ user: currentUser, assistantParts: currentAssistant });
|
|
2247
|
+
}
|
|
2248
|
+
return { rounds, date, startedAt, updatedAt, sourceTimestamp, project };
|
|
2249
|
+
}
|
|
2250
|
+
function walkCodexSessionFiles(dir, visit) {
|
|
2251
|
+
try {
|
|
2252
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
2253
|
+
const fullPath = join3(dir, entry.name);
|
|
2254
|
+
if (entry.isDirectory()) {
|
|
2255
|
+
walkCodexSessionFiles(fullPath, visit);
|
|
2256
|
+
} else if (entry.name.endsWith(".jsonl")) {
|
|
2257
|
+
visit(fullPath);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
} catch {}
|
|
2261
|
+
}
|
|
2262
|
+
function countImportableCodexSessions() {
|
|
2263
|
+
const sessionsDir = join3(homedir3(), ".codex", "sessions");
|
|
2264
|
+
if (!existsSync3(sessionsDir))
|
|
2265
|
+
return 0;
|
|
2266
|
+
let count = 0;
|
|
2267
|
+
walkCodexSessionFiles(sessionsDir, (filePath) => {
|
|
2268
|
+
try {
|
|
2269
|
+
const lines = readFileSync3(filePath, "utf-8").split(`
|
|
2270
|
+
`).filter(Boolean);
|
|
2271
|
+
const { rounds } = parseCodexSession(lines);
|
|
2272
|
+
if (rounds.length > 0)
|
|
2273
|
+
count++;
|
|
2274
|
+
} catch {}
|
|
2275
|
+
});
|
|
2276
|
+
return count;
|
|
2277
|
+
}
|
|
2333
2278
|
function walkCodexSessions(dir, memories, sessionIds, stats) {
|
|
2334
2279
|
try {
|
|
2335
|
-
for (const entry of
|
|
2280
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
2336
2281
|
if (entry.isDirectory()) {
|
|
2337
|
-
walkCodexSessions(
|
|
2282
|
+
walkCodexSessions(join3(dir, entry.name), memories, sessionIds, stats);
|
|
2338
2283
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
2339
2284
|
const sessionId = basename2(entry.name, ".jsonl");
|
|
2340
2285
|
try {
|
|
2341
|
-
const lines =
|
|
2286
|
+
const lines = readFileSync3(join3(dir, entry.name), "utf-8").split(`
|
|
2342
2287
|
`).filter(Boolean);
|
|
2343
|
-
|
|
2344
|
-
let project = null;
|
|
2345
|
-
const rounds = [];
|
|
2346
|
-
let currentUser = null;
|
|
2347
|
-
let currentAssistant = [];
|
|
2348
|
-
for (const line of lines) {
|
|
2349
|
-
try {
|
|
2350
|
-
const obj = JSON.parse(line);
|
|
2351
|
-
if (!date && obj.timestamp)
|
|
2352
|
-
date = typeof obj.timestamp === "string" ? obj.timestamp.slice(0, 10) : null;
|
|
2353
|
-
if (obj.type === "session_meta" && obj.payload?.cwd) {
|
|
2354
|
-
project = projectFromCwd(obj.payload.cwd);
|
|
2355
|
-
}
|
|
2356
|
-
let role;
|
|
2357
|
-
let msgContent;
|
|
2358
|
-
if (obj.type === "response_item" && obj.payload?.type === "message") {
|
|
2359
|
-
role = obj.payload.role;
|
|
2360
|
-
msgContent = Array.isArray(obj.payload.content) ? obj.payload.content : undefined;
|
|
2361
|
-
} else if (obj.type === "message" && obj.role) {
|
|
2362
|
-
role = obj.role;
|
|
2363
|
-
msgContent = Array.isArray(obj.content) ? obj.content : undefined;
|
|
2364
|
-
}
|
|
2365
|
-
if (!role || !msgContent)
|
|
2366
|
-
continue;
|
|
2367
|
-
if (role === "user") {
|
|
2368
|
-
for (const block of msgContent) {
|
|
2369
|
-
if (block.type === "input_text" && block.text) {
|
|
2370
|
-
if (!project) {
|
|
2371
|
-
const cwd = extractCwd(block.text);
|
|
2372
|
-
if (cwd)
|
|
2373
|
-
project = projectFromCwd(cwd);
|
|
2374
|
-
}
|
|
2375
|
-
const text = cleanText(block.text);
|
|
2376
|
-
if (isCodexBoilerplate(text))
|
|
2377
|
-
continue;
|
|
2378
|
-
if (text.length >= 50) {
|
|
2379
|
-
if (currentUser !== null && currentAssistant.length > 0) {
|
|
2380
|
-
rounds.push({ user: currentUser, assistantParts: currentAssistant });
|
|
2381
|
-
}
|
|
2382
|
-
currentUser = text;
|
|
2383
|
-
currentAssistant = [];
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
} else if (role === "assistant") {
|
|
2388
|
-
for (const block of msgContent) {
|
|
2389
|
-
if (block.type === "output_text" && block.text) {
|
|
2390
|
-
const text = cleanText(block.text);
|
|
2391
|
-
if (!isNarration(text)) {
|
|
2392
|
-
currentAssistant.push(text);
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
} catch {
|
|
2398
|
-
continue;
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
if (currentUser !== null && currentAssistant.length > 0) {
|
|
2402
|
-
rounds.push({ user: currentUser, assistantParts: currentAssistant });
|
|
2403
|
-
}
|
|
2288
|
+
const { rounds, date, startedAt, updatedAt, sourceTimestamp, project } = parseCodexSession(lines);
|
|
2404
2289
|
if (rounds.length === 0) {
|
|
2405
2290
|
stats.filtered++;
|
|
2406
2291
|
continue;
|
|
@@ -2429,8 +2314,12 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
|
|
|
2429
2314
|
source: "codex-session",
|
|
2430
2315
|
sessionId,
|
|
2431
2316
|
date: date || "unknown",
|
|
2317
|
+
...sourceTimestamp ? { sourceTimestamp } : {},
|
|
2318
|
+
...startedAt ? { sessionStartedAt: startedAt } : {},
|
|
2319
|
+
...updatedAt ? { sessionUpdatedAt: updatedAt } : {},
|
|
2432
2320
|
...project ? { project } : {}
|
|
2433
|
-
}
|
|
2321
|
+
},
|
|
2322
|
+
...sourceTimestamp ? { created_at: sourceTimestamp, updated_at: sourceTimestamp } : {}
|
|
2434
2323
|
});
|
|
2435
2324
|
sessionIds.push(sessionId);
|
|
2436
2325
|
} catch {}
|
|
@@ -2441,35 +2330,104 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
|
|
|
2441
2330
|
|
|
2442
2331
|
// src/ingest/cursor.ts
|
|
2443
2332
|
init_shared();
|
|
2444
|
-
import { readFileSync as
|
|
2445
|
-
import { join as
|
|
2446
|
-
import { createRequire as
|
|
2333
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
2334
|
+
import { dirname, join as join4 } from "node:path";
|
|
2335
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
2336
|
+
import { homedir as homedir4 } from "node:os";
|
|
2447
2337
|
var MAX_DB_SIZE = 2 * 1024 * 1024 * 1024;
|
|
2448
2338
|
var WARN_DB_SIZE = 500 * 1024 * 1024;
|
|
2449
2339
|
var MIN_TURN_LEN2 = 50;
|
|
2340
|
+
function parseCursorTimestamp(value) {
|
|
2341
|
+
const parsed = parseTimestampMs(value);
|
|
2342
|
+
if (parsed === null)
|
|
2343
|
+
return null;
|
|
2344
|
+
return parsed < 10000000000 ? parsed * 1000 : parsed;
|
|
2345
|
+
}
|
|
2450
2346
|
function loadSqlJs(wasmDir) {
|
|
2451
2347
|
try {
|
|
2452
2348
|
if (wasmDir) {
|
|
2453
|
-
const require2 =
|
|
2349
|
+
const require2 = createRequire2(join4(wasmDir, "sql.js", "package.json"));
|
|
2454
2350
|
return require2("sql.js");
|
|
2455
2351
|
} else {
|
|
2456
|
-
const require2 =
|
|
2352
|
+
const require2 = createRequire2(import.meta.url);
|
|
2457
2353
|
return require2("sql.js");
|
|
2458
2354
|
}
|
|
2459
2355
|
} catch {
|
|
2460
2356
|
return null;
|
|
2461
2357
|
}
|
|
2462
2358
|
}
|
|
2359
|
+
function resolveSqlJsWasmDir(wasmDir) {
|
|
2360
|
+
if (wasmDir)
|
|
2361
|
+
return wasmDir;
|
|
2362
|
+
try {
|
|
2363
|
+
const require2 = createRequire2(import.meta.url);
|
|
2364
|
+
const packageJson = require2.resolve("sql.js/package.json");
|
|
2365
|
+
return dirname(dirname(packageJson));
|
|
2366
|
+
} catch {
|
|
2367
|
+
const installedBundleDir = join4(homedir4(), ".conare", "bin", "node_modules");
|
|
2368
|
+
return existsSync4(join4(installedBundleDir, "sql.js", "package.json")) ? installedBundleDir : undefined;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2463
2371
|
function openDb(initSqlJs, dbPath, wasmDir) {
|
|
2464
2372
|
const locateOpts = {};
|
|
2465
2373
|
if (wasmDir) {
|
|
2466
|
-
locateOpts.locateFile = (file) =>
|
|
2374
|
+
locateOpts.locateFile = (file) => join4(wasmDir, "sql.js", "dist", file);
|
|
2467
2375
|
}
|
|
2468
2376
|
return initSqlJs(locateOpts).then((SQL) => {
|
|
2469
|
-
const buffer =
|
|
2377
|
+
const buffer = readFileSync4(dbPath);
|
|
2470
2378
|
return new SQL.Database(buffer);
|
|
2471
2379
|
});
|
|
2472
2380
|
}
|
|
2381
|
+
async function countImportableCursorSessions(dbPath, wasmDir) {
|
|
2382
|
+
try {
|
|
2383
|
+
const fileSize = statSync(dbPath).size;
|
|
2384
|
+
if (fileSize > MAX_DB_SIZE)
|
|
2385
|
+
return 0;
|
|
2386
|
+
} catch {
|
|
2387
|
+
return 0;
|
|
2388
|
+
}
|
|
2389
|
+
const effectiveWasmDir = resolveSqlJsWasmDir(wasmDir);
|
|
2390
|
+
const initSqlJs = loadSqlJs(effectiveWasmDir);
|
|
2391
|
+
if (!initSqlJs)
|
|
2392
|
+
return 0;
|
|
2393
|
+
let db;
|
|
2394
|
+
try {
|
|
2395
|
+
db = await openDb(initSqlJs, dbPath, effectiveWasmDir);
|
|
2396
|
+
} catch {
|
|
2397
|
+
return 0;
|
|
2398
|
+
}
|
|
2399
|
+
try {
|
|
2400
|
+
let rows = [];
|
|
2401
|
+
try {
|
|
2402
|
+
const results = db.exec("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'");
|
|
2403
|
+
if (results.length > 0)
|
|
2404
|
+
rows = results[0].values;
|
|
2405
|
+
} catch {
|
|
2406
|
+
return 0;
|
|
2407
|
+
}
|
|
2408
|
+
let count = 0;
|
|
2409
|
+
for (const [key, value] of rows) {
|
|
2410
|
+
const composerId = key.replace("composerData:", "");
|
|
2411
|
+
let parsed;
|
|
2412
|
+
try {
|
|
2413
|
+
parsed = JSON.parse(value);
|
|
2414
|
+
if (!parsed || typeof parsed !== "object")
|
|
2415
|
+
continue;
|
|
2416
|
+
} catch {
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
const bubbleHeaders = parsed.fullConversationHeadersOnly;
|
|
2420
|
+
if (!Array.isArray(bubbleHeaders) || bubbleHeaders.length === 0)
|
|
2421
|
+
continue;
|
|
2422
|
+
const turns = extractTurns(db, composerId, bubbleHeaders);
|
|
2423
|
+
if (turns.length > 0)
|
|
2424
|
+
count++;
|
|
2425
|
+
}
|
|
2426
|
+
return count;
|
|
2427
|
+
} finally {
|
|
2428
|
+
db.close();
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2473
2431
|
function extractTurns(db, composerId, bubbleHeaders) {
|
|
2474
2432
|
const turns = [];
|
|
2475
2433
|
let pendingUser = null;
|
|
@@ -2514,14 +2472,15 @@ async function ingestCursor(dbPath, wasmDir) {
|
|
|
2514
2472
|
if (fileSize > WARN_DB_SIZE) {
|
|
2515
2473
|
console.log(` Warning: large database (${(fileSize / 1024 / 1024).toFixed(0)}MB), loading into memory...`);
|
|
2516
2474
|
}
|
|
2517
|
-
const
|
|
2475
|
+
const effectiveWasmDir = resolveSqlJsWasmDir(wasmDir);
|
|
2476
|
+
const initSqlJs = loadSqlJs(effectiveWasmDir);
|
|
2518
2477
|
if (!initSqlJs) {
|
|
2519
2478
|
console.log(" Skipping Cursor: sql.js not available");
|
|
2520
2479
|
return { memories, sessionIds, skipped: 0, filtered, deduped };
|
|
2521
2480
|
}
|
|
2522
2481
|
let db;
|
|
2523
2482
|
try {
|
|
2524
|
-
db = await openDb(initSqlJs, dbPath,
|
|
2483
|
+
db = await openDb(initSqlJs, dbPath, effectiveWasmDir);
|
|
2525
2484
|
} catch (e) {
|
|
2526
2485
|
console.log(` Skipping Cursor: cannot open database (${e.message})`);
|
|
2527
2486
|
return { memories, sessionIds, skipped: 0, filtered, deduped };
|
|
@@ -2557,7 +2516,9 @@ async function ingestCursor(dbPath, wasmDir) {
|
|
|
2557
2516
|
continue;
|
|
2558
2517
|
}
|
|
2559
2518
|
const sessionName = parsed.name || "Cursor Chat";
|
|
2560
|
-
const
|
|
2519
|
+
const sourceTimestamp = parseCursorTimestamp(parsed.lastUpdatedAt) || parseCursorTimestamp(parsed.updatedAt) || parseCursorTimestamp(parsed.createdAt);
|
|
2520
|
+
const startedTimestamp = parseCursorTimestamp(parsed.createdAt);
|
|
2521
|
+
const date = sourceTimestamp ? new Date(sourceTimestamp).toISOString().slice(0, 10) : "unknown";
|
|
2561
2522
|
const header = `# ${sessionName} | ${date}`;
|
|
2562
2523
|
const content = fitContent(header, turns);
|
|
2563
2524
|
const contentHash = createContentHash(content);
|
|
@@ -2576,8 +2537,12 @@ async function ingestCursor(dbPath, wasmDir) {
|
|
|
2576
2537
|
source: "cursor",
|
|
2577
2538
|
sessionId: composerId,
|
|
2578
2539
|
name: sessionName,
|
|
2579
|
-
date
|
|
2580
|
-
|
|
2540
|
+
date,
|
|
2541
|
+
...sourceTimestamp ? { sourceTimestamp } : {},
|
|
2542
|
+
...startedTimestamp ? { sessionStartedAt: new Date(startedTimestamp).toISOString() } : {},
|
|
2543
|
+
...sourceTimestamp ? { sessionUpdatedAt: new Date(sourceTimestamp).toISOString() } : {}
|
|
2544
|
+
},
|
|
2545
|
+
...sourceTimestamp ? { created_at: sourceTimestamp, updated_at: sourceTimestamp } : {}
|
|
2581
2546
|
});
|
|
2582
2547
|
sessionIds.push(composerId);
|
|
2583
2548
|
}
|
|
@@ -2589,15 +2554,202 @@ async function ingestCursor(dbPath, wasmDir) {
|
|
|
2589
2554
|
return { memories, sessionIds, skipped: filtered + deduped, filtered, deduped };
|
|
2590
2555
|
}
|
|
2591
2556
|
|
|
2557
|
+
// src/detect.ts
|
|
2558
|
+
async function detect() {
|
|
2559
|
+
const home = homedir5();
|
|
2560
|
+
const os = platform3();
|
|
2561
|
+
const tools = [];
|
|
2562
|
+
const claudeDir = join5(home, ".claude", "projects");
|
|
2563
|
+
if (existsSync5(claudeDir)) {
|
|
2564
|
+
const sessionCount = countImportableClaudeSessions();
|
|
2565
|
+
tools.push({ name: "Claude Code", id: "claude", available: sessionCount > 0, path: claudeDir, sessionCount, sessionCountApproximate: true });
|
|
2566
|
+
} else {
|
|
2567
|
+
tools.push({ name: "Claude Code", id: "claude", available: false, path: claudeDir, sessionCount: 0 });
|
|
2568
|
+
}
|
|
2569
|
+
const codexConfig = join5(home, ".codex", "config.toml");
|
|
2570
|
+
const codexSessions = join5(home, ".codex", "sessions");
|
|
2571
|
+
if (existsSync5(codexConfig) || existsSync5(codexSessions)) {
|
|
2572
|
+
const sessionCount = countImportableCodexSessions();
|
|
2573
|
+
tools.push({ name: "Codex", id: "codex", available: true, path: codexConfig, sessionCount, sessionCountApproximate: true });
|
|
2574
|
+
} else {
|
|
2575
|
+
tools.push({ name: "Codex", id: "codex", available: false, path: codexConfig, sessionCount: 0 });
|
|
2576
|
+
}
|
|
2577
|
+
let cursorDbPath;
|
|
2578
|
+
if (os === "darwin") {
|
|
2579
|
+
cursorDbPath = join5(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
2580
|
+
} else if (os === "win32") {
|
|
2581
|
+
cursorDbPath = join5(process.env.APPDATA || join5(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
|
|
2582
|
+
} else {
|
|
2583
|
+
cursorDbPath = join5(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
2584
|
+
}
|
|
2585
|
+
tools.push({
|
|
2586
|
+
name: "Cursor",
|
|
2587
|
+
id: "cursor",
|
|
2588
|
+
available: existsSync5(cursorDbPath),
|
|
2589
|
+
path: cursorDbPath,
|
|
2590
|
+
sessionCount: existsSync5(cursorDbPath) ? await countImportableCursorSessions(cursorDbPath) : 0,
|
|
2591
|
+
sessionCountApproximate: true
|
|
2592
|
+
});
|
|
2593
|
+
const windsurfDir = join5(home, ".codeium", "windsurf");
|
|
2594
|
+
tools.push({
|
|
2595
|
+
name: "Windsurf",
|
|
2596
|
+
id: "windsurf",
|
|
2597
|
+
available: existsSync5(windsurfDir),
|
|
2598
|
+
path: join5(windsurfDir, "mcp_config.json"),
|
|
2599
|
+
sessionCount: 0
|
|
2600
|
+
});
|
|
2601
|
+
let vscodePath;
|
|
2602
|
+
if (os === "darwin") {
|
|
2603
|
+
vscodePath = join5(home, "Library", "Application Support", "Code");
|
|
2604
|
+
} else if (os === "win32") {
|
|
2605
|
+
vscodePath = join5(process.env.APPDATA || join5(home, "AppData", "Roaming"), "Code");
|
|
2606
|
+
} else {
|
|
2607
|
+
vscodePath = join5(home, ".config", "Code");
|
|
2608
|
+
}
|
|
2609
|
+
tools.push({
|
|
2610
|
+
name: "VS Code Copilot",
|
|
2611
|
+
id: "vscode",
|
|
2612
|
+
available: existsSync5(vscodePath),
|
|
2613
|
+
path: join5(vscodePath, "User", "mcp.json"),
|
|
2614
|
+
sessionCount: 0
|
|
2615
|
+
});
|
|
2616
|
+
let clinePath;
|
|
2617
|
+
if (os === "darwin") {
|
|
2618
|
+
clinePath = join5(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev");
|
|
2619
|
+
} else if (os === "win32") {
|
|
2620
|
+
clinePath = join5(process.env.APPDATA || join5(home, "AppData", "Roaming"), "Code", "User", "globalStorage", "saoudrizwan.claude-dev");
|
|
2621
|
+
} else {
|
|
2622
|
+
clinePath = join5(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev");
|
|
2623
|
+
}
|
|
2624
|
+
tools.push({
|
|
2625
|
+
name: "Cline",
|
|
2626
|
+
id: "cline",
|
|
2627
|
+
available: existsSync5(clinePath),
|
|
2628
|
+
path: join5(clinePath, "settings", "cline_mcp_settings.json"),
|
|
2629
|
+
sessionCount: 0
|
|
2630
|
+
});
|
|
2631
|
+
const zedPath = os === "darwin" ? join5(home, ".zed") : join5(home, ".config", "zed");
|
|
2632
|
+
tools.push({
|
|
2633
|
+
name: "Zed",
|
|
2634
|
+
id: "zed",
|
|
2635
|
+
available: existsSync5(zedPath),
|
|
2636
|
+
path: join5(zedPath, "settings.json"),
|
|
2637
|
+
sessionCount: 0
|
|
2638
|
+
});
|
|
2639
|
+
const openclawDir = join5(home, ".openclaw");
|
|
2640
|
+
tools.push({
|
|
2641
|
+
name: "OpenClaw",
|
|
2642
|
+
id: "openclaw",
|
|
2643
|
+
available: existsSync5(openclawDir),
|
|
2644
|
+
path: join5(openclawDir, "openclaw.json"),
|
|
2645
|
+
sessionCount: 0
|
|
2646
|
+
});
|
|
2647
|
+
const antigravityDir = join5(home, ".gemini", "antigravity");
|
|
2648
|
+
const antigravityConvDir = join5(antigravityDir, "conversations");
|
|
2649
|
+
let antigravityCount = 0;
|
|
2650
|
+
if (existsSync5(antigravityConvDir)) {
|
|
2651
|
+
try {
|
|
2652
|
+
antigravityCount = readdirSync3(antigravityConvDir).filter((f) => f.endsWith(".pb")).length;
|
|
2653
|
+
} catch {}
|
|
2654
|
+
}
|
|
2655
|
+
tools.push({
|
|
2656
|
+
name: "Antigravity",
|
|
2657
|
+
id: "antigravity",
|
|
2658
|
+
available: existsSync5(antigravityDir),
|
|
2659
|
+
path: join5(antigravityDir, "mcp_config.json"),
|
|
2660
|
+
sessionCount: antigravityCount
|
|
2661
|
+
});
|
|
2662
|
+
return tools;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
// src/auth.ts
|
|
2666
|
+
import { execSync } from "node:child_process";
|
|
2667
|
+
import { platform as platform4 } from "node:os";
|
|
2668
|
+
var API_URL = "https://conare.ai";
|
|
2669
|
+
async function browserAuth() {
|
|
2670
|
+
const stateBytes = new Uint8Array(16);
|
|
2671
|
+
crypto.getRandomValues(stateBytes);
|
|
2672
|
+
const state = Array.from(stateBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2673
|
+
const sessionRes = await fetch(`${API_URL}/api/auth/cli-session`, {
|
|
2674
|
+
method: "POST",
|
|
2675
|
+
headers: { "Content-Type": "application/json" },
|
|
2676
|
+
body: JSON.stringify({ state })
|
|
2677
|
+
});
|
|
2678
|
+
if (!sessionRes.ok) {
|
|
2679
|
+
throw new Error(`Failed to create auth session: HTTP ${sessionRes.status}`);
|
|
2680
|
+
}
|
|
2681
|
+
const { code, expiresAt } = await sessionRes.json();
|
|
2682
|
+
const authUrl = `${API_URL}/cli-auth?code=${code}&state=${state}`;
|
|
2683
|
+
const opened = openBrowser(authUrl);
|
|
2684
|
+
if (!opened) {
|
|
2685
|
+
console.log("");
|
|
2686
|
+
console.log(" Open this URL in your browser to sign in:");
|
|
2687
|
+
console.log("");
|
|
2688
|
+
console.log(` ${authUrl}`);
|
|
2689
|
+
console.log("");
|
|
2690
|
+
}
|
|
2691
|
+
const timeout = Math.max(expiresAt - Date.now(), 0);
|
|
2692
|
+
const deadline = Date.now() + timeout;
|
|
2693
|
+
while (Date.now() < deadline) {
|
|
2694
|
+
await sleep(2000);
|
|
2695
|
+
const exchangeRes = await fetch(`${API_URL}/api/auth/cli-exchange`, {
|
|
2696
|
+
method: "POST",
|
|
2697
|
+
headers: { "Content-Type": "application/json" },
|
|
2698
|
+
body: JSON.stringify({ code, state })
|
|
2699
|
+
});
|
|
2700
|
+
if (exchangeRes.status === 202) {
|
|
2701
|
+
continue;
|
|
2702
|
+
}
|
|
2703
|
+
if (exchangeRes.ok) {
|
|
2704
|
+
const data = await exchangeRes.json();
|
|
2705
|
+
return data.apiKey;
|
|
2706
|
+
}
|
|
2707
|
+
if (exchangeRes.status === 410) {
|
|
2708
|
+
throw new Error("Authentication code was already used. Please try again.");
|
|
2709
|
+
}
|
|
2710
|
+
if (exchangeRes.status === 404) {
|
|
2711
|
+
throw new Error("Authentication session expired. Please try again.");
|
|
2712
|
+
}
|
|
2713
|
+
throw new Error(`Authentication failed: HTTP ${exchangeRes.status}`);
|
|
2714
|
+
}
|
|
2715
|
+
throw new Error("Authentication timed out. Please try again.");
|
|
2716
|
+
}
|
|
2717
|
+
function openBrowser(url) {
|
|
2718
|
+
try {
|
|
2719
|
+
const os = platform4();
|
|
2720
|
+
if (os === "darwin") {
|
|
2721
|
+
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
2722
|
+
} else if (os === "win32") {
|
|
2723
|
+
execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
2724
|
+
} else {
|
|
2725
|
+
try {
|
|
2726
|
+
execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
2727
|
+
} catch {
|
|
2728
|
+
try {
|
|
2729
|
+
execSync(`wslview "${url}"`, { stdio: "ignore" });
|
|
2730
|
+
} catch {
|
|
2731
|
+
return false;
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
return true;
|
|
2736
|
+
} catch {
|
|
2737
|
+
return false;
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
function sleep(ms) {
|
|
2741
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2592
2744
|
// src/index.ts
|
|
2593
2745
|
init_codebase();
|
|
2594
2746
|
init_shared();
|
|
2595
2747
|
init_api();
|
|
2596
2748
|
|
|
2597
2749
|
// src/configure.ts
|
|
2598
|
-
import { existsSync as
|
|
2599
|
-
import { dirname, join as join7 } from "node:path";
|
|
2600
|
-
import { homedir as
|
|
2750
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2, symlinkSync, readlinkSync, rmSync } from "node:fs";
|
|
2751
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
2752
|
+
import { homedir as homedir6, platform as platform5 } from "node:os";
|
|
2601
2753
|
import { spawnSync } from "node:child_process";
|
|
2602
2754
|
var CONARE_URL = "https://conare.ai";
|
|
2603
2755
|
var SERVER_NAME = "conare";
|
|
@@ -2614,13 +2766,13 @@ var MCP_TARGETS = [
|
|
|
2614
2766
|
];
|
|
2615
2767
|
function readJsonFile(path) {
|
|
2616
2768
|
try {
|
|
2617
|
-
return JSON.parse(
|
|
2769
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
2618
2770
|
} catch {
|
|
2619
2771
|
return {};
|
|
2620
2772
|
}
|
|
2621
2773
|
}
|
|
2622
2774
|
function writeJsonFile(path, data) {
|
|
2623
|
-
mkdirSync2(
|
|
2775
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
2624
2776
|
writeFileSync2(path, JSON.stringify(data, null, 2) + `
|
|
2625
2777
|
`);
|
|
2626
2778
|
}
|
|
@@ -2634,32 +2786,41 @@ function getServerConfig(apiKey) {
|
|
|
2634
2786
|
};
|
|
2635
2787
|
}
|
|
2636
2788
|
function configureClaude(apiKey) {
|
|
2637
|
-
const claudeConfigPath = join7(homedir5(), ".claude.json");
|
|
2638
|
-
const claudeMcpPath = join7(homedir5(), ".claude", "mcp.json");
|
|
2639
2789
|
if (spawnSync("claude", ["mcp", "add-json", SERVER_NAME, "--scope", "user", JSON.stringify(getServerConfig(apiKey))], {
|
|
2640
2790
|
stdio: "ignore",
|
|
2641
2791
|
shell: platform5() === "win32"
|
|
2642
2792
|
}).status === 0) {
|
|
2643
2793
|
return "\x1B[32m✓\x1B[0m Claude Code";
|
|
2644
2794
|
}
|
|
2795
|
+
if (spawnSync("claude", [
|
|
2796
|
+
"mcp",
|
|
2797
|
+
"add",
|
|
2798
|
+
SERVER_NAME,
|
|
2799
|
+
"--transport",
|
|
2800
|
+
"http",
|
|
2801
|
+
"--scope",
|
|
2802
|
+
"user",
|
|
2803
|
+
"--header",
|
|
2804
|
+
`Authorization: Bearer ${apiKey}`,
|
|
2805
|
+
"--",
|
|
2806
|
+
`${CONARE_URL}/mcp`
|
|
2807
|
+
], {
|
|
2808
|
+
stdio: "ignore",
|
|
2809
|
+
shell: platform5() === "win32"
|
|
2810
|
+
}).status === 0) {
|
|
2811
|
+
return "\x1B[32m✓\x1B[0m Claude Code";
|
|
2812
|
+
}
|
|
2813
|
+
const claudeConfigPath = join7(homedir6(), ".claude.json");
|
|
2645
2814
|
const config = readJsonFile(claudeConfigPath);
|
|
2646
2815
|
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
2647
2816
|
config.mcpServers = {};
|
|
2648
2817
|
}
|
|
2649
2818
|
config.mcpServers[SERVER_NAME] = getServerConfig(apiKey);
|
|
2650
2819
|
writeJsonFile(claudeConfigPath, config);
|
|
2651
|
-
|
|
2652
|
-
const mcpConfig = readJsonFile(claudeMcpPath);
|
|
2653
|
-
if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== "object") {
|
|
2654
|
-
mcpConfig.mcpServers = {};
|
|
2655
|
-
}
|
|
2656
|
-
mcpConfig.mcpServers[SERVER_NAME] = getServerConfig(apiKey);
|
|
2657
|
-
writeJsonFile(claudeMcpPath, mcpConfig);
|
|
2658
|
-
}
|
|
2659
|
-
return "\x1B[32m✓\x1B[0m Claude Code";
|
|
2820
|
+
return "\x1B[32m✓\x1B[0m Claude Code (json fallback)";
|
|
2660
2821
|
}
|
|
2661
2822
|
function configureCodex(apiKey) {
|
|
2662
|
-
const configPath = join7(
|
|
2823
|
+
const configPath = join7(homedir6(), ".codex", "config.toml");
|
|
2663
2824
|
if (spawnSync("codex", ["mcp", "add", SERVER_NAME, "--url", `${CONARE_URL}/mcp`, "--header", `Authorization: Bearer ${apiKey}`], {
|
|
2664
2825
|
stdio: "ignore",
|
|
2665
2826
|
shell: platform5() === "win32"
|
|
@@ -2668,7 +2829,7 @@ function configureCodex(apiKey) {
|
|
|
2668
2829
|
}
|
|
2669
2830
|
let toml = "";
|
|
2670
2831
|
try {
|
|
2671
|
-
toml =
|
|
2832
|
+
toml = readFileSync6(configPath, "utf-8");
|
|
2672
2833
|
} catch {}
|
|
2673
2834
|
const sectionHeader = `[mcp_servers.${SERVER_NAME}]`;
|
|
2674
2835
|
const newSection = [
|
|
@@ -2689,11 +2850,11 @@ function configureCodex(apiKey) {
|
|
|
2689
2850
|
${newSection}
|
|
2690
2851
|
` : `${newSection}
|
|
2691
2852
|
`;
|
|
2692
|
-
mkdirSync2(
|
|
2853
|
+
mkdirSync2(dirname2(configPath), { recursive: true });
|
|
2693
2854
|
writeFileSync2(configPath, result);
|
|
2694
|
-
const oldMcpJson = join7(
|
|
2855
|
+
const oldMcpJson = join7(homedir6(), ".codex", "mcp.json");
|
|
2695
2856
|
try {
|
|
2696
|
-
if (
|
|
2857
|
+
if (existsSync7(oldMcpJson)) {
|
|
2697
2858
|
const old = readJsonFile(oldMcpJson);
|
|
2698
2859
|
const servers = old.mcpServers;
|
|
2699
2860
|
if (servers && (("conare" in servers) || ("conare-memory" in servers))) {
|
|
@@ -2710,7 +2871,7 @@ ${newSection}
|
|
|
2710
2871
|
return "\x1B[32m✓\x1B[0m Codex";
|
|
2711
2872
|
}
|
|
2712
2873
|
function configureCursor(apiKey) {
|
|
2713
|
-
const configPath = join7(
|
|
2874
|
+
const configPath = join7(homedir6(), ".cursor", "mcp.json");
|
|
2714
2875
|
const config = readJsonFile(configPath);
|
|
2715
2876
|
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
2716
2877
|
config.mcpServers = {};
|
|
@@ -2723,7 +2884,7 @@ function configureCursor(apiKey) {
|
|
|
2723
2884
|
return "\x1B[32m✓\x1B[0m Cursor";
|
|
2724
2885
|
}
|
|
2725
2886
|
function configureWindsurf(apiKey) {
|
|
2726
|
-
const configPath = join7(
|
|
2887
|
+
const configPath = join7(homedir6(), ".codeium", "windsurf", "mcp_config.json");
|
|
2727
2888
|
const config = readJsonFile(configPath);
|
|
2728
2889
|
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
2729
2890
|
config.mcpServers = {};
|
|
@@ -2739,11 +2900,11 @@ function configureVscode(apiKey) {
|
|
|
2739
2900
|
const os = platform5();
|
|
2740
2901
|
let configPath;
|
|
2741
2902
|
if (os === "darwin") {
|
|
2742
|
-
configPath = join7(
|
|
2903
|
+
configPath = join7(homedir6(), "Library", "Application Support", "Code", "User", "mcp.json");
|
|
2743
2904
|
} else if (os === "win32") {
|
|
2744
|
-
configPath = join7(process.env.APPDATA || join7(
|
|
2905
|
+
configPath = join7(process.env.APPDATA || join7(homedir6(), "AppData", "Roaming"), "Code", "User", "mcp.json");
|
|
2745
2906
|
} else {
|
|
2746
|
-
configPath = join7(
|
|
2907
|
+
configPath = join7(homedir6(), ".config", "Code", "User", "mcp.json");
|
|
2747
2908
|
}
|
|
2748
2909
|
const config = readJsonFile(configPath);
|
|
2749
2910
|
if (!config.servers || typeof config.servers !== "object") {
|
|
@@ -2761,11 +2922,11 @@ function configureCline(apiKey) {
|
|
|
2761
2922
|
const os = platform5();
|
|
2762
2923
|
let configPath;
|
|
2763
2924
|
if (os === "darwin") {
|
|
2764
|
-
configPath = join7(
|
|
2925
|
+
configPath = join7(homedir6(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
2765
2926
|
} else if (os === "win32") {
|
|
2766
|
-
configPath = join7(process.env.APPDATA || join7(
|
|
2927
|
+
configPath = join7(process.env.APPDATA || join7(homedir6(), "AppData", "Roaming"), "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
2767
2928
|
} else {
|
|
2768
|
-
configPath = join7(
|
|
2929
|
+
configPath = join7(homedir6(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
2769
2930
|
}
|
|
2770
2931
|
const config = readJsonFile(configPath);
|
|
2771
2932
|
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
@@ -2783,9 +2944,9 @@ function configureZed(apiKey) {
|
|
|
2783
2944
|
const os = platform5();
|
|
2784
2945
|
let configPath;
|
|
2785
2946
|
if (os === "darwin") {
|
|
2786
|
-
configPath = join7(
|
|
2947
|
+
configPath = join7(homedir6(), ".zed", "settings.json");
|
|
2787
2948
|
} else {
|
|
2788
|
-
configPath = join7(
|
|
2949
|
+
configPath = join7(homedir6(), ".config", "zed", "settings.json");
|
|
2789
2950
|
}
|
|
2790
2951
|
const config = readJsonFile(configPath);
|
|
2791
2952
|
if (!config.context_servers || typeof config.context_servers !== "object") {
|
|
@@ -2806,7 +2967,7 @@ function configureZed(apiKey) {
|
|
|
2806
2967
|
return "\x1B[32m✓\x1B[0m Zed";
|
|
2807
2968
|
}
|
|
2808
2969
|
function configureOpenclaw(apiKey) {
|
|
2809
|
-
const configPath = join7(
|
|
2970
|
+
const configPath = join7(homedir6(), ".openclaw", "openclaw.json");
|
|
2810
2971
|
const config = readJsonFile(configPath);
|
|
2811
2972
|
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
2812
2973
|
config.mcpServers = {};
|
|
@@ -2820,7 +2981,7 @@ function configureOpenclaw(apiKey) {
|
|
|
2820
2981
|
return "\x1B[32m✓\x1B[0m OpenClaw";
|
|
2821
2982
|
}
|
|
2822
2983
|
function configureAntigravity(apiKey) {
|
|
2823
|
-
const configPath = join7(
|
|
2984
|
+
const configPath = join7(homedir6(), ".gemini", "antigravity", "mcp_config.json");
|
|
2824
2985
|
const config = readJsonFile(configPath);
|
|
2825
2986
|
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
2826
2987
|
config.mcpServers = {};
|
|
@@ -2849,7 +3010,7 @@ description: Load prior project context, search past sessions, save durable pref
|
|
|
2849
3010
|
compatibility: Requires the Conare MCP server tools (\`recall\`, \`search\`, \`save\`, \`list\`, \`forget\`) to be installed and connected.
|
|
2850
3011
|
metadata:
|
|
2851
3012
|
author: Conare
|
|
2852
|
-
version: 1.
|
|
3013
|
+
version: 1.3.0
|
|
2853
3014
|
mcp-server: conare
|
|
2854
3015
|
homepage: https://conare.ai
|
|
2855
3016
|
---
|
|
@@ -2868,22 +3029,33 @@ This skill teaches the agent the default workflow, tool-selection rules, and que
|
|
|
2868
3029
|
|
|
2869
3030
|
| Situation | Tool | Example |
|
|
2870
3031
|
|-----------|------|---------|
|
|
2871
|
-
| Start of conversation | \`recall\` | Always call first with conversation context |
|
|
2872
|
-
| User asks about past work | \`search\` |
|
|
3032
|
+
| Start of conversation | \`recall\` | Always call first with conversation context + \`prompt\` |
|
|
3033
|
+
| User asks about past work | \`search\` | query + \`prompt\` steering the angle |
|
|
2873
3034
|
| User says "remember this" | \`save\` | Save preferences, rules, decisions |
|
|
2874
3035
|
| User says "forget this" | \`forget\` | Remove a specific memory |
|
|
2875
3036
|
| Browse what's stored | \`list\` | "Show me recent memories" |
|
|
2876
|
-
|
|
|
2877
|
-
|
|
3037
|
+
| Exact-string raw lookup | \`search\` with \`deep: false\` | Verbatim memory text (rare) |
|
|
3038
|
+
|
|
3039
|
+
## How recall & search Work
|
|
3040
|
+
|
|
3041
|
+
Both return an LLM-synthesized answer by default — a noise-removed, detail-preserving brief distilled from the matched memories. The synthesizer is **not a summarizer**: it strips redundancy and superseded claims while preserving every specific number, file path, CLI command, code block, and the WHY behind each decision.
|
|
2878
3042
|
|
|
2879
|
-
|
|
3043
|
+
**Two axes, always pair them:**
|
|
2880
3044
|
|
|
2881
|
-
|
|
3045
|
+
- \`query\` / \`context\` → keyword-dense retrieval phrase (finds the right memories)
|
|
3046
|
+
- \`prompt\` → synthesis instruction (what to emphasize / how to structure it)
|
|
2882
3047
|
|
|
2883
|
-
|
|
2884
|
-
|
|
3048
|
+
Example:
|
|
3049
|
+
\`\`\`
|
|
3050
|
+
search({
|
|
3051
|
+
query: "auth rewrite middleware compliance",
|
|
3052
|
+
prompt: "focus on the final decision and why; preserve all file paths and config values"
|
|
3053
|
+
})
|
|
3054
|
+
\`\`\`
|
|
2885
3055
|
|
|
2886
|
-
\`prompt\`
|
|
3056
|
+
Pass \`prompt\` on almost every call. Without it the synthesizer picks a sensible default, but with it you get exactly the angle the user cares about.
|
|
3057
|
+
|
|
3058
|
+
**Opt out of synthesis** with \`deep: false\` only when you need raw memory text for an exact-string lookup. Prefer leaving it unset.
|
|
2887
3059
|
|
|
2888
3060
|
## Critical Rules
|
|
2889
3061
|
|
|
@@ -2940,14 +3112,14 @@ The wizard handles everything: account creation, API key, MCP configuration, bac
|
|
|
2940
3112
|
For manual setup, visit [conare.ai](https://conare.ai).
|
|
2941
3113
|
`;
|
|
2942
3114
|
function installSkill() {
|
|
2943
|
-
const skillDir = join7(
|
|
3115
|
+
const skillDir = join7(homedir6(), ".agents", "skills", "conare");
|
|
2944
3116
|
mkdirSync2(skillDir, { recursive: true });
|
|
2945
3117
|
writeFileSync2(join7(skillDir, "SKILL.md"), SKILL_MD);
|
|
2946
|
-
const claudeSkillsDir = join7(
|
|
3118
|
+
const claudeSkillsDir = join7(homedir6(), ".claude", "skills");
|
|
2947
3119
|
const claudeSkillDir = join7(claudeSkillsDir, "conare");
|
|
2948
3120
|
try {
|
|
2949
|
-
if (
|
|
2950
|
-
if (
|
|
3121
|
+
if (existsSync7(claudeSkillsDir)) {
|
|
3122
|
+
if (existsSync7(claudeSkillDir)) {
|
|
2951
3123
|
try {
|
|
2952
3124
|
if (readlinkSync(claudeSkillDir) === skillDir)
|
|
2953
3125
|
return "\x1B[32m✓\x1B[0m Agent Skill";
|
|
@@ -2978,16 +3150,16 @@ function configureMcp(apiKey, targets = ["claude", "codex"]) {
|
|
|
2978
3150
|
}
|
|
2979
3151
|
|
|
2980
3152
|
// src/config.ts
|
|
2981
|
-
import { existsSync as
|
|
3153
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2982
3154
|
import { join as join8 } from "node:path";
|
|
2983
|
-
import { homedir as
|
|
2984
|
-
var CONFIG_DIR = join8(
|
|
3155
|
+
import { homedir as homedir7 } from "node:os";
|
|
3156
|
+
var CONFIG_DIR = join8(homedir7(), ".conare");
|
|
2985
3157
|
var CONFIG_PATH = join8(CONFIG_DIR, "config.json");
|
|
2986
3158
|
function readConfig() {
|
|
2987
3159
|
try {
|
|
2988
|
-
if (!
|
|
3160
|
+
if (!existsSync8(CONFIG_PATH))
|
|
2989
3161
|
return {};
|
|
2990
|
-
return JSON.parse(
|
|
3162
|
+
return JSON.parse(readFileSync7(CONFIG_PATH, "utf-8"));
|
|
2991
3163
|
} catch {
|
|
2992
3164
|
return {};
|
|
2993
3165
|
}
|
|
@@ -3007,16 +3179,16 @@ function getSavedApiKey() {
|
|
|
3007
3179
|
}
|
|
3008
3180
|
|
|
3009
3181
|
// src/sync.ts
|
|
3010
|
-
import { existsSync as
|
|
3011
|
-
import { join as join9, dirname as
|
|
3012
|
-
import { homedir as
|
|
3182
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync8, chmodSync, cpSync, rmSync as rmSync2, symlinkSync as symlinkSync2, readlinkSync as readlinkSync2, appendFileSync } from "node:fs";
|
|
3183
|
+
import { join as join9, dirname as dirname3 } from "node:path";
|
|
3184
|
+
import { homedir as homedir8, platform as platform6 } from "node:os";
|
|
3013
3185
|
import { execSync as execSync3 } from "node:child_process";
|
|
3014
|
-
var CONARE_DIR = join9(
|
|
3186
|
+
var CONARE_DIR = join9(homedir8(), ".conare");
|
|
3015
3187
|
var BIN_DIR = join9(CONARE_DIR, "bin");
|
|
3016
3188
|
var CONFIG_PATH2 = join9(CONARE_DIR, "config.json");
|
|
3017
3189
|
var PLIST_LABEL = "ai.conare.ingest";
|
|
3018
|
-
var PLIST_PATH = join9(
|
|
3019
|
-
var SYSTEMD_DIR = join9(
|
|
3190
|
+
var PLIST_PATH = join9(homedir8(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
3191
|
+
var SYSTEMD_DIR = join9(homedir8(), ".config", "systemd", "user");
|
|
3020
3192
|
var SYSTEMD_SERVICE = join9(SYSTEMD_DIR, "conare-sync.service");
|
|
3021
3193
|
var SYSTEMD_TIMER = join9(SYSTEMD_DIR, "conare-sync.timer");
|
|
3022
3194
|
var TASK_NAME = "ConareMemorySync";
|
|
@@ -3161,7 +3333,7 @@ function persistBinary(apiKey) {
|
|
|
3161
3333
|
throw new Error("Could not locate CLI bundle. Run from an installed conare package.");
|
|
3162
3334
|
}
|
|
3163
3335
|
const dest = join9(BIN_DIR, "conare-ingest.mjs");
|
|
3164
|
-
const content =
|
|
3336
|
+
const content = readFileSync8(cliEntry, "utf-8");
|
|
3165
3337
|
writeFileSync4(dest, content);
|
|
3166
3338
|
const sqlJsDir = findSqlJs();
|
|
3167
3339
|
if (sqlJsDir) {
|
|
@@ -3184,17 +3356,17 @@ function persistBinary(apiKey) {
|
|
|
3184
3356
|
`, { mode: 384 });
|
|
3185
3357
|
}
|
|
3186
3358
|
function isValidJsBundle(path) {
|
|
3187
|
-
if (!
|
|
3359
|
+
if (!existsSync9(path))
|
|
3188
3360
|
return false;
|
|
3189
3361
|
if (path.endsWith(".ts") || path.endsWith(".tsx"))
|
|
3190
3362
|
return false;
|
|
3191
|
-
const head =
|
|
3363
|
+
const head = readFileSync8(path, "utf-8").slice(0, 2000);
|
|
3192
3364
|
if (/\btype\s+\{/.test(head) || /,\s*type\s+\w+/.test(head))
|
|
3193
3365
|
return false;
|
|
3194
3366
|
return true;
|
|
3195
3367
|
}
|
|
3196
3368
|
function findCliBundle() {
|
|
3197
|
-
const dir =
|
|
3369
|
+
const dir = dirname3(new URL(import.meta.url).pathname);
|
|
3198
3370
|
const distCandidates = [
|
|
3199
3371
|
join9(dir, "index.js"),
|
|
3200
3372
|
join9(dir, "..", "dist", "index.js")
|
|
@@ -3211,11 +3383,11 @@ function findCliBundle() {
|
|
|
3211
3383
|
function findSqlJs() {
|
|
3212
3384
|
const candidates = [
|
|
3213
3385
|
join9(process.cwd(), "node_modules", "sql.js"),
|
|
3214
|
-
join9(
|
|
3215
|
-
join9(
|
|
3386
|
+
join9(dirname3(new URL(import.meta.url).pathname), "..", "node_modules", "sql.js"),
|
|
3387
|
+
join9(dirname3(new URL(import.meta.url).pathname), "..", "..", "node_modules", "sql.js")
|
|
3216
3388
|
];
|
|
3217
3389
|
for (const c of candidates) {
|
|
3218
|
-
if (
|
|
3390
|
+
if (existsSync9(c))
|
|
3219
3391
|
return c;
|
|
3220
3392
|
}
|
|
3221
3393
|
return null;
|
|
@@ -3224,7 +3396,7 @@ function cleanupOldLaunchAgent() {
|
|
|
3224
3396
|
try {
|
|
3225
3397
|
execSync3(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
|
|
3226
3398
|
} catch {}
|
|
3227
|
-
if (
|
|
3399
|
+
if (existsSync9(PLIST_PATH)) {
|
|
3228
3400
|
unlinkSync(PLIST_PATH);
|
|
3229
3401
|
}
|
|
3230
3402
|
}
|
|
@@ -3253,7 +3425,7 @@ function clampCronInterval(intervalMinutes) {
|
|
|
3253
3425
|
}
|
|
3254
3426
|
function setupCron(intervalMinutes) {
|
|
3255
3427
|
const clamped = clampCronInterval(intervalMinutes);
|
|
3256
|
-
const cronCmd = `"${
|
|
3428
|
+
const cronCmd = `"${homedir8()}/.conare/bin/run.sh"`;
|
|
3257
3429
|
const cronLine = `*/${clamped} * * * * ${cronCmd} # conare-sync`;
|
|
3258
3430
|
try {
|
|
3259
3431
|
const existing = execSync3("crontab -l 2>/dev/null", { encoding: "utf-8" });
|
|
@@ -3296,13 +3468,13 @@ function runSyncNow() {
|
|
|
3296
3468
|
try {
|
|
3297
3469
|
if (os === "win32") {
|
|
3298
3470
|
const runCmd = join9(BIN_DIR, "run.cmd");
|
|
3299
|
-
if (
|
|
3471
|
+
if (existsSync9(runCmd)) {
|
|
3300
3472
|
execSync3(`"${runCmd}"`, { stdio: "ignore", timeout: 60000 });
|
|
3301
3473
|
return true;
|
|
3302
3474
|
}
|
|
3303
3475
|
} else {
|
|
3304
3476
|
const runSh = join9(BIN_DIR, "run.sh");
|
|
3305
|
-
if (
|
|
3477
|
+
if (existsSync9(runSh)) {
|
|
3306
3478
|
execSync3(`/bin/bash "${runSh}"`, { stdio: "ignore", timeout: 60000 });
|
|
3307
3479
|
return true;
|
|
3308
3480
|
}
|
|
@@ -3356,7 +3528,7 @@ exec "$NODE" "$CONARE_DIR/bin/conare-ingest.mjs" "$@"
|
|
|
3356
3528
|
chmodSync(wrapper, 493);
|
|
3357
3529
|
const symlinkTarget = "/usr/local/bin/conare";
|
|
3358
3530
|
try {
|
|
3359
|
-
if (
|
|
3531
|
+
if (existsSync9(symlinkTarget)) {
|
|
3360
3532
|
try {
|
|
3361
3533
|
const existing = readlinkSync2(symlinkTarget);
|
|
3362
3534
|
if (existing === wrapper)
|
|
@@ -3374,7 +3546,7 @@ exec "$NODE" "$CONARE_DIR/bin/conare-ingest.mjs" "$@"
|
|
|
3374
3546
|
const shellProfile = getShellProfile();
|
|
3375
3547
|
if (shellProfile) {
|
|
3376
3548
|
try {
|
|
3377
|
-
const profileContent =
|
|
3549
|
+
const profileContent = existsSync9(shellProfile) ? readFileSync8(shellProfile, "utf-8") : "";
|
|
3378
3550
|
const exportLine = `export PATH="$HOME/.conare/bin:$PATH"`;
|
|
3379
3551
|
if (!profileContent.includes(".conare/bin")) {
|
|
3380
3552
|
appendFileSync(shellProfile, `
|
|
@@ -3392,19 +3564,19 @@ ${exportLine}
|
|
|
3392
3564
|
}
|
|
3393
3565
|
}
|
|
3394
3566
|
function getShellProfile() {
|
|
3395
|
-
const home =
|
|
3567
|
+
const home = homedir8();
|
|
3396
3568
|
const shell = process.env.SHELL || "";
|
|
3397
3569
|
if (shell.includes("zsh"))
|
|
3398
3570
|
return join9(home, ".zshrc");
|
|
3399
3571
|
if (shell.includes("bash")) {
|
|
3400
3572
|
const profile = join9(home, ".bash_profile");
|
|
3401
|
-
if (platform6() === "darwin" &&
|
|
3573
|
+
if (platform6() === "darwin" && existsSync9(profile))
|
|
3402
3574
|
return profile;
|
|
3403
3575
|
return join9(home, ".bashrc");
|
|
3404
3576
|
}
|
|
3405
|
-
if (
|
|
3577
|
+
if (existsSync9(join9(home, ".zshrc")))
|
|
3406
3578
|
return join9(home, ".zshrc");
|
|
3407
|
-
if (
|
|
3579
|
+
if (existsSync9(join9(home, ".bashrc")))
|
|
3408
3580
|
return join9(home, ".bashrc");
|
|
3409
3581
|
return null;
|
|
3410
3582
|
}
|
|
@@ -3474,9 +3646,9 @@ function uninstallSync() {
|
|
|
3474
3646
|
try {
|
|
3475
3647
|
execSync3("systemctl --user disable --now conare-sync.timer 2>/dev/null", { stdio: "ignore" });
|
|
3476
3648
|
} catch {}
|
|
3477
|
-
if (
|
|
3649
|
+
if (existsSync9(SYSTEMD_SERVICE))
|
|
3478
3650
|
unlinkSync(SYSTEMD_SERVICE);
|
|
3479
|
-
if (
|
|
3651
|
+
if (existsSync9(SYSTEMD_TIMER))
|
|
3480
3652
|
unlinkSync(SYSTEMD_TIMER);
|
|
3481
3653
|
try {
|
|
3482
3654
|
execSync3("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
@@ -3487,15 +3659,15 @@ function uninstallSync() {
|
|
|
3487
3659
|
}
|
|
3488
3660
|
}
|
|
3489
3661
|
const configPath = join9(CONARE_DIR, "config.json");
|
|
3490
|
-
if (
|
|
3662
|
+
if (existsSync9(configPath))
|
|
3491
3663
|
unlinkSync(configPath);
|
|
3492
3664
|
const lockDir = join9(CONARE_DIR, "sync.lock.d");
|
|
3493
|
-
if (
|
|
3665
|
+
if (existsSync9(lockDir))
|
|
3494
3666
|
rmSync2(lockDir, { recursive: true, force: true });
|
|
3495
3667
|
const lockFile = join9(CONARE_DIR, "sync.lock");
|
|
3496
|
-
if (
|
|
3668
|
+
if (existsSync9(lockFile))
|
|
3497
3669
|
unlinkSync(lockFile);
|
|
3498
|
-
if (
|
|
3670
|
+
if (existsSync9(BIN_DIR)) {
|
|
3499
3671
|
rmSync2(BIN_DIR, { recursive: true, force: true });
|
|
3500
3672
|
messages.push("Removed ~/.conare/bin/");
|
|
3501
3673
|
}
|
|
@@ -3508,6 +3680,11 @@ function uninstallSync() {
|
|
|
3508
3680
|
// src/index.ts
|
|
3509
3681
|
init_interactive();
|
|
3510
3682
|
var CONARE_URL2 = "https://conare.ai";
|
|
3683
|
+
var CHAT_CONTAINER_LABELS = {
|
|
3684
|
+
"claude-chats": "Claude Code",
|
|
3685
|
+
"codex-chats": "Codex",
|
|
3686
|
+
"cursor-chats": "Cursor"
|
|
3687
|
+
};
|
|
3511
3688
|
function getManifestFingerprint(memory) {
|
|
3512
3689
|
const metadata = memory.metadata;
|
|
3513
3690
|
if (metadata?.dedupKey && metadata?.contentHash) {
|
|
@@ -3563,6 +3740,16 @@ function renderDiscoverySummary(discovered, _filtered, deduped) {
|
|
|
3563
3740
|
parts.push(`${deduped} already imported`);
|
|
3564
3741
|
return parts.join(", ");
|
|
3565
3742
|
}
|
|
3743
|
+
function renderSourceBreakdown(memories) {
|
|
3744
|
+
const counts = new Map;
|
|
3745
|
+
for (const memory of memories) {
|
|
3746
|
+
counts.set(memory.containerTag, (counts.get(memory.containerTag) || 0) + 1);
|
|
3747
|
+
}
|
|
3748
|
+
return [...counts.entries()].map(([tag, count]) => `${CHAT_CONTAINER_LABELS[tag] || tag}: ${count}`).join(", ");
|
|
3749
|
+
}
|
|
3750
|
+
function formatSessionCount(count, approximate) {
|
|
3751
|
+
return `${approximate ? "~" : ""}${count} sessions`;
|
|
3752
|
+
}
|
|
3566
3753
|
function parseArgs() {
|
|
3567
3754
|
const args = process.argv.slice(2);
|
|
3568
3755
|
let key = "";
|
|
@@ -3621,6 +3808,7 @@ conare — AI memory for your coding tools
|
|
|
3621
3808
|
Usage:
|
|
3622
3809
|
conare Interactive setup with browser auth
|
|
3623
3810
|
conare install Just install the MCP (all detected clients)
|
|
3811
|
+
conare logout Clear saved API key, sync timer, and local index history
|
|
3624
3812
|
conare --key <api_key> Index chat history (key optional with browser auth)
|
|
3625
3813
|
conare --key <api_key> --index [path] Index codebase
|
|
3626
3814
|
|
|
@@ -3713,16 +3901,36 @@ async function runInstall() {
|
|
|
3713
3901
|
console.log(" \x1B[32m✓\x1B[0m MCP installed. Restart your AI tool to connect.");
|
|
3714
3902
|
console.log("");
|
|
3715
3903
|
}
|
|
3904
|
+
async function runLogout() {
|
|
3905
|
+
const { unlinkSync: unlinkSync2, existsSync: existsSync11 } = await import("node:fs");
|
|
3906
|
+
const { join: join11 } = await import("node:path");
|
|
3907
|
+
const { homedir: homedir9 } = await import("node:os");
|
|
3908
|
+
const messages = uninstallSync();
|
|
3909
|
+
const manifestPath = join11(homedir9(), ".conare", "ingested.json");
|
|
3910
|
+
if (existsSync11(manifestPath)) {
|
|
3911
|
+
unlinkSync2(manifestPath);
|
|
3912
|
+
messages.push("Removed local index history");
|
|
3913
|
+
}
|
|
3914
|
+
console.log("");
|
|
3915
|
+
for (const msg of messages)
|
|
3916
|
+
console.log(` ${msg}`);
|
|
3917
|
+
console.log("");
|
|
3918
|
+
console.log(" \x1B[32m✓\x1B[0m Logged out. API key cleared from ~/.conare/.");
|
|
3919
|
+
console.log("");
|
|
3920
|
+
}
|
|
3716
3921
|
async function main() {
|
|
3717
3922
|
if (process.argv[2] === "install") {
|
|
3718
3923
|
return runInstall();
|
|
3719
3924
|
}
|
|
3925
|
+
if (process.argv[2] === "logout") {
|
|
3926
|
+
return runLogout();
|
|
3927
|
+
}
|
|
3720
3928
|
const opts = parseArgs();
|
|
3721
3929
|
let configFileKey;
|
|
3722
3930
|
if (opts.configFile) {
|
|
3723
3931
|
try {
|
|
3724
|
-
const { readFileSync:
|
|
3725
|
-
const raw = JSON.parse(
|
|
3932
|
+
const { readFileSync: readFileSync9 } = await import("node:fs");
|
|
3933
|
+
const raw = JSON.parse(readFileSync9(opts.configFile, "utf-8"));
|
|
3726
3934
|
configFileKey = raw.apiKey || raw.key;
|
|
3727
3935
|
if (!configFileKey) {
|
|
3728
3936
|
console.error(`Error: no apiKey/key found in ${opts.configFile}`);
|
|
@@ -3749,7 +3957,8 @@ async function main() {
|
|
|
3749
3957
|
label: target.label,
|
|
3750
3958
|
available: true,
|
|
3751
3959
|
recommended: target.defaultSelected,
|
|
3752
|
-
detectedCount: undefined
|
|
3960
|
+
detectedCount: undefined,
|
|
3961
|
+
detectedCountApproximate: undefined
|
|
3753
3962
|
}));
|
|
3754
3963
|
let interactiveMode = false;
|
|
3755
3964
|
if (shouldRunInteractive) {
|
|
@@ -3761,7 +3970,8 @@ async function main() {
|
|
|
3761
3970
|
label: target.label,
|
|
3762
3971
|
available: detected?.available,
|
|
3763
3972
|
recommended: target.defaultSelected && detected?.available !== false,
|
|
3764
|
-
detectedCount: detected?.sessionCount
|
|
3973
|
+
detectedCount: detected?.sessionCount,
|
|
3974
|
+
detectedCountApproximate: detected?.sessionCountApproximate
|
|
3765
3975
|
};
|
|
3766
3976
|
});
|
|
3767
3977
|
startSetup();
|
|
@@ -3841,7 +4051,7 @@ async function main() {
|
|
|
3841
4051
|
log("");
|
|
3842
4052
|
}
|
|
3843
4053
|
}
|
|
3844
|
-
if (!opts.wasmDir &&
|
|
4054
|
+
if (!opts.wasmDir && existsSync10(join10(process.cwd(), "node_modules", "sql.js"))) {
|
|
3845
4055
|
opts.wasmDir = join10(process.cwd(), "node_modules");
|
|
3846
4056
|
}
|
|
3847
4057
|
if (effectiveConfigOnly) {
|
|
@@ -3964,7 +4174,7 @@ Nothing new to index.`);
|
|
|
3964
4174
|
log("Detected AI tools:");
|
|
3965
4175
|
for (const t of tools) {
|
|
3966
4176
|
if (t.available) {
|
|
3967
|
-
log(` + ${t.name}${t.sessionCount || t.sessionCount === 0 ? ` — ${t.sessionCount}
|
|
4177
|
+
log(` + ${t.name}${t.sessionCount || t.sessionCount === 0 ? ` — ${formatSessionCount(t.sessionCount, t.sessionCountApproximate)}` : ""}`);
|
|
3968
4178
|
} else {
|
|
3969
4179
|
log(` - ${t.name} (not found)`);
|
|
3970
4180
|
}
|
|
@@ -4009,6 +4219,10 @@ Nothing new to index.`);
|
|
|
4009
4219
|
}
|
|
4010
4220
|
}
|
|
4011
4221
|
allMemories.sort((a, b3) => {
|
|
4222
|
+
const ta = a.metadata?.sourceTimestamp || a.updated_at || 0;
|
|
4223
|
+
const tb = b3.metadata?.sourceTimestamp || b3.updated_at || 0;
|
|
4224
|
+
if (ta !== tb)
|
|
4225
|
+
return tb - ta;
|
|
4012
4226
|
const da = a.metadata?.date || "0000";
|
|
4013
4227
|
const db = b3.metadata?.date || "0000";
|
|
4014
4228
|
return db.localeCompare(da);
|
|
@@ -4016,10 +4230,20 @@ Nothing new to index.`);
|
|
|
4016
4230
|
const billing = await getBillingStatus(apiKey);
|
|
4017
4231
|
if (billing && billing.plan === "free" && billing.limits.uploadedChats > 0) {
|
|
4018
4232
|
const chatLimit = billing.limits.uploadedChats;
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4233
|
+
const remoteChatCount = opts.dryRun ? 0 : await getRemoteChatMemoryCount(apiKey);
|
|
4234
|
+
const knownRemoteChatCount = remoteChatCount ?? billing.usage?.memories ?? 0;
|
|
4235
|
+
const remaining = Math.max(0, chatLimit - knownRemoteChatCount);
|
|
4236
|
+
if (allMemories.length > remaining) {
|
|
4237
|
+
const skipped = allMemories.length - remaining;
|
|
4238
|
+
allMemories.splice(remaining);
|
|
4239
|
+
if (remaining > 0) {
|
|
4240
|
+
const breakdown = renderSourceBreakdown(allMemories);
|
|
4241
|
+
log(`\x1B[33m⚠\x1B[0m Free plan: uploading ${remaining} newest remaining sessions (${knownRemoteChatCount}/${chatLimit} already imported; ${skipped} older sessions skipped)`);
|
|
4242
|
+
if (breakdown)
|
|
4243
|
+
log(` Selected now: ${breakdown}`);
|
|
4244
|
+
} else {
|
|
4245
|
+
log(`\x1B[33m⚠\x1B[0m Free plan: chat upload limit already reached (${knownRemoteChatCount}/${chatLimit}); ${skipped} new local sessions skipped`);
|
|
4246
|
+
}
|
|
4023
4247
|
log(` Upgrade to Pro for unlimited uploads → \x1B[4mhttps://conare.ai/pricing\x1B[0m`);
|
|
4024
4248
|
log();
|
|
4025
4249
|
}
|