conare 0.1.0 → 0.1.1
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 +99 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -281,6 +281,7 @@ import { homedir as homedir3 } from "node:os";
|
|
|
281
281
|
|
|
282
282
|
// src/ingest/shared.ts
|
|
283
283
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
284
|
+
import { createHash } from "node:crypto";
|
|
284
285
|
import { join as join2 } from "node:path";
|
|
285
286
|
import { homedir as homedir2 } from "node:os";
|
|
286
287
|
var MANIFEST_PATH = join2(homedir2(), ".conare", "ingested.json");
|
|
@@ -290,6 +291,9 @@ function cleanText(raw) {
|
|
|
290
291
|
text = text.replace(/<attached-context[\s\S]*?<\/attached-context>/g, "");
|
|
291
292
|
return text.trim();
|
|
292
293
|
}
|
|
294
|
+
function createContentHash(content) {
|
|
295
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
296
|
+
}
|
|
293
297
|
function getIngested() {
|
|
294
298
|
try {
|
|
295
299
|
if (existsSync2(MANIFEST_PATH)) {
|
|
@@ -390,10 +394,6 @@ function ingestClaude() {
|
|
|
390
394
|
}
|
|
391
395
|
for (const file of files) {
|
|
392
396
|
const sessionId = basename(file, ".jsonl");
|
|
393
|
-
if (isIngested("claude", sessionId)) {
|
|
394
|
-
skipped++;
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
397
|
const raw = readFileSync2(join3(projPath, file), "utf-8");
|
|
398
398
|
const { turns, date } = parseSession(raw.split(`
|
|
399
399
|
`));
|
|
@@ -419,10 +419,24 @@ ${body}`;
|
|
|
419
419
|
content = content.slice(0, MAX_CONTENT) + `
|
|
420
420
|
|
|
421
421
|
[truncated]`;
|
|
422
|
+
const contentHash = createContentHash(content);
|
|
423
|
+
const dedupKey = `claude:${sessionId}`;
|
|
424
|
+
const fingerprint = `${dedupKey}:${contentHash}`;
|
|
425
|
+
if (isIngested("claude", fingerprint)) {
|
|
426
|
+
skipped++;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
422
429
|
memories.push({
|
|
423
430
|
content,
|
|
424
431
|
containerTag: "claude-chats",
|
|
425
|
-
metadata: {
|
|
432
|
+
metadata: {
|
|
433
|
+
dedupKey,
|
|
434
|
+
contentHash,
|
|
435
|
+
source: "claude-code",
|
|
436
|
+
sessionId,
|
|
437
|
+
project,
|
|
438
|
+
date: date || "unknown"
|
|
439
|
+
}
|
|
426
440
|
});
|
|
427
441
|
sessionIds.push(sessionId);
|
|
428
442
|
}
|
|
@@ -458,10 +472,6 @@ function ingestCodex() {
|
|
|
458
472
|
}
|
|
459
473
|
}
|
|
460
474
|
for (const [sessionId, entries] of sessions) {
|
|
461
|
-
if (isIngested("codex", sessionId)) {
|
|
462
|
-
skipped++;
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
475
|
entries.sort((a, b) => a.ts - b.ts);
|
|
466
476
|
const date = new Date(entries[0].ts * 1000).toISOString().slice(0, 10);
|
|
467
477
|
const body = entries.map((e) => {
|
|
@@ -483,10 +493,17 @@ ${body}`;
|
|
|
483
493
|
skipped++;
|
|
484
494
|
continue;
|
|
485
495
|
}
|
|
496
|
+
const contentHash = createContentHash(content);
|
|
497
|
+
const dedupKey = `codex:${sessionId}`;
|
|
498
|
+
const fingerprint = `${dedupKey}:${contentHash}`;
|
|
499
|
+
if (isIngested("codex", fingerprint)) {
|
|
500
|
+
skipped++;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
486
503
|
memories.push({
|
|
487
504
|
content,
|
|
488
505
|
containerTag: "codex-chats",
|
|
489
|
-
metadata: { source: "codex", sessionId, date }
|
|
506
|
+
metadata: { dedupKey, contentHash, source: "codex", sessionId, date }
|
|
490
507
|
});
|
|
491
508
|
sessionIds.push(sessionId);
|
|
492
509
|
}
|
|
@@ -507,7 +524,7 @@ function walkCodexSessions(dir, memories, sessionIds, skipped) {
|
|
|
507
524
|
walkCodexSessions(join4(dir, entry.name), memories, sessionIds, skipped);
|
|
508
525
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
509
526
|
const sessionId = basename2(entry.name, ".jsonl");
|
|
510
|
-
if (
|
|
527
|
+
if (sessionIds.includes(sessionId))
|
|
511
528
|
continue;
|
|
512
529
|
try {
|
|
513
530
|
const lines = readFileSync3(join4(dir, entry.name), "utf-8").split(`
|
|
@@ -549,10 +566,22 @@ ${body}`;
|
|
|
549
566
|
content = content.slice(0, MAX_CONTENT2) + `
|
|
550
567
|
|
|
551
568
|
[truncated]`;
|
|
569
|
+
const contentHash = createContentHash(content);
|
|
570
|
+
const dedupKey = `codex:${sessionId}`;
|
|
571
|
+
const fingerprint = `${dedupKey}:${contentHash}`;
|
|
572
|
+
if (isIngested("codex", fingerprint)) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
552
575
|
memories.push({
|
|
553
576
|
content,
|
|
554
577
|
containerTag: "codex-chats",
|
|
555
|
-
metadata: {
|
|
578
|
+
metadata: {
|
|
579
|
+
dedupKey,
|
|
580
|
+
contentHash,
|
|
581
|
+
source: "codex-session",
|
|
582
|
+
sessionId,
|
|
583
|
+
date: date || "unknown"
|
|
584
|
+
}
|
|
556
585
|
});
|
|
557
586
|
sessionIds.push(sessionId);
|
|
558
587
|
} catch {}
|
|
@@ -656,10 +685,6 @@ async function ingestCursor(dbPath, wasmDir) {
|
|
|
656
685
|
} catch {}
|
|
657
686
|
for (const [key, value] of rows) {
|
|
658
687
|
const composerId = key.replace("composerData:", "");
|
|
659
|
-
if (isIngested("cursor", composerId)) {
|
|
660
|
-
skipped++;
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
688
|
let parsed;
|
|
664
689
|
try {
|
|
665
690
|
parsed = JSON.parse(value);
|
|
@@ -701,10 +726,24 @@ ${body}`;
|
|
|
701
726
|
content = content.slice(0, MAX_CONTENT3) + `
|
|
702
727
|
|
|
703
728
|
[truncated]`;
|
|
729
|
+
const contentHash = createContentHash(content);
|
|
730
|
+
const dedupKey = `cursor:${composerId}`;
|
|
731
|
+
const fingerprint = `${dedupKey}:${contentHash}`;
|
|
732
|
+
if (isIngested("cursor", fingerprint)) {
|
|
733
|
+
skipped++;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
704
736
|
memories.push({
|
|
705
737
|
content,
|
|
706
738
|
containerTag: "cursor-chats",
|
|
707
|
-
metadata: {
|
|
739
|
+
metadata: {
|
|
740
|
+
dedupKey,
|
|
741
|
+
contentHash,
|
|
742
|
+
source: "cursor",
|
|
743
|
+
sessionId: composerId,
|
|
744
|
+
name: sessionName,
|
|
745
|
+
date
|
|
746
|
+
}
|
|
708
747
|
});
|
|
709
748
|
sessionIds.push(composerId);
|
|
710
749
|
}
|
|
@@ -717,7 +756,7 @@ ${body}`;
|
|
|
717
756
|
}
|
|
718
757
|
|
|
719
758
|
// src/ingest/codebase.ts
|
|
720
|
-
import { createHash } from "node:crypto";
|
|
759
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
721
760
|
import { readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync2, existsSync as existsSync4 } from "node:fs";
|
|
722
761
|
import { join as join6, relative, extname, resolve } from "node:path";
|
|
723
762
|
var DEFAULT_IGNORE = new Set([
|
|
@@ -884,6 +923,7 @@ ${content}
|
|
|
884
923
|
}
|
|
885
924
|
function indexCodebase(rootPath) {
|
|
886
925
|
const absRoot = resolve(rootPath);
|
|
926
|
+
const repoHash = createHash2("sha256").update(absRoot).digest("hex").slice(0, 12);
|
|
887
927
|
const gitignorePatterns = parseGitignore(absRoot);
|
|
888
928
|
const memories = [];
|
|
889
929
|
let fileCount = 0;
|
|
@@ -930,9 +970,10 @@ function indexCodebase(rootPath) {
|
|
|
930
970
|
continue;
|
|
931
971
|
}
|
|
932
972
|
const relPath = relative(absRoot, fullPath);
|
|
933
|
-
const contentHash =
|
|
934
|
-
const
|
|
935
|
-
|
|
973
|
+
const contentHash = createContentHash(raw);
|
|
974
|
+
const dedupKey = `codebase:${repoHash}:${relPath}`;
|
|
975
|
+
const fingerprint = `${dedupKey}:${contentHash}`;
|
|
976
|
+
if (isIngested("codebase", fingerprint)) {
|
|
936
977
|
skipped++;
|
|
937
978
|
continue;
|
|
938
979
|
}
|
|
@@ -941,9 +982,12 @@ function indexCodebase(rootPath) {
|
|
|
941
982
|
content,
|
|
942
983
|
containerTag: "codebase",
|
|
943
984
|
metadata: {
|
|
985
|
+
dedupKey,
|
|
986
|
+
contentHash,
|
|
944
987
|
source: "codebase-index",
|
|
988
|
+
repoHash,
|
|
945
989
|
filePath: relPath,
|
|
946
|
-
fileHash
|
|
990
|
+
fileHash: `${relPath}:${contentHash}`,
|
|
947
991
|
language: langFromExt(ext)
|
|
948
992
|
}
|
|
949
993
|
});
|
|
@@ -992,6 +1036,21 @@ async function validateKey(apiKey) {
|
|
|
992
1036
|
return { valid: false };
|
|
993
1037
|
}
|
|
994
1038
|
}
|
|
1039
|
+
async function getRemoteMemoryCount(apiKey) {
|
|
1040
|
+
try {
|
|
1041
|
+
const res = await fetch(`${API_URL}/api/containers`, {
|
|
1042
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1043
|
+
});
|
|
1044
|
+
if (!res.ok)
|
|
1045
|
+
return null;
|
|
1046
|
+
const data = await res.json();
|
|
1047
|
+
if (!Array.isArray(data.containers))
|
|
1048
|
+
return 0;
|
|
1049
|
+
return data.containers.reduce((sum, container) => sum + (container.count || 0), 0);
|
|
1050
|
+
} catch {
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
995
1054
|
async function uploadItems(apiKey, items) {
|
|
996
1055
|
let retries = 4;
|
|
997
1056
|
while (retries > 0) {
|
|
@@ -1822,9 +1881,12 @@ async function confirmIndexCodebase() {
|
|
|
1822
1881
|
}
|
|
1823
1882
|
|
|
1824
1883
|
// src/index.ts
|
|
1825
|
-
function
|
|
1884
|
+
function getManifestFingerprint(memory) {
|
|
1826
1885
|
const metadata = memory.metadata;
|
|
1827
|
-
|
|
1886
|
+
if (metadata?.dedupKey && metadata?.contentHash) {
|
|
1887
|
+
return `${metadata.dedupKey}:${metadata.contentHash}`;
|
|
1888
|
+
}
|
|
1889
|
+
return metadata?.dedupKey || metadata?.sessionId || metadata?.fileHash || null;
|
|
1828
1890
|
}
|
|
1829
1891
|
function printMissingKeyError() {
|
|
1830
1892
|
console.error("Error: no API key configured.");
|
|
@@ -1987,6 +2049,16 @@ async function main() {
|
|
|
1987
2049
|
console.log(auth.email ? `OK (${auth.email})` : "OK");
|
|
1988
2050
|
saveApiKey(apiKey);
|
|
1989
2051
|
console.log();
|
|
2052
|
+
if (!opts.force && !opts.dryRun) {
|
|
2053
|
+
const remoteMemoryCount = await getRemoteMemoryCount(apiKey);
|
|
2054
|
+
const localManifest = getIngested();
|
|
2055
|
+
const localManifestCount = Object.values(localManifest).reduce((sum, entries) => sum + entries.length, 0);
|
|
2056
|
+
if (remoteMemoryCount === 0 && localManifestCount > 0) {
|
|
2057
|
+
clearIngested();
|
|
2058
|
+
console.log("Remote account is empty; cleared stale local ingestion history.");
|
|
2059
|
+
console.log("");
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
1990
2062
|
if (!opts.wasmDir && existsSync7(join9(process.cwd(), "node_modules", "sql.js"))) {
|
|
1991
2063
|
opts.wasmDir = join9(process.cwd(), "node_modules");
|
|
1992
2064
|
}
|
|
@@ -2046,7 +2118,7 @@ Nothing new to index.`);
|
|
|
2046
2118
|
process.stdout.write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
|
|
2047
2119
|
});
|
|
2048
2120
|
process.stdout.write(renderProgressSummary(success, failed, "indexed"));
|
|
2049
|
-
const fileHashes = results.filter((result) => result.success).map((result) =>
|
|
2121
|
+
const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
|
|
2050
2122
|
markIngested("codebase", fileHashes);
|
|
2051
2123
|
printFailureSummary(results, memories);
|
|
2052
2124
|
}
|
|
@@ -2137,7 +2209,7 @@ Nothing new to index.`);
|
|
|
2137
2209
|
if (!result.success)
|
|
2138
2210
|
continue;
|
|
2139
2211
|
const memory = allMemories[result.index];
|
|
2140
|
-
const key =
|
|
2212
|
+
const key = getManifestFingerprint(memory);
|
|
2141
2213
|
if (!key)
|
|
2142
2214
|
continue;
|
|
2143
2215
|
switch (memory.containerTag) {
|
|
@@ -2200,7 +2272,7 @@ Nothing new to index.`);
|
|
|
2200
2272
|
process.stdout.write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
|
|
2201
2273
|
});
|
|
2202
2274
|
process.stdout.write(renderProgressSummary(success, failed, "indexed"));
|
|
2203
|
-
const fileHashes = results.filter((result) => result.success).map((result) =>
|
|
2275
|
+
const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
|
|
2204
2276
|
markIngested("codebase", fileHashes);
|
|
2205
2277
|
printFailureSummary(results, memories);
|
|
2206
2278
|
}
|