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.
- package/dist/index.js +896 -612
- 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
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
1067
|
-
delete manifest[key];
|
|
1616
|
+
tools.push({ name: "Codex", available: false, path: codexHistory, sessionCount: 0 });
|
|
1068
1617
|
}
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
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
|
-
|
|
1771
|
-
|
|
2081
|
+
const turns = extractTurns(db, composerId, bubbleHeaders);
|
|
2082
|
+
if (turns.length === 0) {
|
|
2083
|
+
filtered++;
|
|
1772
2084
|
continue;
|
|
1773
2085
|
}
|
|
1774
|
-
const
|
|
1775
|
-
const
|
|
1776
|
-
const
|
|
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("
|
|
1779
|
-
|
|
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: "
|
|
2114
|
+
containerTag: "cursor-chats",
|
|
1786
2115
|
metadata: {
|
|
1787
2116
|
dedupKey,
|
|
1788
2117
|
contentHash,
|
|
1789
|
-
source: "
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
language: langFromExt(ext)
|
|
2118
|
+
source: "cursor",
|
|
2119
|
+
sessionId: composerId,
|
|
2120
|
+
name: sessionName,
|
|
2121
|
+
date
|
|
1794
2122
|
}
|
|
1795
2123
|
});
|
|
1796
|
-
|
|
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
|
-
|
|
1800
|
-
return { memories, fileCount, skipped };
|
|
2131
|
+
return { memories, sessionIds, skipped: filtered + deduped, filtered, deduped };
|
|
1801
2132
|
}
|
|
1802
2133
|
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
|
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
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
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
|
|
2242
|
-
if (
|
|
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
|
-
|
|
2486
|
+
execSync2(`launchctl bootout gui/${id} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
|
|
2266
2487
|
} catch {}
|
|
2267
2488
|
try {
|
|
2268
|
-
|
|
2489
|
+
execSync2(`launchctl bootstrap gui/${id} "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
2269
2490
|
} catch {
|
|
2270
2491
|
try {
|
|
2271
|
-
|
|
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
|
-
|
|
2282
|
-
|
|
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 =
|
|
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
|
-
|
|
2516
|
+
execSync2("crontab -", { input: newCrontab, stdio: ["pipe", "ignore", "ignore"] });
|
|
2296
2517
|
} catch {
|
|
2297
|
-
|
|
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
|
-
|
|
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 ${
|
|
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
|
|
2621
|
+
const runVbs = join9(BIN_DIR, "run.vbs").replace(/\//g, "\\");
|
|
2395
2622
|
try {
|
|
2396
|
-
|
|
2623
|
+
execSync2(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
|
|
2397
2624
|
} catch {}
|
|
2398
|
-
|
|
2625
|
+
execSync2(`schtasks /Create /TN "${TASK_NAME}" /TR "wscript.exe \\"${runVbs}\\"" /SC MINUTE /MO ${intervalMinutes} /F`, { stdio: "ignore" });
|
|
2399
2626
|
}
|
|
2400
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
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("");
|