conare 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +810 -604
- 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";
|
|
@@ -794,7 +1372,8 @@ __export(exports_interactive, {
|
|
|
794
1372
|
selectChatSources: () => selectChatSources,
|
|
795
1373
|
promptApiKey: () => promptApiKey,
|
|
796
1374
|
finishSetup: () => finishSetup,
|
|
797
|
-
confirmIndexCodebase: () => confirmIndexCodebase
|
|
1375
|
+
confirmIndexCodebase: () => confirmIndexCodebase,
|
|
1376
|
+
confirmBackgroundSync: () => confirmBackgroundSync
|
|
798
1377
|
});
|
|
799
1378
|
function formatDetectedCount(count) {
|
|
800
1379
|
if (count === undefined)
|
|
@@ -866,6 +1445,12 @@ async function confirmIndexCodebase() {
|
|
|
866
1445
|
initialValue: false
|
|
867
1446
|
}));
|
|
868
1447
|
}
|
|
1448
|
+
async function confirmBackgroundSync() {
|
|
1449
|
+
return ensureValue(await ye({
|
|
1450
|
+
message: "Auto-ingest new chats every 10 minutes?",
|
|
1451
|
+
initialValue: true
|
|
1452
|
+
}));
|
|
1453
|
+
}
|
|
869
1454
|
var init_interactive = __esm(() => {
|
|
870
1455
|
init_dist2();
|
|
871
1456
|
});
|
|
@@ -961,118 +1546,45 @@ async function detect() {
|
|
|
961
1546
|
sessionCount = sessions.size;
|
|
962
1547
|
} catch {}
|
|
963
1548
|
}
|
|
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"));
|
|
1042
|
-
}
|
|
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];
|
|
1549
|
+
tools.push({
|
|
1550
|
+
name: "Codex",
|
|
1551
|
+
available: true,
|
|
1552
|
+
path: existsSync(codexHistory) ? codexHistory : codexSessions,
|
|
1553
|
+
sessionCount
|
|
1554
|
+
});
|
|
1065
1555
|
} else {
|
|
1066
|
-
|
|
1067
|
-
delete manifest[key];
|
|
1556
|
+
tools.push({ name: "Codex", available: false, path: codexHistory, sessionCount: 0 });
|
|
1068
1557
|
}
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1558
|
+
const os = platform();
|
|
1559
|
+
let cursorDbPath;
|
|
1560
|
+
if (os === "darwin") {
|
|
1561
|
+
cursorDbPath = join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
1562
|
+
} else if (os === "win32") {
|
|
1563
|
+
cursorDbPath = join(process.env.APPDATA || join(home, "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
|
|
1564
|
+
} else {
|
|
1565
|
+
cursorDbPath = join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
1566
|
+
}
|
|
1567
|
+
tools.push({
|
|
1568
|
+
name: "Cursor",
|
|
1569
|
+
available: existsSync(cursorDbPath),
|
|
1570
|
+
path: cursorDbPath,
|
|
1571
|
+
sessionCount: existsSync(cursorDbPath) ? await countCursorSessions(cursorDbPath) : 0
|
|
1572
|
+
});
|
|
1573
|
+
const openclawDir = join(home, ".openclaw");
|
|
1574
|
+
tools.push({
|
|
1575
|
+
name: "OpenClaw",
|
|
1576
|
+
available: existsSync(openclawDir),
|
|
1577
|
+
path: openclawDir,
|
|
1578
|
+
sessionCount: 0
|
|
1579
|
+
});
|
|
1580
|
+
return tools;
|
|
1073
1581
|
}
|
|
1074
1582
|
|
|
1075
1583
|
// src/ingest/claude.ts
|
|
1584
|
+
init_shared();
|
|
1585
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
|
|
1586
|
+
import { join as join3, basename } from "node:path";
|
|
1587
|
+
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
1076
1588
|
var MAX_CONTENT = 48000;
|
|
1077
1589
|
var MIN_TURN_LEN = 50;
|
|
1078
1590
|
function resolveProjectName(dirName) {
|
|
@@ -1236,6 +1748,7 @@ ${body}`;
|
|
|
1236
1748
|
}
|
|
1237
1749
|
|
|
1238
1750
|
// src/ingest/codex.ts
|
|
1751
|
+
init_shared();
|
|
1239
1752
|
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
1240
1753
|
import { join as join4, basename as basename2 } from "node:path";
|
|
1241
1754
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -1393,6 +1906,7 @@ ${body}`;
|
|
|
1393
1906
|
}
|
|
1394
1907
|
|
|
1395
1908
|
// src/ingest/cursor.ts
|
|
1909
|
+
init_shared();
|
|
1396
1910
|
import { readFileSync as readFileSync5, statSync } from "node:fs";
|
|
1397
1911
|
import { join as join5 } from "node:path";
|
|
1398
1912
|
import { createRequire as createRequire3 } from "node:module";
|
|
@@ -1446,490 +1960,121 @@ function extractTurns(db, composerId, bubbleHeaders) {
|
|
|
1446
1960
|
pendingUser = null;
|
|
1447
1961
|
}
|
|
1448
1962
|
}
|
|
1449
|
-
return turns;
|
|
1450
|
-
}
|
|
1451
|
-
async function ingestCursor(dbPath, wasmDir) {
|
|
1452
|
-
const memories = [];
|
|
1453
|
-
const sessionIds = [];
|
|
1454
|
-
let filtered = 0;
|
|
1455
|
-
let deduped = 0;
|
|
1456
|
-
let fileSize;
|
|
1457
|
-
try {
|
|
1458
|
-
fileSize = statSync(dbPath).size;
|
|
1459
|
-
} catch {
|
|
1460
|
-
console.log(" Skipping Cursor: database not accessible");
|
|
1461
|
-
return { memories, sessionIds, skipped: 0, filtered, deduped };
|
|
1462
|
-
}
|
|
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);
|
|
1963
|
+
return turns;
|
|
1964
|
+
}
|
|
1965
|
+
async function ingestCursor(dbPath, wasmDir) {
|
|
1730
1966
|
const memories = [];
|
|
1731
|
-
|
|
1732
|
-
let
|
|
1733
|
-
|
|
1734
|
-
|
|
1967
|
+
const sessionIds = [];
|
|
1968
|
+
let filtered = 0;
|
|
1969
|
+
let deduped = 0;
|
|
1970
|
+
let fileSize;
|
|
1971
|
+
try {
|
|
1972
|
+
fileSize = statSync(dbPath).size;
|
|
1973
|
+
} catch {
|
|
1974
|
+
console.log(" Skipping Cursor: database not accessible");
|
|
1975
|
+
return { memories, sessionIds, skipped: 0, filtered, deduped };
|
|
1976
|
+
}
|
|
1977
|
+
if (fileSize > MAX_DB_SIZE) {
|
|
1978
|
+
console.log(` Skipping Cursor: database too large (${(fileSize / 1024 / 1024 / 1024).toFixed(1)}GB)`);
|
|
1979
|
+
return { memories, sessionIds, skipped: 0, filtered, deduped };
|
|
1980
|
+
}
|
|
1981
|
+
if (fileSize > WARN_DB_SIZE) {
|
|
1982
|
+
console.log(` Warning: large database (${(fileSize / 1024 / 1024).toFixed(0)}MB), loading into memory...`);
|
|
1983
|
+
}
|
|
1984
|
+
const initSqlJs = loadSqlJs(wasmDir);
|
|
1985
|
+
if (!initSqlJs) {
|
|
1986
|
+
console.log(" Skipping Cursor: sql.js not available");
|
|
1987
|
+
return { memories, sessionIds, skipped: 0, filtered, deduped };
|
|
1988
|
+
}
|
|
1989
|
+
let db;
|
|
1990
|
+
try {
|
|
1991
|
+
db = await openDb(initSqlJs, dbPath, wasmDir);
|
|
1992
|
+
} catch (e) {
|
|
1993
|
+
console.log(` Skipping Cursor: cannot open database (${e.message})`);
|
|
1994
|
+
return { memories, sessionIds, skipped: 0, filtered, deduped };
|
|
1995
|
+
}
|
|
1996
|
+
try {
|
|
1997
|
+
let rows = [];
|
|
1735
1998
|
try {
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
}
|
|
1740
|
-
for (const
|
|
1741
|
-
|
|
1742
|
-
|
|
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;
|
|
1999
|
+
const results = db.exec("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'");
|
|
2000
|
+
if (results.length > 0)
|
|
2001
|
+
rows = results[0].values;
|
|
2002
|
+
} catch {}
|
|
2003
|
+
for (const [key, value] of rows) {
|
|
2004
|
+
const composerId = key.replace("composerData:", "");
|
|
2005
|
+
let parsed;
|
|
1754
2006
|
try {
|
|
1755
|
-
|
|
2007
|
+
parsed = JSON.parse(value);
|
|
2008
|
+
if (!parsed || typeof parsed !== "object") {
|
|
2009
|
+
filtered++;
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
1756
2012
|
} catch {
|
|
2013
|
+
filtered++;
|
|
1757
2014
|
continue;
|
|
1758
2015
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
}
|
|
1763
|
-
let raw;
|
|
1764
|
-
try {
|
|
1765
|
-
raw = readFileSync6(fullPath, "utf-8");
|
|
1766
|
-
} catch {
|
|
1767
|
-
skipped++;
|
|
2016
|
+
const bubbleHeaders = parsed.fullConversationHeadersOnly;
|
|
2017
|
+
if (!Array.isArray(bubbleHeaders) || bubbleHeaders.length === 0) {
|
|
2018
|
+
filtered++;
|
|
1768
2019
|
continue;
|
|
1769
2020
|
}
|
|
1770
|
-
|
|
1771
|
-
|
|
2021
|
+
const turns = extractTurns(db, composerId, bubbleHeaders);
|
|
2022
|
+
if (turns.length === 0) {
|
|
2023
|
+
filtered++;
|
|
1772
2024
|
continue;
|
|
1773
2025
|
}
|
|
1774
|
-
const
|
|
1775
|
-
const
|
|
1776
|
-
const
|
|
2026
|
+
const sessionName = parsed.name || "Cursor Chat";
|
|
2027
|
+
const date = parsed.createdAt ? new Date(parsed.createdAt).toISOString().slice(0, 10) : "unknown";
|
|
2028
|
+
const header = `# ${sessionName} | ${date}`;
|
|
2029
|
+
const body = turns.map((t) => {
|
|
2030
|
+
return `## Q: ${t.user}
|
|
2031
|
+
|
|
2032
|
+
${t.assistant}`;
|
|
2033
|
+
}).join(`
|
|
2034
|
+
|
|
2035
|
+
---
|
|
2036
|
+
|
|
2037
|
+
`);
|
|
2038
|
+
let content = `${header}
|
|
2039
|
+
|
|
2040
|
+
${body}`;
|
|
2041
|
+
if (content.length > MAX_CONTENT3)
|
|
2042
|
+
content = content.slice(0, MAX_CONTENT3) + `
|
|
2043
|
+
|
|
2044
|
+
[truncated]`;
|
|
2045
|
+
const contentHash = createContentHash(content);
|
|
2046
|
+
const dedupKey = `cursor:${composerId}`;
|
|
1777
2047
|
const fingerprint = `${dedupKey}:${contentHash}`;
|
|
1778
|
-
if (isIngested("
|
|
1779
|
-
|
|
2048
|
+
if (isIngested("cursor", fingerprint)) {
|
|
2049
|
+
deduped++;
|
|
1780
2050
|
continue;
|
|
1781
2051
|
}
|
|
1782
|
-
const content = formatFile(relPath, raw, ext);
|
|
1783
2052
|
memories.push({
|
|
1784
2053
|
content,
|
|
1785
|
-
containerTag: "
|
|
2054
|
+
containerTag: "cursor-chats",
|
|
1786
2055
|
metadata: {
|
|
1787
2056
|
dedupKey,
|
|
1788
2057
|
contentHash,
|
|
1789
|
-
source: "
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
language: langFromExt(ext)
|
|
2058
|
+
source: "cursor",
|
|
2059
|
+
sessionId: composerId,
|
|
2060
|
+
name: sessionName,
|
|
2061
|
+
date
|
|
1794
2062
|
}
|
|
1795
2063
|
});
|
|
1796
|
-
|
|
2064
|
+
sessionIds.push(composerId);
|
|
1797
2065
|
}
|
|
2066
|
+
} catch (e) {
|
|
2067
|
+
console.log(` Cursor ingestion error: ${e.message}`);
|
|
2068
|
+
} finally {
|
|
2069
|
+
db.close();
|
|
1798
2070
|
}
|
|
1799
|
-
|
|
1800
|
-
return { memories, fileCount, skipped };
|
|
2071
|
+
return { memories, sessionIds, skipped: filtered + deduped, filtered, deduped };
|
|
1801
2072
|
}
|
|
1802
2073
|
|
|
1803
|
-
// src/
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
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
|
-
}
|
|
2074
|
+
// src/index.ts
|
|
2075
|
+
init_codebase();
|
|
2076
|
+
init_shared();
|
|
2077
|
+
init_api();
|
|
1933
2078
|
|
|
1934
2079
|
// src/configure.ts
|
|
1935
2080
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -2037,7 +2182,7 @@ function getSavedApiKey() {
|
|
|
2037
2182
|
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync9, chmodSync, cpSync, rmSync, symlinkSync, readlinkSync, appendFileSync } from "node:fs";
|
|
2038
2183
|
import { join as join9, dirname as dirname2 } from "node:path";
|
|
2039
2184
|
import { homedir as homedir7, platform as platform5 } from "node:os";
|
|
2040
|
-
import { execSync } from "node:child_process";
|
|
2185
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
2041
2186
|
var CONARE_DIR = join9(homedir7(), ".conare");
|
|
2042
2187
|
var BIN_DIR = join9(CONARE_DIR, "bin");
|
|
2043
2188
|
var CONFIG_PATH2 = join9(CONARE_DIR, "config.json");
|
|
@@ -2047,6 +2192,9 @@ var SYSTEMD_DIR = join9(homedir7(), ".config", "systemd", "user");
|
|
|
2047
2192
|
var SYSTEMD_SERVICE = join9(SYSTEMD_DIR, "conare-sync.service");
|
|
2048
2193
|
var SYSTEMD_TIMER = join9(SYSTEMD_DIR, "conare-sync.timer");
|
|
2049
2194
|
var TASK_NAME = "ConareMemorySync";
|
|
2195
|
+
var RUN_VBS = `Set WshShell = CreateObject("WScript.Shell")
|
|
2196
|
+
WshShell.Run """" & CreateObject("WScript.Shell").ExpandEnvironmentStrings("%USERPROFILE%") & "\\.conare\\bin\\run.cmd" & """", 0, True
|
|
2197
|
+
`;
|
|
2050
2198
|
var RUN_CMD = `@echo off
|
|
2051
2199
|
REM Conare Memory — background sync wrapper (Windows)
|
|
2052
2200
|
setlocal
|
|
@@ -2190,7 +2338,7 @@ WantedBy=timers.target
|
|
|
2190
2338
|
}
|
|
2191
2339
|
function hasSystemd() {
|
|
2192
2340
|
try {
|
|
2193
|
-
|
|
2341
|
+
execSync2("systemctl --user status 2>/dev/null", { stdio: "ignore" });
|
|
2194
2342
|
return true;
|
|
2195
2343
|
} catch {
|
|
2196
2344
|
return false;
|
|
@@ -2198,7 +2346,7 @@ function hasSystemd() {
|
|
|
2198
2346
|
}
|
|
2199
2347
|
function uid() {
|
|
2200
2348
|
try {
|
|
2201
|
-
return
|
|
2349
|
+
return execSync2("id -u", { encoding: "utf-8" }).trim();
|
|
2202
2350
|
} catch {
|
|
2203
2351
|
return "501";
|
|
2204
2352
|
}
|
|
@@ -2227,6 +2375,8 @@ function persistBinary(apiKey) {
|
|
|
2227
2375
|
} catch {}
|
|
2228
2376
|
const runCmdPath = join9(BIN_DIR, "run.cmd");
|
|
2229
2377
|
writeFileSync4(runCmdPath, RUN_CMD);
|
|
2378
|
+
const runVbsPath = join9(BIN_DIR, "run.vbs");
|
|
2379
|
+
writeFileSync4(runVbsPath, RUN_VBS);
|
|
2230
2380
|
writeFileSync4(CONFIG_PATH2, JSON.stringify({ apiKey }, null, 2) + `
|
|
2231
2381
|
`);
|
|
2232
2382
|
}
|
|
@@ -2262,13 +2412,13 @@ function setupMacOS(intervalMinutes) {
|
|
|
2262
2412
|
writeFileSync4(PLIST_PATH, makePlist(intervalMinutes));
|
|
2263
2413
|
const id = uid();
|
|
2264
2414
|
try {
|
|
2265
|
-
|
|
2415
|
+
execSync2(`launchctl bootout gui/${id} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
|
|
2266
2416
|
} catch {}
|
|
2267
2417
|
try {
|
|
2268
|
-
|
|
2418
|
+
execSync2(`launchctl bootstrap gui/${id} "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
2269
2419
|
} catch {
|
|
2270
2420
|
try {
|
|
2271
|
-
|
|
2421
|
+
execSync2(`launchctl load "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
2272
2422
|
} catch {
|
|
2273
2423
|
throw new Error("Failed to load launchd agent. Try manually: launchctl load " + PLIST_PATH);
|
|
2274
2424
|
}
|
|
@@ -2278,23 +2428,23 @@ function setupLinuxSystemd(intervalMinutes) {
|
|
|
2278
2428
|
mkdirSync4(SYSTEMD_DIR, { recursive: true });
|
|
2279
2429
|
writeFileSync4(SYSTEMD_SERVICE, SYSTEMD_SERVICE_CONTENT);
|
|
2280
2430
|
writeFileSync4(SYSTEMD_TIMER, makeSystemdTimer(intervalMinutes));
|
|
2281
|
-
|
|
2282
|
-
|
|
2431
|
+
execSync2("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
2432
|
+
execSync2("systemctl --user enable --now conare-sync.timer", { stdio: "ignore" });
|
|
2283
2433
|
}
|
|
2284
2434
|
function setupLinuxCron(intervalMinutes) {
|
|
2285
2435
|
const cronCmd = `${homedir7()}/.conare/bin/run.sh`;
|
|
2286
2436
|
const cronLine = `*/${intervalMinutes} * * * * ${cronCmd}`;
|
|
2287
2437
|
try {
|
|
2288
|
-
const existing =
|
|
2438
|
+
const existing = execSync2("crontab -l 2>/dev/null", { encoding: "utf-8" });
|
|
2289
2439
|
const filtered = existing.split(`
|
|
2290
2440
|
`).filter((l) => !l.includes("conare")).join(`
|
|
2291
2441
|
`);
|
|
2292
2442
|
const newCrontab = (filtered.trim() ? filtered.trim() + `
|
|
2293
2443
|
` : "") + cronLine + `
|
|
2294
2444
|
`;
|
|
2295
|
-
|
|
2445
|
+
execSync2("crontab -", { input: newCrontab, stdio: ["pipe", "ignore", "ignore"] });
|
|
2296
2446
|
} catch {
|
|
2297
|
-
|
|
2447
|
+
execSync2("crontab -", { input: cronLine + `
|
|
2298
2448
|
`, stdio: ["pipe", "ignore", "ignore"] });
|
|
2299
2449
|
}
|
|
2300
2450
|
}
|
|
@@ -2303,6 +2453,11 @@ function installGlobalCommand() {
|
|
|
2303
2453
|
if (isWindows) {
|
|
2304
2454
|
const wrapper2 = join9(BIN_DIR, "conare.cmd");
|
|
2305
2455
|
const content2 = `@echo off\r
|
|
2456
|
+
where node >nul 2>nul\r
|
|
2457
|
+
if errorlevel 1 (\r
|
|
2458
|
+
echo Error: node not found. Install Node.js first. >&2\r
|
|
2459
|
+
exit /b 1\r
|
|
2460
|
+
)\r
|
|
2306
2461
|
node "%USERPROFILE%\\.conare\\bin\\conare-ingest.mjs" %*\r
|
|
2307
2462
|
`;
|
|
2308
2463
|
writeFileSync4(wrapper2, content2);
|
|
@@ -2310,11 +2465,12 @@ node "%USERPROFILE%\\.conare\\bin\\conare-ingest.mjs" %*\r
|
|
|
2310
2465
|
if (pathDirs.some((d) => d.toLowerCase() === BIN_DIR.toLowerCase())) {
|
|
2311
2466
|
return "Global command: conare (via .conare\\bin in PATH)";
|
|
2312
2467
|
}
|
|
2468
|
+
const binDirWin = BIN_DIR.replace(/\//g, "\\");
|
|
2313
2469
|
try {
|
|
2314
|
-
|
|
2470
|
+
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
2471
|
return `Global command: conare (added .conare\\bin to user PATH — restart terminal)`;
|
|
2316
2472
|
} catch {
|
|
2317
|
-
return `Global command: add ${
|
|
2473
|
+
return `Global command: add ${binDirWin} to your PATH manually`;
|
|
2318
2474
|
}
|
|
2319
2475
|
}
|
|
2320
2476
|
const wrapper = join9(BIN_DIR, "conare");
|
|
@@ -2391,11 +2547,11 @@ function getShellProfile() {
|
|
|
2391
2547
|
return null;
|
|
2392
2548
|
}
|
|
2393
2549
|
function setupWindows(intervalMinutes) {
|
|
2394
|
-
const
|
|
2550
|
+
const runVbs = join9(BIN_DIR, "run.vbs").replace(/\//g, "\\");
|
|
2395
2551
|
try {
|
|
2396
|
-
|
|
2552
|
+
execSync2(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
|
|
2397
2553
|
} catch {}
|
|
2398
|
-
|
|
2554
|
+
execSync2(`schtasks /Create /TN "${TASK_NAME}" /TR "wscript.exe \\"${runVbs}\\"" /SC MINUTE /MO ${intervalMinutes} /F`, { stdio: "ignore" });
|
|
2399
2555
|
}
|
|
2400
2556
|
function installSync(apiKey, intervalMinutes = 10) {
|
|
2401
2557
|
const messages = [];
|
|
@@ -2431,7 +2587,7 @@ function uninstallSync() {
|
|
|
2431
2587
|
const os = platform5();
|
|
2432
2588
|
if (os === "darwin") {
|
|
2433
2589
|
try {
|
|
2434
|
-
|
|
2590
|
+
execSync2(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
|
|
2435
2591
|
} catch {}
|
|
2436
2592
|
if (existsSync8(PLIST_PATH)) {
|
|
2437
2593
|
unlinkSync(PLIST_PATH);
|
|
@@ -2439,29 +2595,29 @@ function uninstallSync() {
|
|
|
2439
2595
|
}
|
|
2440
2596
|
} else if (os === "win32") {
|
|
2441
2597
|
try {
|
|
2442
|
-
|
|
2598
|
+
execSync2(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
|
|
2443
2599
|
messages.push("Removed Windows scheduled task");
|
|
2444
2600
|
} catch {}
|
|
2445
2601
|
} else if (os === "linux") {
|
|
2446
2602
|
if (hasSystemd()) {
|
|
2447
2603
|
try {
|
|
2448
|
-
|
|
2604
|
+
execSync2("systemctl --user disable --now conare-sync.timer 2>/dev/null", { stdio: "ignore" });
|
|
2449
2605
|
} catch {}
|
|
2450
2606
|
if (existsSync8(SYSTEMD_SERVICE))
|
|
2451
2607
|
unlinkSync(SYSTEMD_SERVICE);
|
|
2452
2608
|
if (existsSync8(SYSTEMD_TIMER))
|
|
2453
2609
|
unlinkSync(SYSTEMD_TIMER);
|
|
2454
2610
|
try {
|
|
2455
|
-
|
|
2611
|
+
execSync2("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
2456
2612
|
} catch {}
|
|
2457
2613
|
messages.push("Removed systemd timer");
|
|
2458
2614
|
} else {
|
|
2459
2615
|
try {
|
|
2460
|
-
const existing =
|
|
2616
|
+
const existing = execSync2("crontab -l 2>/dev/null", { encoding: "utf-8" });
|
|
2461
2617
|
const filtered = existing.split(`
|
|
2462
2618
|
`).filter((l) => !l.includes("conare")).join(`
|
|
2463
2619
|
`);
|
|
2464
|
-
|
|
2620
|
+
execSync2("crontab -", { input: filtered.trim() + `
|
|
2465
2621
|
`, stdio: ["pipe", "ignore", "ignore"] });
|
|
2466
2622
|
messages.push("Removed cron job");
|
|
2467
2623
|
} catch {}
|
|
@@ -2556,6 +2712,8 @@ function parseArgs() {
|
|
|
2556
2712
|
let source;
|
|
2557
2713
|
let wasmDir;
|
|
2558
2714
|
let indexPath;
|
|
2715
|
+
let indexProject;
|
|
2716
|
+
let indexChanged = false;
|
|
2559
2717
|
for (let i = 0;i < args.length; i++) {
|
|
2560
2718
|
if (args[i] === "--key" && args[i + 1]) {
|
|
2561
2719
|
key = args[++i];
|
|
@@ -2579,6 +2737,10 @@ function parseArgs() {
|
|
|
2579
2737
|
wasmDir = args[++i];
|
|
2580
2738
|
} else if (args[i] === "--index") {
|
|
2581
2739
|
indexPath = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : ".";
|
|
2740
|
+
} else if (args[i] === "--project" && args[i + 1]) {
|
|
2741
|
+
indexProject = args[++i];
|
|
2742
|
+
} else if (args[i] === "--changed") {
|
|
2743
|
+
indexChanged = true;
|
|
2582
2744
|
} else if (args[i] === "--ingest-only") {
|
|
2583
2745
|
ingestOnly = true;
|
|
2584
2746
|
} else if (args[i] === "--config-only") {
|
|
@@ -2599,6 +2761,8 @@ Options:
|
|
|
2599
2761
|
--key <key> Your Conare API key (required, starts with cmem_)
|
|
2600
2762
|
--config-file <path> Read API key from JSON config file (e.g. ~/.conare/config.json)
|
|
2601
2763
|
--index [path] Index codebase at path (default: current directory)
|
|
2764
|
+
--project <name> Project name for codebase indexing (auto-detected if omitted)
|
|
2765
|
+
--changed Only index files changed in the last git commit
|
|
2602
2766
|
--dry-run Preview what would be uploaded
|
|
2603
2767
|
--force Re-ingest all / re-index all (bypass dedup)
|
|
2604
2768
|
--quiet Suppress all stdout output (for background timer runs)
|
|
@@ -2624,7 +2788,7 @@ Get your API key at https://mcp.conare.ai
|
|
|
2624
2788
|
console.error("Error: --ingest-only and --config-only cannot be used together");
|
|
2625
2789
|
process.exit(1);
|
|
2626
2790
|
}
|
|
2627
|
-
return { key, configFile, dryRun, force, ingestOnly, configOnly, interactive, quiet, installSync: installSyncFlag, uninstallSync: uninstallSyncFlag, syncInterval, source, wasmDir, indexPath };
|
|
2791
|
+
return { key, configFile, dryRun, force, ingestOnly, configOnly, interactive, quiet, installSync: installSyncFlag, uninstallSync: uninstallSyncFlag, syncInterval, source, wasmDir, indexPath, indexProject, indexChanged };
|
|
2628
2792
|
}
|
|
2629
2793
|
async function runInstall() {
|
|
2630
2794
|
const args = process.argv.slice(3);
|
|
@@ -2788,15 +2952,52 @@ async function main() {
|
|
|
2788
2952
|
} catch {}
|
|
2789
2953
|
}
|
|
2790
2954
|
const idxSpinner = Y2();
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2955
|
+
const scanLabel = opts.indexChanged ? "Scanning changed files..." : "Scanning codebase...";
|
|
2956
|
+
idxSpinner.start(scanLabel);
|
|
2957
|
+
const { memories, fileCount, skipped, localFilePaths, project } = indexCodebase(absPath, {
|
|
2958
|
+
changedOnly: opts.indexChanged,
|
|
2959
|
+
project: opts.indexProject
|
|
2960
|
+
});
|
|
2961
|
+
idxSpinner.stop(`Codebase: ${fileCount} files found, ${skipped} skipped (project: ${project})`);
|
|
2962
|
+
if (!opts.dryRun && !opts.indexChanged) {
|
|
2963
|
+
const cleanupSpinner = Y2();
|
|
2964
|
+
cleanupSpinner.start("Checking for stale/deleted files...");
|
|
2965
|
+
try {
|
|
2966
|
+
const { listRemoteMemories: listRemoteMemories2, deleteMemories: deleteMemories2 } = await Promise.resolve().then(() => (init_api(), exports_api));
|
|
2967
|
+
const { getRemoteFilePath: getRemoteFilePath2 } = await Promise.resolve().then(() => (init_codebase(), exports_codebase));
|
|
2968
|
+
const remoteMemories = await listRemoteMemories2(apiKey, "codebase");
|
|
2969
|
+
const localPathSet = new Set(localFilePaths);
|
|
2970
|
+
const uploadingPaths = new Set(memories.map((m2) => getRemoteFilePath2(m2)).filter(Boolean));
|
|
2971
|
+
const toDelete = [];
|
|
2972
|
+
for (const remote of remoteMemories) {
|
|
2973
|
+
const remotePath = getRemoteFilePath2(remote);
|
|
2974
|
+
if (!remotePath)
|
|
2975
|
+
continue;
|
|
2976
|
+
if (!localPathSet.has(remotePath) || uploadingPaths.has(remotePath)) {
|
|
2977
|
+
toDelete.push(remote.id);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
if (toDelete.length > 0) {
|
|
2981
|
+
cleanupSpinner.stop(`Found ${toDelete.length} stale/orphaned files`);
|
|
2982
|
+
const deleted = await deleteMemories2(apiKey, toDelete, (done, total) => {
|
|
2983
|
+
write(`\r Cleaning up ${done}/${total}...`);
|
|
2984
|
+
});
|
|
2985
|
+
write(`\r Cleaned up ${deleted}/${toDelete.length} old memories
|
|
2986
|
+
`);
|
|
2987
|
+
} else {
|
|
2988
|
+
cleanupSpinner.stop("No stale files found");
|
|
2989
|
+
}
|
|
2990
|
+
} catch (err) {
|
|
2991
|
+
cleanupSpinner.stop(`Cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2992
|
+
log(" Continuing with upload (stale files may remain)...");
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2794
2995
|
if (memories.length === 0) {
|
|
2795
2996
|
log(`
|
|
2796
2997
|
Nothing new to index.`);
|
|
2797
2998
|
} else if (opts.dryRun) {
|
|
2798
2999
|
log(`
|
|
2799
|
-
[DRY RUN] Would upload ${memories.length} files`);
|
|
3000
|
+
[DRY RUN] Would upload ${memories.length} files (project: ${project})`);
|
|
2800
3001
|
for (const m2 of memories.slice(0, 10)) {
|
|
2801
3002
|
const meta = m2.metadata;
|
|
2802
3003
|
log(` ${meta.language.padEnd(12)} ${meta.filePath}`);
|
|
@@ -2970,8 +3171,10 @@ Nothing new to index.`);
|
|
|
2970
3171
|
const absPath = resolve2(postIngestIndexPath);
|
|
2971
3172
|
const idxSpinner = Y2();
|
|
2972
3173
|
idxSpinner.start("Indexing codebase...");
|
|
2973
|
-
const { memories, fileCount, skipped } = indexCodebase(absPath
|
|
2974
|
-
|
|
3174
|
+
const { memories, fileCount, skipped, project } = indexCodebase(absPath, {
|
|
3175
|
+
project: opts.indexProject
|
|
3176
|
+
});
|
|
3177
|
+
idxSpinner.stop(`Codebase: ${fileCount} files found, ${skipped} skipped (project: ${project})`);
|
|
2975
3178
|
if (memories.length === 0) {
|
|
2976
3179
|
log(`
|
|
2977
3180
|
Nothing new to index.`);
|
|
@@ -2992,13 +3195,16 @@ Nothing new to index.`);
|
|
|
2992
3195
|
log("");
|
|
2993
3196
|
}
|
|
2994
3197
|
if (!opts.dryRun && !opts.quiet) {
|
|
2995
|
-
const
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3198
|
+
const shouldSync = interactiveMode ? await confirmBackgroundSync() : true;
|
|
3199
|
+
if (shouldSync) {
|
|
3200
|
+
const syncSpinner = Y2();
|
|
3201
|
+
syncSpinner.start("Setting up background sync...");
|
|
3202
|
+
try {
|
|
3203
|
+
installSync(apiKey, opts.syncInterval);
|
|
3204
|
+
syncSpinner.stop(`Background sync active (every ${opts.syncInterval}min)`);
|
|
3205
|
+
} catch (e2) {
|
|
3206
|
+
syncSpinner.stop(`Could not set up background sync: ${e2.message}`);
|
|
3207
|
+
}
|
|
3002
3208
|
}
|
|
3003
3209
|
}
|
|
3004
3210
|
log("");
|