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.
Files changed (2) hide show
  1. package/dist/index.js +699 -475
  2. 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: () => mod[key],
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: (newValue) => all[name] = () => newValue
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 as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "node:fs";
51
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
34
52
  import { createHash } from "node:crypto";
35
- import { join as join2 } from "node:path";
36
- import { homedir as homedir2 } from "node:os";
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 (existsSync2(MANIFEST_PATH)) {
90
- return JSON.parse(readFileSync2(MANIFEST_PATH, "utf-8"));
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 = join2(homedir2(), ".conare");
102
- if (!existsSync2(dir))
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 = join2(homedir2(), ".conare");
119
- if (!existsSync2(dir))
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 = join2(homedir2(), ".conare", "ingested.json");
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 readFileSync6, statSync as statSync2, existsSync as existsSync5 } from "node:fs";
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 (!existsSync5(gitignorePath))
170
+ if (!existsSync6(gitignorePath))
145
171
  return patterns;
146
172
  try {
147
- const content = readFileSync6(gitignorePath, "utf-8");
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(readFileSync6(join6(rootPath, "package.json"), "utf-8"));
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 = readFileSync6(join6(rootPath, "Cargo.toml"), "utf-8");
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 = readFileSync6(join6(rootPath, "pyproject.toml"), "utf-8");
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 = readFileSync6(fullPath, "utf-8");
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 existsSync9 } from "node:fs";
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, readFileSync } from "node:fs";
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 as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
2126
- import { join as join3, basename } from "node:path";
2127
- import { homedir as homedir3, platform as platform3 } from "node:os";
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 = platform3() === "win32";
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 = join3(resolved, candidate);
2147
- if (existsSync3(candidatePath)) {
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 = join3(resolved, segments[i]);
1949
+ resolved = join2(resolved, segments[i]);
2156
1950
  i++;
2157
1951
  }
2158
1952
  }
2159
- const home = homedir3();
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 (!date && obj.timestamp)
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 = join3(homedir3(), ".claude", "projects");
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 = readdirSync2(projectsDir);
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 = join3(projectsDir, projDir);
2085
+ const projPath = join2(projectsDir, projDir);
2247
2086
  const project = resolveProjectName(projDir);
2248
2087
  let files;
