conare 0.3.2 → 0.3.4

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 +896 -612
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -29,6 +29,584 @@ var __export = (target, all) => {
29
29
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
30
30
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
31
31
 
32
+ // src/ingest/shared.ts
33
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "node:fs";
34
+ import { createHash } from "node:crypto";
35
+ import { join as join2 } from "node:path";
36
+ import { homedir as homedir2 } from "node:os";
37
+ function isNarration(text) {
38
+ const stripped = text.trim();
39
+ if (stripped.length < MIN_SUBSTANTIVE)
40
+ return true;
41
+ if (stripped.length <= 300 && NARRATION_RE.test(stripped))
42
+ return true;
43
+ return false;
44
+ }
45
+ function cleanText(raw) {
46
+ let text = raw;
47
+ text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
48
+ text = text.replace(/<attached-context[\s\S]*?<\/attached-context>/g, "");
49
+ text = text.replace(/<environment_context>[\s\S]*?<\/environment_context>/g, "");
50
+ text = text.replace(/^\s*Base directory for this skill:[\s\S]*/g, "");
51
+ text = text.replace(/\nBase directory for this skill:[\s\S]*/g, "");
52
+ text = text.replace(/<!-- vibe-rules Integration -->[\s\S]*?<!-- \/vibe-rules Integration -->/g, "");
53
+ text = text.replace(/<good-behaviour>[\s\S]*?<\/good-behaviour>/g, "");
54
+ text = text.replace(/<frontend-design>[\s\S]*?<\/frontend-design>/g, "");
55
+ text = text.replace(/<architecture>[\s\S]*?<\/architecture>/g, "");
56
+ text = text.replace(/<task-notification>[\s\S]*?<\/task-notification>/g, "");
57
+ text = text.replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, "");
58
+ text = text.replace(/<system_instruction>[\s\S]*?<\/system_instruction>/g, "");
59
+ return text.trim();
60
+ }
61
+ function createContentHash(content) {
62
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
63
+ }
64
+ function getIngested() {
65
+ try {
66
+ if (existsSync2(MANIFEST_PATH)) {
67
+ return JSON.parse(readFileSync2(MANIFEST_PATH, "utf-8"));
68
+ }
69
+ } catch {}
70
+ return {};
71
+ }
72
+ function markIngested(source, sessionIds) {
73
+ const manifest = getIngested();
74
+ const existing = new Set(manifest[source] || []);
75
+ for (const id of sessionIds)
76
+ existing.add(id);
77
+ manifest[source] = [...existing];
78
+ const dir = join2(homedir2(), ".conare");
79
+ if (!existsSync2(dir))
80
+ mkdirSync(dir, { recursive: true });
81
+ writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
82
+ }
83
+ function isIngested(source, sessionId) {
84
+ const manifest = getIngested();
85
+ return (manifest[source] || []).includes(sessionId);
86
+ }
87
+ function clearIngested(source) {
88
+ const manifest = getIngested();
89
+ if (source) {
90
+ delete manifest[source];
91
+ } else {
92
+ for (const key of Object.keys(manifest))
93
+ delete manifest[key];
94
+ }
95
+ const dir = join2(homedir2(), ".conare");
96
+ if (!existsSync2(dir))
97
+ mkdirSync(dir, { recursive: true });
98
+ writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
99
+ }
100
+ var MANIFEST_PATH, MIN_SUBSTANTIVE = 200, NARRATION_RE;
101
+ var init_shared = __esm(() => {
102
+ MANIFEST_PATH = join2(homedir2(), ".conare", "ingested.json");
103
+ 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))/;
104
+ });
105
+
106
+ // src/ingest/codebase.ts
107
+ var exports_codebase = {};
108
+ __export(exports_codebase, {
109
+ indexCodebase: () => indexCodebase,
110
+ getRemoteFilePath: () => getRemoteFilePath,
111
+ getChangedFiles: () => getChangedFiles,
112
+ detectProjectName: () => detectProjectName
113
+ });
114
+ import { createHash as createHash2 } from "node:crypto";
115
+ import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync2, existsSync as existsSync5 } from "node:fs";
116
+ import { join as join6, relative, extname, resolve, basename as basename3 } from "node:path";
117
+ import { execSync } from "node:child_process";
118
+ function parseGitignore(rootPath) {
119
+ const patterns = new Set;
120
+ const gitignorePath = join6(rootPath, ".gitignore");
121
+ if (!existsSync5(gitignorePath))
122
+ return patterns;
123
+ try {
124
+ const content = readFileSync6(gitignorePath, "utf-8");
125
+ for (const line of content.split(`
126
+ `)) {
127
+ const trimmed = line.trim();
128
+ if (!trimmed || trimmed.startsWith("#"))
129
+ continue;
130
+ patterns.add(trimmed.replace(/\/$/, ""));
131
+ }
132
+ } catch {}
133
+ return patterns;
134
+ }
135
+ function shouldIgnore(name, ignorePatterns) {
136
+ if (DEFAULT_IGNORE.has(name))
137
+ return true;
138
+ if (IGNORE_FILES.has(name))
139
+ return true;
140
+ if (name.startsWith(".env"))
141
+ return true;
142
+ if (ignorePatterns.has(name))
143
+ return true;
144
+ return false;
145
+ }
146
+ function langFromExt(ext) {
147
+ return EXT_TO_LANG[ext] || ext.slice(1);
148
+ }
149
+ function formatFile(relPath, content, ext) {
150
+ const lang = langFromExt(ext);
151
+ return `# File: ${relPath}
152
+
153
+ \`\`\`${lang}
154
+ ${content}
155
+ \`\`\``;
156
+ }
157
+ function detectProjectName(rootPath) {
158
+ const readers = [
159
+ () => {
160
+ const pkg = JSON.parse(readFileSync6(join6(rootPath, "package.json"), "utf-8"));
161
+ return typeof pkg.name === "string" ? pkg.name : null;
162
+ },
163
+ () => {
164
+ const content = readFileSync6(join6(rootPath, "Cargo.toml"), "utf-8");
165
+ return content.match(/^name\s*=\s*"([^"]+)"/m)?.[1] ?? null;
166
+ },
167
+ () => {
168
+ const content = readFileSync6(join6(rootPath, "pyproject.toml"), "utf-8");
169
+ return content.match(/^name\s*=\s*"([^"]+)"/m)?.[1] ?? null;
170
+ }
171
+ ];
172
+ for (const reader of readers) {
173
+ try {
174
+ const name = reader();
175
+ if (name)
176
+ return name;
177
+ } catch {}
178
+ }
179
+ return basename3(resolve(rootPath));
180
+ }
181
+ function getChangedFiles(rootPath) {
182
+ try {
183
+ const committed = execSync("git diff --name-only HEAD 2>/dev/null || true", {
184
+ cwd: rootPath,
185
+ encoding: "utf-8",
186
+ stdio: ["pipe", "pipe", "pipe"]
187
+ }).trim();
188
+ const staged = execSync("git diff --name-only --cached 2>/dev/null || true", {
189
+ cwd: rootPath,
190
+ encoding: "utf-8",
191
+ stdio: ["pipe", "pipe", "pipe"]
192
+ }).trim();
193
+ const all = new Set;
194
+ for (const line of [...committed.split(`
195
+ `), ...staged.split(`
196
+ `)]) {
197
+ const f = line.trim();
198
+ if (f.length > 0)
199
+ all.add(f);
200
+ }
201
+ return [...all];
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+ function getRemoteFilePath(memory) {
207
+ const meta = memory.metadata;
208
+ if (!meta || typeof meta !== "object")
209
+ return null;
210
+ const fp = meta.filePath;
211
+ return typeof fp === "string" ? fp : null;
212
+ }
213
+ function indexCodebase(rootPath, options = {}) {
214
+ const absRoot = resolve(rootPath);
215
+ const repoHash = createHash2("sha256").update(absRoot).digest("hex").slice(0, 12);
216
+ const gitignorePatterns = parseGitignore(absRoot);
217
+ const memories = [];
218
+ const localFilePaths = [];
219
+ let fileCount = 0;
220
+ let skipped = 0;
221
+ const project = options.project || detectProjectName(absRoot);
222
+ const changedFiles = options.changedOnly ? getChangedFiles(absRoot) : null;
223
+ const changedSet = changedFiles ? new Set(changedFiles) : null;
224
+ if (options.changedOnly && changedSet !== null && changedSet.size === 0) {
225
+ return { memories, fileCount: 0, skipped: 0, localFilePaths: [], project };
226
+ }
227
+ function walk(dir) {
228
+ let entries;
229
+ try {
230
+ entries = readdirSync4(dir, { withFileTypes: true });
231
+ } catch {
232
+ return;
233
+ }
234
+ for (const entry of entries) {
235
+ if (shouldIgnore(entry.name, gitignorePatterns))
236
+ continue;
237
+ const fullPath = join6(dir, entry.name);
238
+ if (entry.isDirectory()) {
239
+ walk(fullPath);
240
+ continue;
241
+ }
242
+ if (!entry.isFile())
243
+ continue;
244
+ const ext = extname(entry.name).toLowerCase();
245
+ if (!CODE_EXTENSIONS.has(ext) && !SPECIAL_FILES.has(entry.name))
246
+ continue;
247
+ const relPath = relative(absRoot, fullPath);
248
+ localFilePaths.push(relPath);
249
+ if (changedSet && !changedSet.has(relPath)) {
250
+ skipped++;
251
+ continue;
252
+ }
253
+ let stat;
254
+ try {
255
+ stat = statSync2(fullPath);
256
+ } catch {
257
+ continue;
258
+ }
259
+ if (stat.size > MAX_FILE_SIZE || stat.size === 0) {
260
+ skipped++;
261
+ continue;
262
+ }
263
+ let raw;
264
+ try {
265
+ raw = readFileSync6(fullPath, "utf-8");
266
+ } catch {
267
+ skipped++;
268
+ continue;
269
+ }
270
+ if (raw.includes("\x00")) {
271
+ skipped++;
272
+ continue;
273
+ }
274
+ const contentHash = createContentHash(raw);
275
+ const dedupKey = `codebase:${repoHash}:${relPath}`;
276
+ const fingerprint = `${dedupKey}:${contentHash}`;
277
+ if (isIngested("codebase", fingerprint)) {
278
+ skipped++;
279
+ continue;
280
+ }
281
+ const content = formatFile(relPath, raw, ext);
282
+ const metadata = {
283
+ dedupKey,
284
+ contentHash,
285
+ source: "codebase-index",
286
+ repoHash,
287
+ filePath: relPath,
288
+ fileHash: `${relPath}:${contentHash}`,
289
+ language: langFromExt(ext),
290
+ project
291
+ };
292
+ memories.push({ content, containerTag: "codebase", metadata });
293
+ fileCount++;
294
+ }
295
+ }
296
+ walk(absRoot);
297
+ return { memories, fileCount, skipped, localFilePaths, project };
298
+ }
299
+ var DEFAULT_IGNORE, IGNORE_FILES, CODE_EXTENSIONS, SPECIAL_FILES, MAX_FILE_SIZE = 1e5, EXT_TO_LANG;
300
+ var init_codebase = __esm(() => {
301
+ init_shared();
302
+ DEFAULT_IGNORE = new Set([
303
+ "node_modules",
304
+ ".git",
305
+ ".next",
306
+ ".nuxt",
307
+ ".output",
308
+ "dist",
309
+ "build",
310
+ ".cache",
311
+ "__pycache__",
312
+ ".venv",
313
+ "venv",
314
+ "vendor",
315
+ "target",
316
+ ".turbo",
317
+ ".DS_Store",
318
+ ".claude",
319
+ ".conare"
320
+ ]);
321
+ IGNORE_FILES = new Set([
322
+ "package-lock.json",
323
+ "bun.lockb",
324
+ "yarn.lock",
325
+ "pnpm-lock.yaml"
326
+ ]);
327
+ CODE_EXTENSIONS = new Set([
328
+ ".ts",
329
+ ".tsx",
330
+ ".js",
331
+ ".jsx",
332
+ ".mjs",
333
+ ".cjs",
334
+ ".vue",
335
+ ".svelte",
336
+ ".astro",
337
+ ".py",
338
+ ".rb",
339
+ ".go",
340
+ ".rs",
341
+ ".java",
342
+ ".kt",
343
+ ".swift",
344
+ ".c",
345
+ ".cpp",
346
+ ".h",
347
+ ".css",
348
+ ".scss",
349
+ ".less",
350
+ ".html",
351
+ ".json",
352
+ ".yaml",
353
+ ".yml",
354
+ ".toml",
355
+ ".md",
356
+ ".mdx",
357
+ ".txt",
358
+ ".sql",
359
+ ".graphql",
360
+ ".prisma",
361
+ ".sh",
362
+ ".bash",
363
+ ".zsh",
364
+ ".lua",
365
+ ".zig",
366
+ ".ex",
367
+ ".exs"
368
+ ]);
369
+ SPECIAL_FILES = new Set([
370
+ "Dockerfile",
371
+ "Makefile",
372
+ "Procfile",
373
+ "Justfile",
374
+ ".gitignore",
375
+ ".dockerignore",
376
+ "CLAUDE.md",
377
+ "AGENTS.md",
378
+ "README.md",
379
+ "ARCHITECTURE.md",
380
+ "wrangler.toml",
381
+ "wrangler.json"
382
+ ]);
383
+ EXT_TO_LANG = {
384
+ ".ts": "typescript",
385
+ ".tsx": "tsx",
386
+ ".js": "javascript",
387
+ ".jsx": "jsx",
388
+ ".mjs": "javascript",
389
+ ".cjs": "javascript",
390
+ ".vue": "vue",
391
+ ".svelte": "svelte",
392
+ ".astro": "astro",
393
+ ".py": "python",
394
+ ".rb": "ruby",
395
+ ".go": "go",
396
+ ".rs": "rust",
397
+ ".java": "java",
398
+ ".kt": "kotlin",
399
+ ".swift": "swift",
400
+ ".c": "c",
401
+ ".cpp": "cpp",
402
+ ".h": "c",
403
+ ".css": "css",
404
+ ".scss": "scss",
405
+ ".less": "less",
406
+ ".html": "html",
407
+ ".json": "json",
408
+ ".yaml": "yaml",
409
+ ".yml": "yaml",
410
+ ".toml": "toml",
411
+ ".md": "markdown",
412
+ ".mdx": "mdx",
413
+ ".sql": "sql",
414
+ ".graphql": "graphql",
415
+ ".prisma": "prisma",
416
+ ".sh": "bash",
417
+ ".bash": "bash",
418
+ ".zsh": "zsh",
419
+ ".lua": "lua",
420
+ ".zig": "zig",
421
+ ".ex": "elixir",
422
+ ".exs": "elixir"
423
+ };
424
+ });
425
+
426
+ // src/api.ts
427
+ var exports_api = {};
428
+ __export(exports_api, {
429
+ validateKey: () => validateKey,
430
+ uploadBulk: () => uploadBulk,
431
+ listRemoteMemories: () => listRemoteMemories,
432
+ getRemoteMemoryCount: () => getRemoteMemoryCount,
433
+ deleteMemory: () => deleteMemory,
434
+ deleteMemories: () => deleteMemories,
435
+ createUploadBatches: () => createUploadBatches,
436
+ ApiError: () => ApiError
437
+ });
438
+ async function apiRequest(path, apiKey, init) {
439
+ const res = await fetch(`${API_URL}${path}`, {
440
+ ...init,
441
+ headers: {
442
+ Authorization: `Bearer ${apiKey}`,
443
+ "Content-Type": "application/json",
444
+ ...init?.headers || {}
445
+ }
446
+ });
447
+ if (!res.ok) {
448
+ const body = await res.text().catch(() => "");
449
+ throw new ApiError(`${path} failed: HTTP ${res.status}${body ? ` — ${body.slice(0, 200)}` : ""}`, res.status, path);
450
+ }
451
+ return res.json();
452
+ }
453
+ async function listRemoteMemories(apiKey, containerTag) {
454
+ const all = [];
455
+ let offset = 0;
456
+ while (true) {
457
+ const data = await apiRequest(`/api/memories?containerTag=${encodeURIComponent(containerTag)}&limit=${PAGE_SIZE}&offset=${offset}`, apiKey);
458
+ const page = data.memories || [];
459
+ all.push(...page);
460
+ if (page.length < PAGE_SIZE)
461
+ break;
462
+ offset += PAGE_SIZE;
463
+ }
464
+ return all;
465
+ }
466
+ async function deleteMemory(apiKey, memoryId) {
467
+ try {
468
+ await apiRequest(`/api/memories/${memoryId}`, apiKey, { method: "DELETE" });
469
+ return true;
470
+ } catch (err) {
471
+ if (err instanceof ApiError && err.statusCode === 404)
472
+ return false;
473
+ throw err;
474
+ }
475
+ }
476
+ async function deleteMemories(apiKey, memoryIds, onProgress) {
477
+ let deleted = 0;
478
+ const total = memoryIds.length;
479
+ for (let i = 0;i < total; i += DELETE_CONCURRENCY) {
480
+ const batch = memoryIds.slice(i, i + DELETE_CONCURRENCY);
481
+ const results = await Promise.all(batch.map((id) => deleteMemory(apiKey, id)));
482
+ deleted += results.filter(Boolean).length;
483
+ onProgress?.(deleted, total);
484
+ }
485
+ return deleted;
486
+ }
487
+ function createUploadBatches(memories, maxItems = 10, maxChars = 500000) {
488
+ const batches = [];
489
+ let current = [];
490
+ let currentChars = 0;
491
+ let startIndex = 0;
492
+ for (let i = 0;i < memories.length; i++) {
493
+ const item = memories[i];
494
+ const itemChars = item.content.length;
495
+ const exceedsBatch = current.length > 0 && (current.length >= maxItems || currentChars + itemChars > maxChars);
496
+ if (exceedsBatch) {
497
+ batches.push({ startIndex, items: current });
498
+ current = [];
499
+ currentChars = 0;
500
+ startIndex = i;
501
+ }
502
+ current.push(item);
503
+ currentChars += itemChars;
504
+ }
505
+ if (current.length > 0) {
506
+ batches.push({ startIndex, items: current });
507
+ }
508
+ return batches;
509
+ }
510
+ async function validateKey(apiKey) {
511
+ try {
512
+ const data = await apiRequest("/api/auth/me", apiKey);
513
+ return { valid: true, email: data.user.email, name: data.user.name };
514
+ } catch {
515
+ return { valid: false };
516
+ }
517
+ }
518
+ async function getRemoteMemoryCount(apiKey) {
519
+ try {
520
+ const data = await apiRequest("/api/containers", apiKey);
521
+ if (!Array.isArray(data.containers))
522
+ return 0;
523
+ return data.containers.reduce((sum, c) => sum + (c.count || 0), 0);
524
+ } catch {
525
+ return null;
526
+ }
527
+ }
528
+ async function uploadItems(apiKey, items, asyncEmbed = false) {
529
+ let retries = 4;
530
+ while (retries > 0) {
531
+ try {
532
+ const data = await apiRequest("/api/memories/bulk", apiKey, {
533
+ method: "POST",
534
+ body: JSON.stringify(asyncEmbed ? { items, async: true } : items)
535
+ });
536
+ if (!Array.isArray(data.results) || data.results.length !== items.length) {
537
+ throw new Error("Unexpected bulk upload response");
538
+ }
539
+ return data.results;
540
+ } catch (error) {
541
+ if (error instanceof ApiError && error.statusCode === 429) {
542
+ retries--;
543
+ await new Promise((r) => setTimeout(r, 5000));
544
+ continue;
545
+ }
546
+ retries--;
547
+ if (retries === 0) {
548
+ return items.map(() => ({
549
+ success: false,
550
+ error: error instanceof Error ? error.message : String(error)
551
+ }));
552
+ }
553
+ await new Promise((r) => setTimeout(r, 2000));
554
+ }
555
+ }
556
+ return items.map(() => ({ success: false, error: "Upload failed" }));
557
+ }
558
+ async function uploadBulk(apiKey, memories, onProgress) {
559
+ let success = 0;
560
+ let failed = 0;
561
+ const total = memories.length;
562
+ const results = [];
563
+ const batches = createUploadBatches(memories);
564
+ for (const batch of batches) {
565
+ let batchResults = await uploadItems(apiKey, batch.items, true);
566
+ if (batch.items.length > 1 && batchResults.some((result) => !result.success)) {
567
+ const retriedResults = [];
568
+ for (let i = 0;i < batch.items.length; i++) {
569
+ const result = batchResults[i];
570
+ if (result.success) {
571
+ retriedResults.push(result);
572
+ continue;
573
+ }
574
+ const [singleResult] = await uploadItems(apiKey, [batch.items[i]], true);
575
+ retriedResults.push(singleResult);
576
+ }
577
+ batchResults = retriedResults;
578
+ }
579
+ for (let i = 0;i < batchResults.length; i++) {
580
+ const result = batchResults[i];
581
+ results.push({
582
+ index: batch.startIndex + i,
583
+ success: result.success,
584
+ deduped: result.deduped,
585
+ error: result.error
586
+ });
587
+ if (result.success)
588
+ success++;
589
+ else
590
+ failed++;
591
+ }
592
+ onProgress?.(success + failed, total, failed);
593
+ }
594
+ return { success, failed, results };
595
+ }
596
+ var API_URL = "https://mcp.conare.ai", ApiError, PAGE_SIZE = 200, DELETE_CONCURRENCY = 5;
597
+ var init_api = __esm(() => {
598
+ ApiError = class ApiError extends Error {
599
+ statusCode;
600
+ endpoint;
601
+ constructor(message, statusCode, endpoint) {
602
+ super(message);
603
+ this.statusCode = statusCode;
604
+ this.endpoint = endpoint;
605
+ this.name = "ApiError";
606
+ }
607
+ };
608
+ });
609
+
32
610
  // node_modules/sisteransi/src/index.js
33
611
  var require_src = __commonJS((exports, module) => {
34
612
  var ESC = "\x1B";
@@ -446,7 +1024,7 @@ var import_sisteransi, import_picocolors, uD, W, tD, eD, FD = function() {
446
1024
  ` && (s && o && (F += z(s)), i && (F += K(i)));
447
1025
  }
448
1026
  return F;
449
- }, xD, B, AD, S, gD, vD = (e, u, t) => (u in e) ? gD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t, h = (e, u, t) => (vD(e, typeof u != "symbol" ? u + "" : u, t), t), dD, A, kD, $D = (e, u, t) => (u in e) ? kD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t, H = (e, u, t) => ($D(e, typeof u != "symbol" ? u + "" : u, t), t), SD, TD, jD = (e, u, t) => (u in e) ? TD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t, U = (e, u, t) => (jD(e, typeof u != "symbol" ? u + "" : u, t), t), MD;
1027
+ }, xD, B, AD, S, gD, vD = (e, u, t) => (u in e) ? gD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t, h = (e, u, t) => (vD(e, typeof u != "symbol" ? u + "" : u, t), t), dD, A, kD, $D = (e, u, t) => (u in e) ? kD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t, H = (e, u, t) => ($D(e, typeof u != "symbol" ? u + "" : u, t), t), SD, TD, jD = (e, u, t) => (u in e) ? TD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t, U = (e, u, t) => (jD(e, typeof u != "symbol" ? u + "" : u, t), t), MD, OD, PD = (e, u, t) => (u in e) ? OD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t, J = (e, u, t) => (PD(e, typeof u != "symbol" ? u + "" : u, t), t), LD;
450
1028
  var init_dist = __esm(() => {
451
1029
  import_sisteransi = __toESM(require_src(), 1);
452
1030
  import_picocolors = __toESM(require_picocolors(), 1);
@@ -570,6 +1148,30 @@ var init_dist = __esm(() => {
570
1148
  return this.value.replaceAll(/./g, this._mask);
571
1149
  }
572
1150
  };
1151
+ OD = Object.defineProperty;
1152
+ LD = class LD extends x {
1153
+ constructor(u) {
1154
+ super(u, false), J(this, "options"), J(this, "cursor", 0), this.options = u.options, this.cursor = this.options.findIndex(({ value: t }) => t === u.initialValue), this.cursor === -1 && (this.cursor = 0), this.changeValue(), this.on("cursor", (t) => {
1155
+ switch (t) {
1156
+ case "left":
1157
+ case "up":
1158
+ this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
1159
+ break;
1160
+ case "down":
1161
+ case "right":
1162
+ this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
1163
+ break;
1164
+ }
1165
+ this.changeValue();
1166
+ });
1167
+ }
1168
+ get _value() {
1169
+ return this.options[this.cursor];
1170
+ }
1171
+ changeValue() {
1172
+ this.value = this._value.value;
1173
+ }
1174
+ };
573
1175
  });
574
1176
 
575
1177
  // node_modules/@clack/prompts/dist/index.mjs
@@ -634,6 +1236,37 @@ ${import_picocolors2.default.gray(o)}`;
634
1236
  default:
635
1237
  return `${i}${import_picocolors2.default.cyan(o)} ${this.value ? `${import_picocolors2.default.green(k2)} ${n}` : `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(n)}`} ${import_picocolors2.default.dim("/")} ${this.value ? `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(r2)}` : `${import_picocolors2.default.green(k2)} ${r2}`}
636
1238
  ${import_picocolors2.default.cyan(d2)}
1239
+ `;
1240
+ }
1241
+ } }).prompt();
1242
+ }, ve = (t) => {
1243
+ const n = (r2, i) => {
1244
+ const s = r2.label ?? String(r2.value);
1245
+ switch (i) {
1246
+ case "selected":
1247
+ return `${import_picocolors2.default.dim(s)}`;
1248
+ case "active":
1249
+ return `${import_picocolors2.default.green(k2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}`;
1250
+ case "cancelled":
1251
+ return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}`;
1252
+ default:
1253
+ return `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(s)}`;
1254
+ }
1255
+ };
1256
+ return new LD({ options: t.options, initialValue: t.initialValue, render() {
1257
+ const r2 = `${import_picocolors2.default.gray(o)}
1258
+ ${b2(this.state)} ${t.message}
1259
+ `;
1260
+ switch (this.state) {
1261
+ case "submit":
1262
+ return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
1263
+ case "cancel":
1264
+ return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
1265
+ ${import_picocolors2.default.gray(o)}`;
1266
+ default:
1267
+ return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ cursor: this.cursor, options: this.options, maxItems: t.maxItems, style: (i, s) => n(i, s ? "active" : "inactive") }).join(`
1268
+ ${import_picocolors2.default.cyan(o)} `)}
1269
+ ${import_picocolors2.default.cyan(d2)}
637
1270
  `;
638
1271
  }
639
1272
  } }).prompt();
@@ -703,7 +1336,7 @@ ${import_picocolors2.default.gray(de + _2.repeat(s + 2) + pe)}
703
1336
  ${import_picocolors2.default.gray(d2)} ${t}
704
1337
 
705
1338
  `);
