conare 0.0.9 → 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.
Files changed (2) hide show
  1. package/dist/index.js +112 -33
  2. 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: { source: "claude-code", sessionId, project, date: date || "unknown" }
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 (isIngested("codex", sessionId) || sessionIds.includes(sessionId))
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: { source: "codex-session", sessionId, date: date || "unknown" }
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: { source: "cursor", sessionId: composerId, name: sessionName, date }
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 = createHash("sha256").update(raw).digest("hex").slice(0, 16);
934
- const fileHash = `${relPath}:${contentHash}`;
935
- if (isIngested("codebase", fileHash)) {
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 getDedupKey(memory) {
1884
+ function getManifestFingerprint(memory) {
1826
1885
  const metadata = memory.metadata;
1827
- return metadata?.sessionId || metadata?.fileHash || null;
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.");
@@ -1854,6 +1916,16 @@ function printFailureSummary(results, memories) {
1854
1916
  console.log(` - ... and ${failures.length - 3} more`);
1855
1917
  }
1856
1918
  }
1919
+ function renderProgressSummary(success, failed, noun) {
1920
+ const total = success + failed;
1921
+ const barWidth = 20;
1922
+ const ratio = total === 0 ? 1 : success / total;
1923
+ const pct = (ratio * 100).toFixed(1);
1924
+ const filled = Math.round(ratio * barWidth);
1925
+ const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
1926
+ return `\r \x1B[32m✓\x1B[0m [\x1B[36m${bar}\x1B[0m] ${success} ${noun}, ${failed} failed (${pct}%)${" ".repeat(12)}
1927
+ `;
1928
+ }
1857
1929
  function parseArgs() {
1858
1930
  const args = process.argv.slice(2);
1859
1931
  let key = "";
@@ -1977,6 +2049,16 @@ async function main() {
1977
2049
  console.log(auth.email ? `OK (${auth.email})` : "OK");
1978
2050
  saveApiKey(apiKey);
1979
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
+ }
1980
2062
  if (!opts.wasmDir && existsSync7(join9(process.cwd(), "node_modules", "sql.js"))) {
1981
2063
  opts.wasmDir = join9(process.cwd(), "node_modules");
1982
2064
  }
@@ -2035,9 +2117,8 @@ Nothing new to index.`);
2035
2117
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
2036
2118
  process.stdout.write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2037
2119
  });
2038
- process.stdout.write(`\r \x1B[32m✓\x1B[0m ${success} files indexed, ${failed} failed${" ".repeat(20)}
2039
- `);
2040
- const fileHashes = results.filter((result) => result.success).map((result) => getDedupKey(memories[result.index])).filter((key) => !!key);
2120
+ process.stdout.write(renderProgressSummary(success, failed, "indexed"));
2121
+ const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
2041
2122
  markIngested("codebase", fileHashes);
2042
2123
  printFailureSummary(results, memories);
2043
2124
  }
@@ -2114,8 +2195,7 @@ Nothing new to index.`);
2114
2195
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
2115
2196
  process.stdout.write(`\r Uploading [\x1B[33m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2116
2197
  });
2117
- process.stdout.write(`\r \x1B[32m✓\x1B[0m ${success} uploaded, ${failed} failed${" ".repeat(20)}
2118
- `);
2198
+ process.stdout.write(renderProgressSummary(success, failed, "uploaded"));
2119
2199
  if (failed > 0) {
2120
2200
  console.log(` Re-run with --force to retry failed memories.`);
2121
2201
  }
@@ -2129,7 +2209,7 @@ Nothing new to index.`);
2129
2209
  if (!result.success)
2130
2210
  continue;
2131
2211
  const memory = allMemories[result.index];
2132
- const key = getDedupKey(memory);
2212
+ const key = getManifestFingerprint(memory);
2133
2213
  if (!key)
2134
2214
  continue;
2135
2215
  switch (memory.containerTag) {
@@ -2191,9 +2271,8 @@ Nothing new to index.`);
2191
2271
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
2192
2272
  process.stdout.write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
2193
2273
  });
2194
- process.stdout.write(`\r \x1B[32m✓\x1B[0m ${success} files indexed, ${failed} failed${" ".repeat(20)}
2195
- `);
2196
- const fileHashes = results.filter((result) => result.success).map((result) => getDedupKey(memories[result.index])).filter((key) => !!key);
2274
+ process.stdout.write(renderProgressSummary(success, failed, "indexed"));
2275
+ const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
2197
2276
  markIngested("codebase", fileHashes);
2198
2277
  printFailureSummary(results, memories);
2199
2278
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conare",
3
- "version": "0.0.9",
3
+ "version": "0.1.1",
4
4
  "description": "Conare CLI for ingesting AI chat history and configuring memory at conare.ai",
5
5
  "type": "module",
6
6
  "bin": {