2249
2088
  try {
2250
- files = readdirSync2(projPath).filter((f) => f.endsWith(".jsonl"));
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 = readFileSync3(join3(projPath, file), "utf-8");
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 existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
2299
- import { join as join4, basename as basename2 } from "node:path";
2300
- import { homedir as homedir4 } from "node:os";
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 = homedir4();
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 = join4(homedir4(), ".codex", "sessions");
2323
- if (existsSync4(sessionsDir)) {
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 readdirSync3(dir, { withFileTypes: true })) {
2280
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
2336
2281
  if (entry.isDirectory()) {
2337
- walkCodexSessions(join4(dir, entry.name), memories, sessionIds, stats);
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 = readFileSync4(join4(dir, entry.name), "utf-8").split(`
2286
+ const lines = readFileSync3(join3(dir, entry.name), "utf-8").split(`
2342
2287
  `).filter(Boolean);
2343
- let date = null;
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 readFileSync5, statSync } from "node:fs";
2445
- import { join as join5 } from "node:path";
2446
- import { createRequire as createRequire3 } from "node:module";
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 = createRequire3(join5(wasmDir, "sql.js", "package.json"));
2349
+ const require2 = createRequire2(join4(wasmDir, "sql.js", "package.json"));
2454
2350
  return require2("sql.js");
2455
2351
  } else {
2456
- const require2 = createRequire3(import.meta.url);
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) => join5(wasmDir, "sql.js", "dist", file);
2374
+ locateOpts.locateFile = (file) => join4(wasmDir, "sql.js", "dist", file);
2467
2375
  }
2468
2376
  return initSqlJs(locateOpts).then((SQL) => {
2469
- const buffer = readFileSync5(dbPath);
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 initSqlJs = loadSqlJs(wasmDir);
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, wasmDir);
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 date = parsed.createdAt ? new Date(parsed.createdAt).toISOString().slice(0, 10) : "unknown";
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 existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2, symlinkSync, readlinkSync, rmSync } from "node:fs";
2599
- import { dirname, join as join7 } from "node:path";
2600
- import { homedir as homedir5, platform as platform5 } from "node:os";
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(readFileSync7(path, "utf-8"));
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(dirname(path), { recursive: true });
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
- if (existsSync6(join7(homedir5(), ".claude"))) {
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(homedir5(), ".codex", "config.toml");
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 = readFileSync7(configPath, "utf-8");
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(dirname(configPath), { recursive: true });
2853
+ mkdirSync2(dirname2(configPath), { recursive: true });
2693
2854
  writeFileSync2(configPath, result);
2694
- const oldMcpJson = join7(homedir5(), ".codex", "mcp.json");
2855
+ const oldMcpJson = join7(homedir6(), ".codex", "mcp.json");
2695
2856
  try {
2696
- if (existsSync6(oldMcpJson)) {
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(homedir5(), ".cursor", "mcp.json");
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(homedir5(), ".codeium", "windsurf", "mcp_config.json");
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(homedir5(), "Library", "Application Support", "Code", "User", "mcp.json");
2903
+ configPath = join7(homedir6(), "Library", "Application Support", "Code", "User", "mcp.json");
2743
2904
  } else if (os === "win32") {
2744
- configPath = join7(process.env.APPDATA || join7(homedir5(), "AppData", "Roaming"), "Code", "User", "mcp.json");
2905
+ configPath = join7(process.env.APPDATA || join7(homedir6(), "AppData", "Roaming"), "Code", "User", "mcp.json");
2745
2906
  } else {
2746
- configPath = join7(homedir5(), ".config", "Code", "User", "mcp.json");
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(homedir5(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
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(homedir5(), "AppData", "Roaming"), "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
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(homedir5(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
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(homedir5(), ".zed", "settings.json");
2947
+ configPath = join7(homedir6(), ".zed", "settings.json");
2787
2948
  } else {
2788
- configPath = join7(homedir5(), ".config", "zed", "settings.json");
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(homedir5(), ".openclaw", "openclaw.json");
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(homedir5(), ".gemini", "antigravity", "mcp_config.json");
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.2.0
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\` | "What did we do last week?" |
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
- | Narrative/summary question | \`search\` with \`deep: true\` | "What happened with the auth rewrite?" |
2877
- | Deep + specific format | \`search\` with \`deep\` + \`prompt\` | query="auth rewrite bug", prompt="timeline with dates" |
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
- ## Deep Mode
3043
+ **Two axes, always pair them:**
2880
3044
 
2881
- \`deep: true\` on \`search\` or \`recall\` returns an LLM-synthesized answer instead of raw memories.
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
- - **Use for**: narratives, summaries, "what happened with X", comparing approaches
2884
- - **Don't use for**: exact values, config lookups, file paths — raw preserves full detail
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\` (deep only) separates retrieval from synthesis. Keep \`query\` keyword-dense to find the right memories. Use \`prompt\` to instruct the LLM on format: "chronological timeline with dates", "only shipped changes, not proposals", "compare X vs Y". Omit \`prompt\` to let the LLM infer format from the query.
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(homedir5(), ".agents", "skills", "conare");
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(homedir5(), ".claude", "skills");
3118
+ const claudeSkillsDir = join7(homedir6(), ".claude", "skills");
2947
3119
  const claudeSkillDir = join7(claudeSkillsDir, "conare");
2948
3120
  try {
2949
- if (existsSync6(claudeSkillsDir)) {
2950
- if (existsSync6(claudeSkillDir)) {
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 existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
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 homedir6 } from "node:os";
2984
- var CONFIG_DIR = join8(homedir6(), ".conare");
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 (!existsSync7(CONFIG_PATH))
3160
+ if (!existsSync8(CONFIG_PATH))
2989
3161
  return {};
2990
- return JSON.parse(readFileSync8(CONFIG_PATH, "utf-8"));
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 existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync9, chmodSync, cpSync, rmSync as rmSync2, symlinkSync as symlinkSync2, readlinkSync as readlinkSync2, appendFileSync } from "node:fs";
3011
- import { join as join9, dirname as dirname2 } from "node:path";
3012
- import { homedir as homedir7, platform as platform6 } from "node:os";
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(homedir7(), ".conare");
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(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
3019
- var SYSTEMD_DIR = join9(homedir7(), ".config", "systemd", "user");
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 = readFileSync9(cliEntry, "utf-8");
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 (!existsSync8(path))
3359
+ if (!existsSync9(path))
3188
3360
  return false;
3189
3361
  if (path.endsWith(".ts") || path.endsWith(".tsx"))
3190
3362
  return false;
3191
- const head = readFileSync9(path, "utf-8").slice(0, 2000);
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 = dirname2(new URL(import.meta.url).pathname);
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(dirname2(new URL(import.meta.url).pathname), "..", "node_modules", "sql.js"),
3215
- join9(dirname2(new URL(import.meta.url).pathname), "..", "..", "node_modules", "sql.js")
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 (existsSync8(c))
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 (existsSync8(PLIST_PATH)) {
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 = `"${homedir7()}/.conare/bin/run.sh"`;
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 (existsSync8(runCmd)) {
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 (existsSync8(runSh)) {
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 (existsSync8(symlinkTarget)) {
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 = existsSync8(shellProfile) ? readFileSync9(shellProfile, "utf-8") : "";
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 = homedir7();
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" && existsSync8(profile))
3573
+ if (platform6() === "darwin" && existsSync9(profile))
3402
3574
  return profile;
3403
3575
  return join9(home, ".bashrc");
3404
3576
  }
3405
- if (existsSync8(join9(home, ".zshrc")))
3577
+ if (existsSync9(join9(home, ".zshrc")))
3406
3578
  return join9(home, ".zshrc");
3407
- if (existsSync8(join9(home, ".bashrc")))
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 (existsSync8(SYSTEMD_SERVICE))
3649
+ if (existsSync9(SYSTEMD_SERVICE))
3478
3650
  unlinkSync(SYSTEMD_SERVICE);
3479
- if (existsSync8(SYSTEMD_TIMER))
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 (existsSync8(configPath))
3662
+ if (existsSync9(configPath))
3491
3663
  unlinkSync(configPath);
3492
3664
  const lockDir = join9(CONARE_DIR, "sync.lock.d");
3493
- if (existsSync8(lockDir))
3665
+ if (existsSync9(lockDir))
3494
3666
  rmSync2(lockDir, { recursive: true, force: true });
3495
3667
  const lockFile = join9(CONARE_DIR, "sync.lock");
3496
- if (existsSync8(lockFile))
3668
+ if (existsSync9(lockFile))
3497
3669
  unlinkSync(lockFile);
3498
- if (existsSync8(BIN_DIR)) {
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: readFileSync10 } = await import("node:fs");
3725
- const raw = JSON.parse(readFileSync10(opts.configFile, "utf-8"));
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 && existsSync9(join10(process.cwd(), "node_modules", "sql.js"))) {
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} sessions` : ""}`);
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
- if (allMemories.length > chatLimit) {
4020
- const skipped = allMemories.length - chatLimit;
4021
- allMemories.splice(chatLimit);
4022
- log(`\x1B[33m⚠\x1B[0m Free plan: uploading ${chatLimit} most recent sessions (${skipped} older sessions skipped)`);
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
  }