706
- }, J, Y2 = ({ indicator: t = "dots" } = {}) => {
1339
+ }, J2, Y2 = ({ indicator: t = "dots" } = {}) => {
707
1340
  const n = V2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"], r2 = V2 ? 80 : 120, i = process.env.CI === "true";
708
1341
  let s, c, a = false, l2 = "", $2, g2 = performance.now();
709
1342
  const p2 = (m2) => {
@@ -781,7 +1414,7 @@ var init_dist2 = __esm(() => {
781
1414
  D = u("◆", "*");
782
1415
  U2 = u("▲", "!");
783
1416
  K2 = u("■", "x");
784
- J = `${import_picocolors2.default.gray(o)} `;
1417
+ J2 = `${import_picocolors2.default.gray(o)} `;
785
1418
  });
786
1419
 
787
1420
  // src/interactive.ts
@@ -794,7 +1427,8 @@ __export(exports_interactive, {
794
1427
  selectChatSources: () => selectChatSources,
795
1428
  promptApiKey: () => promptApiKey,
796
1429
  finishSetup: () => finishSetup,
797
- confirmIndexCodebase: () => confirmIndexCodebase
1430
+ confirmIndexCodebase: () => confirmIndexCodebase,
1431
+ confirmBackgroundSync: () => confirmBackgroundSync
798
1432
  });
799
1433
  function formatDetectedCount(count) {
800
1434
  if (count === undefined)
@@ -866,6 +1500,17 @@ async function confirmIndexCodebase() {
866
1500
  initialValue: false
867
1501
  }));
868
1502
  }
1503
+ async function confirmBackgroundSync() {
1504
+ const value = ensureValue(await ve({
1505
+ message: "Auto-ingest new chats every 10 minutes?",
1506
+ initialValue: "yes",
1507
+ options: [
1508
+ { value: "yes", label: "Yes" },
1509
+ { value: "no", label: "No" }
1510
+ ]
1511
+ }));
1512
+ return value === "yes";
1513
+ }
869
1514
  var init_interactive = __esm(() => {
870
1515
  init_dist2();
871
1516
  });
@@ -951,128 +1596,55 @@ async function detect() {
951
1596
  const lines = readFileSync2(codexHistory, "utf-8").split(`
952
1597
  `).filter(Boolean);
953
1598
  const sessions = new Set(lines.map((l) => {
954
- try {
955
- return JSON.parse(l).session_id;
956
- } catch {
957
- return null;
958
- }
959
- }));
960
- sessions.delete(null);
961
- sessionCount = sessions.size;
962
- } catch {}
963
- }
964
- tools.push({
965
- name: "Codex",
966
- available: true,
967
- path: existsSync(codexHistory) ? codexHistory : codexSessions,
968
- sessionCount
969
- });
970
- } else {
971
- tools.push({ name: "Codex", available: false, path: codexHistory, sessionCount: 0 });
972
- }
973
- const os = platform();
974
- let cursorDbPath;
975
- if (os === "darwin") {
976
- cursorDbPath = join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
977
- } else if (os === "win32") {
978
- cursorDbPath = join(process.env.APPDATA || join(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
979
- } else {
980
- cursorDbPath = join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb");
981
- }
982
- tools.push({
983
- name: "Cursor",
984
- available: existsSync(cursorDbPath),
985
- path: cursorDbPath,
986
- sessionCount: existsSync(cursorDbPath) ? await countCursorSessions(cursorDbPath) : 0
987
- });
988
- const openclawDir = join(home, ".openclaw");
989
- tools.push({
990
- name: "OpenClaw",
991
- available: existsSync(openclawDir),
992
- path: openclawDir,
993
- sessionCount: 0
994
- });
995
- return tools;
996
- }
997
-
998
- // src/ingest/claude.ts
999
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
1000
- import { join as join3, basename } from "node:path";
1001
- import { homedir as homedir3, platform as platform2 } from "node:os";
1002
-
1003
- // src/ingest/shared.ts
1004
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "node:fs";
1005
- import { createHash } from "node:crypto";
1006
- import { join as join2 } from "node:path";
1007
- import { homedir as homedir2 } from "node:os";
1008
- var MANIFEST_PATH = join2(homedir2(), ".conare", "ingested.json");
1009
- var MIN_SUBSTANTIVE = 200;
1010
- var 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))/;
1011
- function isNarration(text) {
1012
- const stripped = text.trim();
1013
- if (stripped.length < MIN_SUBSTANTIVE)
1014
- return true;
1015
- if (stripped.length <= 300 && NARRATION_RE.test(stripped))
1016
- return true;
1017
- return false;
1018
- }
1019
- function cleanText(raw) {
1020
- let text = raw;
1021
- text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
1022
- text = text.replace(/<attached-context[\s\S]*?<\/attached-context>/g, "");
1023
- text = text.replace(/<environment_context>[\s\S]*?<\/environment_context>/g, "");
1024
- text = text.replace(/^\s*Base directory for this skill:[\s\S]*/g, "");
1025
- text = text.replace(/\nBase directory for this skill:[\s\S]*/g, "");
1026
- text = text.replace(/<!-- vibe-rules Integration -->[\s\S]*?<!-- \/vibe-rules Integration -->/g, "");
1027
- text = text.replace(/<good-behaviour>[\s\S]*?<\/good-behaviour>/g, "");
1028
- text = text.replace(/<frontend-design>[\s\S]*?<\/frontend-design>/g, "");
1029
- text = text.replace(/<architecture>[\s\S]*?<\/architecture>/g, "");
1030
- text = text.replace(/<task-notification>[\s\S]*?<\/task-notification>/g, "");
1031
- text = text.replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, "");
1032
- text = text.replace(/<system_instruction>[\s\S]*?<\/system_instruction>/g, "");
1033
- return text.trim();
1034
- }
1035
- function createContentHash(content) {
1036
- return createHash("sha256").update(content).digest("hex").slice(0, 16);
1037
- }
1038
- function getIngested() {
1039
- try {
1040
- if (existsSync2(MANIFEST_PATH)) {
1041
- return JSON.parse(readFileSync2(MANIFEST_PATH, "utf-8"));
1599
+ try {
1600
+ return JSON.parse(l).session_id;
1601
+ } catch {
1602
+ return null;
1603
+ }
1604
+ }));
1605
+ sessions.delete(null);
1606
+ sessionCount = sessions.size;
1607
+ } catch {}
1042
1608
  }
1043
- } catch {}
1044
- return {};
1045
- }
1046
- function markIngested(source, sessionIds) {
1047
- const manifest = getIngested();
1048
- const existing = new Set(manifest[source] || []);
1049
- for (const id of sessionIds)
1050
- existing.add(id);
1051
- manifest[source] = [...existing];
1052
- const dir = join2(homedir2(), ".conare");
1053
- if (!existsSync2(dir))
1054
- mkdirSync(dir, { recursive: true });
1055
- writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
1056
- }
1057
- function isIngested(source, sessionId) {
1058
- const manifest = getIngested();
1059
- return (manifest[source] || []).includes(sessionId);
1060
- }
1061
- function clearIngested(source) {
1062
- const manifest = getIngested();
1063
- if (source) {
1064
- delete manifest[source];
1609
+ tools.push({
1610
+ name: "Codex",
1611
+ available: true,
1612
+ path: existsSync(codexHistory) ? codexHistory : codexSessions,
1613
+ sessionCount
1614
+ });
1065
1615
  } else {
1066
- for (const key of Object.keys(manifest))
1067
- delete manifest[key];
1616
+ tools.push({ name: "Codex", available: false, path: codexHistory, sessionCount: 0 });
1068
1617
  }
1069
- const dir = join2(homedir2(), ".conare");
1070
- if (!existsSync2(dir))
1071
- mkdirSync(dir, { recursive: true });
1072
- writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
1618
+ const os = platform();
1619
+ let cursorDbPath;
1620
+ if (os === "darwin") {
1621
+ cursorDbPath = join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
1622
+ } else if (os === "win32") {
1623
+ cursorDbPath = join(process.env.APPDATA || join(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
1624
+ } else {
1625
+ cursorDbPath = join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb");
1626
+ }
1627
+ tools.push({
1628
+ name: "Cursor",
1629
+ available: existsSync(cursorDbPath),
1630
+ path: cursorDbPath,
1631
+ sessionCount: existsSync(cursorDbPath) ? await countCursorSessions(cursorDbPath) : 0
1632
+ });
1633
+ const openclawDir = join(home, ".openclaw");
1634
+ tools.push({
1635
+ name: "OpenClaw",
1636
+ available: existsSync(openclawDir),
1637
+ path: openclawDir,
1638
+ sessionCount: 0
1639
+ });
1640
+ return tools;
1073
1641
  }
1074
1642
 
1075
1643
  // src/ingest/claude.ts
1644
+ init_shared();
1645
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
1646
+ import { join as join3, basename } from "node:path";
1647
+ import { homedir as homedir3, platform as platform2 } from "node:os";
1076
1648
  var MAX_CONTENT = 48000;
1077
1649
  var MIN_TURN_LEN = 50;
1078
1650
  function resolveProjectName(dirName) {
@@ -1236,6 +1808,7 @@ ${body}`;
1236
1808
  }
1237
1809
 
1238
1810
  // src/ingest/codex.ts
1811
+ init_shared();
1239
1812
  import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
1240
1813
  import { join as join4, basename as basename2 } from "node:path";
1241
1814
  import { homedir as homedir4 } from "node:os";
@@ -1393,6 +1966,7 @@ ${body}`;
1393
1966
  }
1394
1967
 
1395
1968
  // src/ingest/cursor.ts
1969
+ init_shared();
1396
1970
  import { readFileSync as readFileSync5, statSync } from "node:fs";
1397
1971
  import { join as join5 } from "node:path";
1398
1972
  import { createRequire as createRequire3 } from "node:module";
@@ -1460,476 +2034,107 @@ async function ingestCursor(dbPath, wasmDir) {
1460
2034
  console.log(" Skipping Cursor: database not accessible");
1461
2035
  return { memories, sessionIds, skipped: 0, filtered, deduped };
1462
2036
  }
1463
- if (fileSize > MAX_DB_SIZE) {
1464
- console.log(` Skipping Cursor: database too large (${(fileSize / 1024 / 1024 / 1024).toFixed(1)}GB)`);
1465
- return { memories, sessionIds, skipped: 0, filtered, deduped };
1466
- }
1467
- if (fileSize > WARN_DB_SIZE) {
1468
- console.log(` Warning: large database (${(fileSize / 1024 / 1024).toFixed(0)}MB), loading into memory...`);
1469
- }
1470
- const initSqlJs = loadSqlJs(wasmDir);
1471
- if (!initSqlJs) {
1472
- console.log(" Skipping Cursor: sql.js not available");
1473
- return { memories, sessionIds, skipped: 0, filtered, deduped };
1474
- }
1475
- let db;
1476
- try {
1477
- db = await openDb(initSqlJs, dbPath, wasmDir);
1478
- } catch (e) {
1479
- console.log(` Skipping Cursor: cannot open database (${e.message})`);
1480
- return { memories, sessionIds, skipped: 0, filtered, deduped };
1481
- }
1482
- try {
1483
- let rows = [];
1484
- try {
1485
- const results = db.exec("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'");
1486
- if (results.length > 0)
1487
- rows = results[0].values;
1488
- } catch {}
1489
- for (const [key, value] of rows) {
1490
- const composerId = key.replace("composerData:", "");
1491
- let parsed;
1492
- try {
1493
- parsed = JSON.parse(value);
1494
- if (!parsed || typeof parsed !== "object") {
1495
- filtered++;
1496
- continue;
1497
- }
1498
- } catch {
1499
- filtered++;
1500
- continue;
1501
- }
1502
- const bubbleHeaders = parsed.fullConversationHeadersOnly;
1503
- if (!Array.isArray(bubbleHeaders) || bubbleHeaders.length === 0) {
1504
- filtered++;
1505
- continue;
1506
- }
1507
- const turns = extractTurns(db, composerId, bubbleHeaders);
1508
- if (turns.length === 0) {
1509
- filtered++;
1510
- continue;
1511
- }
1512
- const sessionName = parsed.name || "Cursor Chat";
1513
- const date = parsed.createdAt ? new Date(parsed.createdAt).toISOString().slice(0, 10) : "unknown";
1514
- const header = `# ${sessionName} | ${date}`;
1515
- const body = turns.map((t) => {
1516
- return `## Q: ${t.user}
1517
-
1518
- ${t.assistant}`;
1519
- }).join(`
1520
-
1521
- ---
1522
-
1523
- `);
1524
- let content = `${header}
1525
-
1526
- ${body}`;
1527
- if (content.length > MAX_CONTENT3)
1528
- content = content.slice(0, MAX_CONTENT3) + `
1529
-
1530
- [truncated]`;
1531
- const contentHash = createContentHash(content);
1532
- const dedupKey = `cursor:${composerId}`;
1533
- const fingerprint = `${dedupKey}:${contentHash}`;
1534
- if (isIngested("cursor", fingerprint)) {
1535
- deduped++;
1536
- continue;
1537
- }
1538
- memories.push({
1539
- content,
1540
- containerTag: "cursor-chats",
1541
- metadata: {
1542
- dedupKey,
1543
- contentHash,
1544
- source: "cursor",
1545
- sessionId: composerId,
1546
- name: sessionName,
1547
- date
1548
- }
1549
- });
1550
- sessionIds.push(composerId);
1551
- }
1552
- } catch (e) {
1553
- console.log(` Cursor ingestion error: ${e.message}`);
1554
- } finally {
1555
- db.close();
1556
- }
1557
- return { memories, sessionIds, skipped: filtered + deduped, filtered, deduped };
1558
- }
1559
-
1560
- // src/ingest/codebase.ts
1561
- import { createHash as createHash2 } from "node:crypto";
1562
- import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync2, existsSync as existsSync5 } from "node:fs";
1563
- import { join as join6, relative, extname, resolve } from "node:path";
1564
- var DEFAULT_IGNORE = new Set([
1565
- "node_modules",
1566
- ".git",
1567
- ".next",
1568
- ".nuxt",
1569
- ".output",
1570
- "dist",
1571
- "build",
1572
- ".cache",
1573
- "__pycache__",
1574
- ".venv",
1575
- "venv",
1576
- "vendor",
1577
- "target",
1578
- ".turbo",
1579
- ".DS_Store",
1580
- ".claude",
1581
- ".conare"
1582
- ]);
1583
- var IGNORE_FILES = new Set([
1584
- "package-lock.json",
1585
- "bun.lockb",
1586
- "yarn.lock",
1587
- "pnpm-lock.yaml"
1588
- ]);
1589
- var CODE_EXTENSIONS = new Set([
1590
- ".ts",
1591
- ".tsx",
1592
- ".js",
1593
- ".jsx",
1594
- ".mjs",
1595
- ".cjs",
1596
- ".vue",
1597
- ".svelte",
1598
- ".astro",
1599
- ".py",
1600
- ".rb",
1601
- ".go",
1602
- ".rs",
1603
- ".java",
1604
- ".kt",
1605
- ".swift",
1606
- ".c",
1607
- ".cpp",
1608
- ".h",
1609
- ".css",
1610
- ".scss",
1611
- ".less",
1612
- ".html",
1613
- ".json",
1614
- ".yaml",
1615
- ".yml",
1616
- ".toml",
1617
- ".md",
1618
- ".mdx",
1619
- ".txt",
1620
- ".sql",
1621
- ".graphql",
1622
- ".prisma",
1623
- ".sh",
1624
- ".bash",
1625
- ".zsh",
1626
- ".lua",
1627
- ".zig",
1628
- ".ex",
1629
- ".exs"
1630
- ]);
1631
- var SPECIAL_FILES = new Set([
1632
- "Dockerfile",
1633
- "Makefile",
1634
- "Procfile",
1635
- "Justfile",
1636
- ".gitignore",
1637
- ".dockerignore",
1638
- "CLAUDE.md",
1639
- "AGENTS.md",
1640
- "README.md",
1641
- "ARCHITECTURE.md",
1642
- "wrangler.toml",
1643
- "wrangler.json"
1644
- ]);
1645
- var MAX_FILE_SIZE = 1e5;
1646
- function parseGitignore(rootPath) {
1647
- const patterns = new Set;
1648
- const gitignorePath = join6(rootPath, ".gitignore");
1649
- if (!existsSync5(gitignorePath))
1650
- return patterns;
1651
- try {
1652
- const content = readFileSync6(gitignorePath, "utf-8");
1653
- for (const line of content.split(`
1654
- `)) {
1655
- const trimmed = line.trim();
1656
- if (!trimmed || trimmed.startsWith("#"))
1657
- continue;
1658
- patterns.add(trimmed.replace(/\/$/, ""));
1659
- }
1660
- } catch {}
1661
- return patterns;
1662
- }
1663
- function shouldIgnore(name, ignorePatterns) {
1664
- if (DEFAULT_IGNORE.has(name))
1665
- return true;
1666
- if (IGNORE_FILES.has(name))
1667
- return true;
1668
- if (name.startsWith(".env"))
1669
- return true;
1670
- if (ignorePatterns.has(name))
1671
- return true;
1672
- return false;
1673
- }
1674
- function langFromExt(ext) {
1675
- const map = {
1676
- ".ts": "typescript",
1677
- ".tsx": "tsx",
1678
- ".js": "javascript",
1679
- ".jsx": "jsx",
1680
- ".mjs": "javascript",
1681
- ".cjs": "javascript",
1682
- ".vue": "vue",
1683
- ".svelte": "svelte",
1684
- ".astro": "astro",
1685
- ".py": "python",
1686
- ".rb": "ruby",
1687
- ".go": "go",
1688
- ".rs": "rust",
1689
- ".java": "java",
1690
- ".kt": "kotlin",
1691
- ".swift": "swift",
1692
- ".c": "c",
1693
- ".cpp": "cpp",
1694
- ".h": "c",
1695
- ".css": "css",
1696
- ".scss": "scss",
1697
- ".less": "less",
1698
- ".html": "html",
1699
- ".json": "json",
1700
- ".yaml": "yaml",
1701
- ".yml": "yaml",
1702
- ".toml": "toml",
1703
- ".md": "markdown",
1704
- ".mdx": "mdx",
1705
- ".sql": "sql",
1706
- ".graphql": "graphql",
1707
- ".prisma": "prisma",
1708
- ".sh": "bash",
1709
- ".bash": "bash",
1710
- ".zsh": "zsh",
1711
- ".lua": "lua",
1712
- ".zig": "zig",
1713
- ".ex": "elixir",
1714
- ".exs": "elixir"
1715
- };
1716
- return map[ext] || ext.slice(1);
1717
- }
1718
- function formatFile(relPath, content, ext) {
1719
- const lang = langFromExt(ext);
1720
- return `# File: ${relPath}
1721
-
1722
- \`\`\`${lang}
1723
- ${content}
1724
- \`\`\``;
1725
- }
1726
- function indexCodebase(rootPath) {
1727
- const absRoot = resolve(rootPath);
1728
- const repoHash = createHash2("sha256").update(absRoot).digest("hex").slice(0, 12);
1729
- const gitignorePatterns = parseGitignore(absRoot);
1730
- const memories = [];
1731
- let fileCount = 0;
1732
- let skipped = 0;
1733
- function walk(dir) {
1734
- let entries;
2037
+ if (fileSize > MAX_DB_SIZE) {
2038
+ console.log(` Skipping Cursor: database too large (${(fileSize / 1024 / 1024 / 1024).toFixed(1)}GB)`);
2039
+ return { memories, sessionIds, skipped: 0, filtered, deduped };
2040
+ }
2041
+ if (fileSize > WARN_DB_SIZE) {
2042
+ console.log(` Warning: large database (${(fileSize / 1024 / 1024).toFixed(0)}MB), loading into memory...`);
2043
+ }
2044
+ const initSqlJs = loadSqlJs(wasmDir);
2045
+ if (!initSqlJs) {
2046
+ console.log(" Skipping Cursor: sql.js not available");
2047
+ return { memories, sessionIds, skipped: 0, filtered, deduped };
2048
+ }
2049
+ let db;
2050
+ try {
2051
+ db = await openDb(initSqlJs, dbPath, wasmDir);
2052
+ } catch (e) {
2053
+ console.log(` Skipping Cursor: cannot open database (${e.message})`);
2054
+ return { memories, sessionIds, skipped: 0, filtered, deduped };
2055
+ }
2056
+ try {
2057
+ let rows = [];
1735
2058
  try {
1736
- entries = readdirSync4(dir, { withFileTypes: true });
1737
- } catch {
1738
- return;
1739
- }
1740
- for (const entry of entries) {
1741
- if (shouldIgnore(entry.name, gitignorePatterns))
1742
- continue;
1743
- const fullPath = join6(dir, entry.name);
1744
- if (entry.isDirectory()) {
1745
- walk(fullPath);
1746
- continue;
1747
- }
1748
- if (!entry.isFile())
1749
- continue;
1750
- const ext = extname(entry.name).toLowerCase();
1751
- if (!CODE_EXTENSIONS.has(ext) && !SPECIAL_FILES.has(entry.name))
1752
- continue;
1753
- let stat;
2059
+ const results = db.exec("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'");
2060
+ if (results.length > 0)
2061
+ rows = results[0].values;
2062
+ } catch {}
2063
+ for (const [key, value] of rows) {
2064
+ const composerId = key.replace("composerData:", "");
2065
+ let parsed;
1754
2066
  try {
1755
- stat = statSync2(fullPath);
2067
+ parsed = JSON.parse(value);
2068
+ if (!parsed || typeof parsed !== "object") {
2069
+ filtered++;
2070
+ continue;
2071
+ }
1756
2072
  } catch {
2073
+ filtered++;
1757
2074
  continue;
1758
2075
  }
1759
- if (stat.size > MAX_FILE_SIZE || stat.size === 0) {
1760
- skipped++;
1761
- continue;
1762
- }
1763
- let raw;
1764
- try {
1765
- raw = readFileSync6(fullPath, "utf-8");
1766
- } catch {
1767
- skipped++;
2076
+ const bubbleHeaders = parsed.fullConversationHeadersOnly;
2077
+ if (!Array.isArray(bubbleHeaders) || bubbleHeaders.length === 0) {
2078
+ filtered++;
1768
2079
  continue;
1769
2080
  }
1770
- if (raw.includes("\x00")) {
1771
- skipped++;
2081
+ const turns = extractTurns(db, composerId, bubbleHeaders);
2082
+ if (turns.length === 0) {
2083
+ filtered++;
1772
2084
  continue;
1773
2085
  }
1774
- const relPath = relative(absRoot, fullPath);
1775
- const contentHash = createContentHash(raw);
1776
- const dedupKey = `codebase:${repoHash}:${relPath}`;
2086
+ const sessionName = parsed.name || "Cursor Chat";
2087
+ const date = parsed.createdAt ? new Date(parsed.createdAt).toISOString().slice(0, 10) : "unknown";
2088
+ const header = `# ${sessionName} | ${date}`;
2089
+ const body = turns.map((t) => {
2090
+ return `## Q: ${t.user}
2091
+
2092
+ ${t.assistant}`;
2093
+ }).join(`
2094
+
2095
+ ---
2096
+
2097
+ `);
2098
+ let content = `${header}
2099
+
2100
+ ${body}`;
2101
+ if (content.length > MAX_CONTENT3)
2102
+ content = content.slice(0, MAX_CONTENT3) + `
2103
+
2104
+ [truncated]`;
2105
+ const contentHash = createContentHash(content);
2106
+ const dedupKey = `cursor:${composerId}`;
1777
2107
  const fingerprint = `${dedupKey}:${contentHash}`;
1778
- if (isIngested("codebase", fingerprint)) {
1779
- skipped++;
2108
+ if (isIngested("cursor", fingerprint)) {
2109
+ deduped++;
1780
2110
  continue;
1781
2111
  }
1782
- const content = formatFile(relPath, raw, ext);
1783
2112
  memories.push({
1784
2113
  content,
1785
- containerTag: "codebase",
2114
+ containerTag: "cursor-chats",
1786
2115
  metadata: {
1787
2116
  dedupKey,
1788
2117
  contentHash,
1789
- source: "codebase-index",
1790
- repoHash,
1791
- filePath: relPath,
1792
- fileHash: `${relPath}:${contentHash}`,
1793
- language: langFromExt(ext)
2118
+ source: "cursor",
2119
+ sessionId: composerId,
2120
+ name: sessionName,
2121
+ date
1794
2122
  }
1795
2123
  });
1796
- fileCount++;
2124
+ sessionIds.push(composerId);
1797
2125
  }
2126
+ } catch (e) {
2127
+ console.log(` Cursor ingestion error: ${e.message}`);
2128
+ } finally {
2129
+ db.close();
1798
2130
  }
1799
- walk(absRoot);
1800
- return { memories, fileCount, skipped };
2131
+ return { memories, sessionIds, skipped: filtered + deduped, filtered, deduped };
1801
2132
  }
1802
2133
 
1803
- // src/api.ts
1804
- var API_URL = "https://mcp.conare.ai";
1805
- function createUploadBatches(memories, maxItems = 50, maxChars = 1500000) {
1806
- const batches = [];
1807
- let current = [];
1808
- let currentChars = 0;
1809
- let startIndex = 0;
1810
- for (let i = 0;i < memories.length; i++) {
1811
- const item = memories[i];
1812
- const itemChars = item.content.length;
1813
- const exceedsBatch = current.length > 0 && (current.length >= maxItems || currentChars + itemChars > maxChars);
1814
- if (exceedsBatch) {
1815
- batches.push({ startIndex, items: current });
1816
- current = [];
1817
- currentChars = 0;
1818
- startIndex = i;
1819
- }
1820
- current.push(item);
1821
- currentChars += itemChars;
1822
- }
1823
- if (current.length > 0) {
1824
- batches.push({ startIndex, items: current });
1825
- }
1826
- return batches;
1827
- }
1828
- async function validateKey(apiKey) {
1829
- try {
1830
- const res = await fetch(`${API_URL}/api/auth/me`, {
1831
- headers: { Authorization: `Bearer ${apiKey}` }
1832
- });
1833
- if (!res.ok)
1834
- return { valid: false };
1835
- const data = await res.json();
1836
- return { valid: true, email: data.user.email, name: data.user.name };
1837
- } catch {
1838
- return { valid: false };
1839
- }
1840
- }
1841
- async function getRemoteMemoryCount(apiKey) {
1842
- try {
1843
- const res = await fetch(`${API_URL}/api/containers`, {
1844
- headers: { Authorization: `Bearer ${apiKey}` }
1845
- });
1846
- if (!res.ok)
1847
- return null;
1848
- const data = await res.json();
1849
- if (!Array.isArray(data.containers))
1850
- return 0;
1851
- return data.containers.reduce((sum, container) => sum + (container.count || 0), 0);
1852
- } catch {
1853
- return null;
1854
- }
1855
- }
1856
- async function uploadItems(apiKey, items, asyncEmbed = false) {
1857
- let retries = 4;
1858
- while (retries > 0) {
1859
- try {
1860
- const res = await fetch(`${API_URL}/api/memories/bulk`, {
1861
- method: "POST",
1862
- headers: {
1863
- "Content-Type": "application/json",
1864
- Authorization: `Bearer ${apiKey}`
1865
- },
1866
- body: JSON.stringify(asyncEmbed ? { items, async: true } : items)
1867
- });
1868
- if (res.status === 429) {
1869
- retries--;
1870
- await new Promise((r) => setTimeout(r, 5000));
1871
- continue;
1872
- }
1873
- if (!res.ok) {
1874
- const body = await res.text().catch(() => "");
1875
- throw new Error(`HTTP ${res.status}: ${body.slice(0, 120)}`);
1876
- }
1877
- const data = await res.json();
1878
- if (!Array.isArray(data.results) || data.results.length !== items.length) {
1879
- throw new Error("Unexpected bulk upload response");
1880
- }
1881
- return data.results;
1882
- } catch (error) {
1883
- retries--;
1884
- if (retries === 0) {
1885
- return items.map(() => ({
1886
- success: false,
1887
- error: error instanceof Error ? error.message : String(error)
1888
- }));
1889
- }
1890
- await new Promise((r) => setTimeout(r, 2000));
1891
- }
1892
- }
1893
- return items.map(() => ({ success: false, error: "Upload failed" }));
1894
- }
1895
- async function uploadBulk(apiKey, memories, onProgress) {
1896
- let success = 0;
1897
- let failed = 0;
1898
- const total = memories.length;
1899
- const results = [];
1900
- const batches = createUploadBatches(memories);
1901
- for (const batch of batches) {
1902
- let batchResults = await uploadItems(apiKey, batch.items, true);
1903
- if (batch.items.length > 1 && batchResults.some((result) => !result.success)) {
1904
- const retriedResults = [];
1905
- for (let i = 0;i < batch.items.length; i++) {
1906
- const result = batchResults[i];
1907
- if (result.success) {
1908
- retriedResults.push(result);
1909
- continue;
1910
- }
1911
- const [singleResult] = await uploadItems(apiKey, [batch.items[i]], true);
1912
- retriedResults.push(singleResult);
1913
- }
1914
- batchResults = retriedResults;
1915
- }
1916
- for (let i = 0;i < batchResults.length; i++) {
1917
- const result = batchResults[i];
1918
- results.push({
1919
- index: batch.startIndex + i,
1920
- success: result.success,
1921
- deduped: result.deduped,
1922
- error: result.error
1923
- });
1924
- if (result.success)
1925
- success++;
1926
- else
1927
- failed++;
1928
- }
1929
- onProgress?.(success + failed, total, failed);
1930
- }
1931
- return { success, failed, results };
1932
- }
2134
+ // src/index.ts
2135
+ init_codebase();
2136
+ init_shared();
2137
+ init_api();
1933
2138
 
1934
2139
  // src/configure.ts
1935
2140
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
@@ -2037,7 +2242,7 @@ function getSavedApiKey() {
2037
2242
  import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync9, chmodSync, cpSync, rmSync, symlinkSync, readlinkSync, appendFileSync } from "node:fs";
2038
2243
  import { join as join9, dirname as dirname2 } from "node:path";
2039
2244
  import { homedir as homedir7, platform as platform5 } from "node:os";
2040
- import { execSync } from "node:child_process";
2245
+ import { execSync as execSync2 } from "node:child_process";
2041
2246
  var CONARE_DIR = join9(homedir7(), ".conare");
2042
2247
  var BIN_DIR = join9(CONARE_DIR, "bin");
2043
2248
  var CONFIG_PATH2 = join9(CONARE_DIR, "config.json");
@@ -2047,6 +2252,9 @@ var SYSTEMD_DIR = join9(homedir7(), ".config", "systemd", "user");
2047
2252
  var SYSTEMD_SERVICE = join9(SYSTEMD_DIR, "conare-sync.service");
2048
2253
  var SYSTEMD_TIMER = join9(SYSTEMD_DIR, "conare-sync.timer");
2049
2254
  var TASK_NAME = "ConareMemorySync";
2255
+ var RUN_VBS = `Set WshShell = CreateObject("WScript.Shell")
2256
+ WshShell.Run """" & CreateObject("WScript.Shell").ExpandEnvironmentStrings("%USERPROFILE%") & "\\.conare\\bin\\run.cmd" & """", 0, True
2257
+ `;
2050
2258
  var RUN_CMD = `@echo off
2051
2259
  REM Conare Memory — background sync wrapper (Windows)
2052
2260
  setlocal
@@ -2190,7 +2398,7 @@ WantedBy=timers.target
2190
2398
  }
2191
2399
  function hasSystemd() {
2192
2400
  try {
2193
- execSync("systemctl --user status 2>/dev/null", { stdio: "ignore" });
2401
+ execSync2("systemctl --user status 2>/dev/null", { stdio: "ignore" });
2194
2402
  return true;
2195
2403
  } catch {
2196
2404
  return false;
@@ -2198,7 +2406,7 @@ function hasSystemd() {
2198
2406
  }
2199
2407
  function uid() {
2200
2408
  try {
2201
- return execSync("id -u", { encoding: "utf-8" }).trim();
2409
+ return execSync2("id -u", { encoding: "utf-8" }).trim();
2202
2410
  } catch {
2203
2411
  return "501";
2204
2412
  }
@@ -2227,21 +2435,34 @@ function persistBinary(apiKey) {
2227
2435
  } catch {}
2228
2436
  const runCmdPath = join9(BIN_DIR, "run.cmd");
2229
2437
  writeFileSync4(runCmdPath, RUN_CMD);
2438
+ const runVbsPath = join9(BIN_DIR, "run.vbs");
2439
+ writeFileSync4(runVbsPath, RUN_VBS);
2230
2440
  writeFileSync4(CONFIG_PATH2, JSON.stringify({ apiKey }, null, 2) + `
2231
2441
  `);
2232
2442
  }
2443
+ function isValidJsBundle(path) {
2444
+ if (!existsSync8(path))
2445
+ return false;
2446
+ if (path.endsWith(".ts") || path.endsWith(".tsx"))
2447
+ return false;
2448
+ const head = readFileSync9(path, "utf-8").slice(0, 2000);
2449
+ if (/\btype\s+\{/.test(head) || /,\s*type\s+\w+/.test(head))
2450
+ return false;
2451
+ return true;
2452
+ }
2233
2453
  function findCliBundle() {
2234
- const entry = process.argv[1];
2235
- if (entry && existsSync8(entry))
2236
- return entry;
2237
- const candidates = [
2238
- join9(dirname2(new URL(import.meta.url).pathname), "index.js"),
2239
- join9(dirname2(new URL(import.meta.url).pathname), "..", "dist", "index.js")
2454
+ const dir = dirname2(new URL(import.meta.url).pathname);
2455
+ const distCandidates = [
2456
+ join9(dir, "index.js"),
2457
+ join9(dir, "..", "dist", "index.js")
2240
2458
  ];
2241
- for (const c of candidates) {
2242
- if (existsSync8(c))
2459
+ for (const c of distCandidates) {
2460
+ if (isValidJsBundle(c))
2243
2461
  return c;
2244
2462
  }
2463
+ const entry = process.argv[1];
2464
+ if (entry && isValidJsBundle(entry))
2465
+ return entry;
2245
2466
  return null;
2246
2467
  }
2247
2468
  function findSqlJs() {
@@ -2262,13 +2483,13 @@ function setupMacOS(intervalMinutes) {
2262
2483
  writeFileSync4(PLIST_PATH, makePlist(intervalMinutes));
2263
2484
  const id = uid();
2264
2485
  try {
2265
- execSync(`launchctl bootout gui/${id} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
2486
+ execSync2(`launchctl bootout gui/${id} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
2266
2487
  } catch {}
2267
2488
  try {
2268
- execSync(`launchctl bootstrap gui/${id} "${PLIST_PATH}"`, { stdio: "ignore" });
2489
+ execSync2(`launchctl bootstrap gui/${id} "${PLIST_PATH}"`, { stdio: "ignore" });
2269
2490
  } catch {
2270
2491
  try {
2271
- execSync(`launchctl load "${PLIST_PATH}"`, { stdio: "ignore" });
2492
+ execSync2(`launchctl load "${PLIST_PATH}"`, { stdio: "ignore" });
2272
2493
  } catch {
2273
2494
  throw new Error("Failed to load launchd agent. Try manually: launchctl load " + PLIST_PATH);
2274
2495
  }
@@ -2278,23 +2499,23 @@ function setupLinuxSystemd(intervalMinutes) {
2278
2499
  mkdirSync4(SYSTEMD_DIR, { recursive: true });
2279
2500
  writeFileSync4(SYSTEMD_SERVICE, SYSTEMD_SERVICE_CONTENT);
2280
2501
  writeFileSync4(SYSTEMD_TIMER, makeSystemdTimer(intervalMinutes));
2281
- execSync("systemctl --user daemon-reload", { stdio: "ignore" });
2282
- execSync("systemctl --user enable --now conare-sync.timer", { stdio: "ignore" });
2502
+ execSync2("systemctl --user daemon-reload", { stdio: "ignore" });
2503
+ execSync2("systemctl --user enable --now conare-sync.timer", { stdio: "ignore" });
2283
2504
  }
2284
2505
  function setupLinuxCron(intervalMinutes) {
2285
2506
  const cronCmd = `${homedir7()}/.conare/bin/run.sh`;
2286
2507
  const cronLine = `*/${intervalMinutes} * * * * ${cronCmd}`;
2287
2508
  try {
2288
- const existing = execSync("crontab -l 2>/dev/null", { encoding: "utf-8" });
2509
+ const existing = execSync2("crontab -l 2>/dev/null", { encoding: "utf-8" });
2289
2510
  const filtered = existing.split(`
2290
2511
  `).filter((l) => !l.includes("conare")).join(`
2291
2512
  `);
2292
2513
  const newCrontab = (filtered.trim() ? filtered.trim() + `
2293
2514
  ` : "") + cronLine + `
2294
2515
  `;
2295
- execSync("crontab -", { input: newCrontab, stdio: ["pipe", "ignore", "ignore"] });
2516
+ execSync2("crontab -", { input: newCrontab, stdio: ["pipe", "ignore", "ignore"] });
2296
2517
  } catch {
2297
- execSync("crontab -", { input: cronLine + `
2518
+ execSync2("crontab -", { input: cronLine + `
2298
2519
  `, stdio: ["pipe", "ignore", "ignore"] });
2299
2520
  }
2300
2521
  }
@@ -2303,6 +2524,11 @@ function installGlobalCommand() {
2303
2524
  if (isWindows) {
2304
2525
  const wrapper2 = join9(BIN_DIR, "conare.cmd");
2305
2526
  const content2 = `@echo off\r
2527
+ where node >nul 2>nul\r
2528
+ if errorlevel 1 (\r
2529
+ echo Error: node not found. Install Node.js first. >&2\r
2530
+ exit /b 1\r
2531
+ )\r
2306
2532
  node "%USERPROFILE%\\.conare\\bin\\conare-ingest.mjs" %*\r
2307
2533
  `;
2308
2534
  writeFileSync4(wrapper2, content2);
@@ -2310,11 +2536,12 @@ node "%USERPROFILE%\\.conare\\bin\\conare-ingest.mjs" %*\r
2310
2536
  if (pathDirs.some((d) => d.toLowerCase() === BIN_DIR.toLowerCase())) {
2311
2537
  return "Global command: conare (via .conare\\bin in PATH)";
2312
2538
  }
2539
+ const binDirWin = BIN_DIR.replace(/\//g, "\\");
2313
2540
  try {
2314
- execSync(`powershell -NoProfile -Command "[Environment]::SetEnvironmentVariable('PATH', [Environment]::GetEnvironmentVariable('PATH','User') + ';${BIN_DIR}', 'User')"`, { stdio: "ignore" });
2541
+ execSync2(`powershell -NoProfile -Command "$p = [Environment]::GetEnvironmentVariable('PATH','User'); if ($p -and $p.ToLower().Contains('${binDirWin.toLowerCase().replace(/\\/g, "\\\\")}')) { exit 0 }; [Environment]::SetEnvironmentVariable('PATH', $(if($p){$p + ';'} else {''}) + '${binDirWin.replace(/\\/g, "\\\\")}', 'User')"`, { stdio: "ignore" });
2315
2542
  return `Global command: conare (added .conare\\bin to user PATH — restart terminal)`;
2316
2543
  } catch {
2317
- return `Global command: add ${BIN_DIR} to your PATH manually`;
2544
+ return `Global command: add ${binDirWin} to your PATH manually`;
2318
2545
  }
2319
2546
  }
2320
2547
  const wrapper = join9(BIN_DIR, "conare");
@@ -2391,21 +2618,25 @@ function getShellProfile() {
2391
2618
  return null;
2392
2619
  }
2393
2620
  function setupWindows(intervalMinutes) {
2394
- const runCmd = join9(BIN_DIR, "run.cmd").replace(/\//g, "\\");
2621
+ const runVbs = join9(BIN_DIR, "run.vbs").replace(/\//g, "\\");
2395
2622
  try {
2396
- execSync(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
2623
+ execSync2(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
2397
2624
  } catch {}
2398
- execSync(`schtasks /Create /TN "${TASK_NAME}" /TR "${runCmd}" /SC MINUTE /MO ${intervalMinutes} /F`, { stdio: "ignore" });
2625
+ execSync2(`schtasks /Create /TN "${TASK_NAME}" /TR "wscript.exe \\"${runVbs}\\"" /SC MINUTE /MO ${intervalMinutes} /F`, { stdio: "ignore" });
2399
2626
  }
2400
- function installSync(apiKey, intervalMinutes = 10) {
2627
+ function persistAndInstallGlobal(apiKey) {
2401
2628
  const messages = [];
2402
- const os = platform5();
2403
2629
  persistBinary(apiKey);
2404
2630
  messages.push("Persisted CLI to ~/.conare/bin/");
2405
2631
  messages.push("Saved config to ~/.conare/config.json");
2406
2632
  const globalMsg = installGlobalCommand();
2407
2633
  if (globalMsg)
2408
2634
  messages.push(globalMsg);
2635
+ return messages;
2636
+ }
2637
+ function installSync(apiKey, intervalMinutes = 10) {
2638
+ const messages = persistAndInstallGlobal(apiKey);
2639
+ const os = platform5();
2409
2640
  if (os === "darwin") {
2410
2641
  setupMacOS(intervalMinutes);
2411
2642
  messages.push(`Installed launchd agent (every ${intervalMinutes} min)`);
@@ -2431,7 +2662,7 @@ function uninstallSync() {
2431
2662
  const os = platform5();
2432
2663
  if (os === "darwin") {
2433
2664
  try {
2434
- execSync(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
2665
+ execSync2(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
2435
2666
  } catch {}
2436
2667
  if (existsSync8(PLIST_PATH)) {
2437
2668
  unlinkSync(PLIST_PATH);
@@ -2439,29 +2670,29 @@ function uninstallSync() {
2439
2670
  }
2440
2671
  } else if (os === "win32") {
2441
2672
  try {
2442
- execSync(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
2673
+ execSync2(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
2443
2674
  messages.push("Removed Windows scheduled task");
2444
2675
  } catch {}
2445
2676
  } else if (os === "linux") {
2446
2677
  if (hasSystemd()) {
2447
2678
  try {
2448
- execSync("systemctl --user disable --now conare-sync.timer 2>/dev/null", { stdio: "ignore" });
2679
+ execSync2("systemctl --user disable --now conare-sync.timer 2>/dev/null", { stdio: "ignore" });
2449
2680
  } catch {}
2450
2681
  if (existsSync8(SYSTEMD_SERVICE))
2451
2682
  unlinkSync(SYSTEMD_SERVICE);
2452
2683
  if (existsSync8(SYSTEMD_TIMER))
2453
2684
  unlinkSync(SYSTEMD_TIMER);
2454
2685
  try {
2455
- execSync("systemctl --user daemon-reload", { stdio: "ignore" });
2686
+ execSync2("systemctl --user daemon-reload", { stdio: "ignore" });
2456
2687
  } catch {}
2457
2688
  messages.push("Removed systemd timer");
2458
2689
  } else {
2459
2690
  try {
2460
- const existing = execSync("crontab -l 2>/dev/null", { encoding: "utf-8" });
2691
+ const existing = execSync2("crontab -l 2>/dev/null", { encoding: "utf-8" });
2461
2692
  const filtered = existing.split(`
2462
2693
  `).filter((l) => !l.includes("conare")).join(`
2463
2694
  `);
2464
- execSync("crontab -", { input: filtered.trim() + `
2695
+ execSync2("crontab -", { input: filtered.trim() + `
2465
2696
  `, stdio: ["pipe", "ignore", "ignore"] });
2466
2697
  messages.push("Removed cron job");
2467
2698
  } catch {}
@@ -2556,6 +2787,8 @@ function parseArgs() {
2556
2787
  let source;
2557
2788
  let wasmDir;
2558
2789
  let indexPath;
2790
+ let indexProject;
2791
+ let indexChanged = false;
2559
2792
  for (let i = 0;i < args.length; i++) {
2560
2793
  if (args[i] === "--key" && args[i + 1]) {
2561
2794
  key = args[++i];
@@ -2579,6 +2812,10 @@ function parseArgs() {
2579
2812
  wasmDir = args[++i];
2580
2813
  } else if (args[i] === "--index") {
2581
2814
  indexPath = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : ".";
2815
+ } else if (args[i] === "--project" && args[i + 1]) {
2816
+ indexProject = args[++i];
2817
+ } else if (args[i] === "--changed") {
2818
+ indexChanged = true;
2582
2819
  } else if (args[i] === "--ingest-only") {
2583
2820
  ingestOnly = true;
2584
2821
  } else if (args[i] === "--config-only") {
@@ -2599,6 +2836,8 @@ Options:
2599
2836
  --key <key> Your Conare API key (required, starts with cmem_)
2600
2837
  --config-file <path> Read API key from JSON config file (e.g. ~/.conare/config.json)
2601
2838
  --index [path] Index codebase at path (default: current directory)
2839
+ --project <name> Project name for codebase indexing (auto-detected if omitted)
2840
+ --changed Only index files changed in the last git commit
2602
2841
  --dry-run Preview what would be uploaded
2603
2842
  --force Re-ingest all / re-index all (bypass dedup)
2604
2843
  --quiet Suppress all stdout output (for background timer runs)
@@ -2624,7 +2863,7 @@ Get your API key at https://mcp.conare.ai
2624
2863
  console.error("Error: --ingest-only and --config-only cannot be used together");
2625
2864
  process.exit(1);
2626
2865
  }
2627
- return { key, configFile, dryRun, force, ingestOnly, configOnly, interactive, quiet, installSync: installSyncFlag, uninstallSync: uninstallSyncFlag, syncInterval, source, wasmDir, indexPath };
2866
+ return { key, configFile, dryRun, force, ingestOnly, configOnly, interactive, quiet, installSync: installSyncFlag, uninstallSync: uninstallSyncFlag, syncInterval, source, wasmDir, indexPath, indexProject, indexChanged };
2628
2867
  }
2629
2868
  async function runInstall() {
2630
2869
  const args = process.argv.slice(3);
@@ -2788,15 +3027,52 @@ async function main() {
2788
3027
  } catch {}
2789
3028
  }
2790
3029
  const idxSpinner = Y2();
2791
- idxSpinner.start("Scanning codebase...");
2792
- const { memories, fileCount, skipped } = indexCodebase(absPath);
2793
- idxSpinner.stop(`Codebase: ${fileCount} files found, ${skipped} skipped`);
3030
+ const scanLabel = opts.indexChanged ? "Scanning changed files..." : "Scanning codebase...";
3031
+ idxSpinner.start(scanLabel);
3032
+ const { memories, fileCount, skipped, localFilePaths, project } = indexCodebase(absPath, {
3033
+ changedOnly: opts.indexChanged,
3034
+ project: opts.indexProject
3035
+ });
3036
+ idxSpinner.stop(`Codebase: ${fileCount} files found, ${skipped} skipped (project: ${project})`);
3037
+ if (!opts.dryRun && !opts.indexChanged) {
3038
+ const cleanupSpinner = Y2();
3039
+ cleanupSpinner.start("Checking for stale/deleted files...");
3040
+ try {
3041
+ const { listRemoteMemories: listRemoteMemories2, deleteMemories: deleteMemories2 } = await Promise.resolve().then(() => (init_api(), exports_api));
3042
+ const { getRemoteFilePath: getRemoteFilePath2 } = await Promise.resolve().then(() => (init_codebase(), exports_codebase));
3043
+ const remoteMemories = await listRemoteMemories2(apiKey, "codebase");
3044
+ const localPathSet = new Set(localFilePaths);
3045
+ const uploadingPaths = new Set(memories.map((m2) => getRemoteFilePath2(m2)).filter(Boolean));
3046
+ const toDelete = [];
3047
+ for (const remote of remoteMemories) {
3048
+ const remotePath = getRemoteFilePath2(remote);
3049
+ if (!remotePath)
3050
+ continue;
3051
+ if (!localPathSet.has(remotePath) || uploadingPaths.has(remotePath)) {
3052
+ toDelete.push(remote.id);
3053
+ }
3054
+ }
3055
+ if (toDelete.length > 0) {
3056
+ cleanupSpinner.stop(`Found ${toDelete.length} stale/orphaned files`);
3057
+ const deleted = await deleteMemories2(apiKey, toDelete, (done, total) => {
3058
+ write(`\r Cleaning up ${done}/${total}...`);
3059
+ });
3060
+ write(`\r Cleaned up ${deleted}/${toDelete.length} old memories
3061
+ `);
3062
+ } else {
3063
+ cleanupSpinner.stop("No stale files found");
3064
+ }
3065
+ } catch (err) {
3066
+ cleanupSpinner.stop(`Cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
3067
+ log(" Continuing with upload (stale files may remain)...");
3068
+ }
3069
+ }
2794
3070
  if (memories.length === 0) {
2795
3071
  log(`
2796
3072
  Nothing new to index.`);
2797
3073
  } else if (opts.dryRun) {
2798
3074
  log(`
2799
- [DRY RUN] Would upload ${memories.length} files`);
3075
+ [DRY RUN] Would upload ${memories.length} files (project: ${project})`);
2800
3076
  for (const m2 of memories.slice(0, 10)) {
2801
3077
  const meta = m2.metadata;
2802
3078
  log(` ${meta.language.padEnd(12)} ${meta.filePath}`);
@@ -2970,8 +3246,10 @@ Nothing new to index.`);
2970
3246
  const absPath = resolve2(postIngestIndexPath);
2971
3247
  const idxSpinner = Y2();
2972
3248
  idxSpinner.start("Indexing codebase...");
2973
- const { memories, fileCount, skipped } = indexCodebase(absPath);
2974
- idxSpinner.stop(`Codebase: ${fileCount} files found, ${skipped} skipped`);
3249
+ const { memories, fileCount, skipped, project } = indexCodebase(absPath, {
3250
+ project: opts.indexProject
3251
+ });
3252
+ idxSpinner.stop(`Codebase: ${fileCount} files found, ${skipped} skipped (project: ${project})`);
2975
3253
  if (memories.length === 0) {
2976
3254
  log(`
2977
3255
  Nothing new to index.`);
@@ -2992,13 +3270,19 @@ Nothing new to index.`);
2992
3270
  log("");
2993
3271
  }
2994
3272
  if (!opts.dryRun && !opts.quiet) {
2995
- const syncSpinner = Y2();
2996
- syncSpinner.start("Setting up background sync...");
2997
3273
  try {
2998
- installSync(apiKey, opts.syncInterval);
2999
- syncSpinner.stop(`Background sync active (every ${opts.syncInterval}min)`);
3000
- } catch (e2) {
3001
- syncSpinner.stop(`Could not set up background sync: ${e2.message}`);
3274
+ persistAndInstallGlobal(apiKey);
3275
+ } catch {}
3276
+ const shouldSync = interactiveMode ? await confirmBackgroundSync() : true;
3277
+ if (shouldSync) {
3278
+ const syncSpinner = Y2();
3279
+ syncSpinner.start("Setting up background sync...");
3280
+ try {
3281
+ installSync(apiKey, opts.syncInterval);
3282
+ syncSpinner.stop(`Background sync active (every ${opts.syncInterval}min)`);
3283
+ } catch (e2) {
3284
+ syncSpinner.stop(`Could not set up background sync: ${e2.message}`);
3285
+ }
3002
3286
  }
3003
3287
  }
3004
3288
  log("");