conare 0.5.5 → 0.5.6

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 +195 -46
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
20
38
  var __export = (target, all) => {
21
39
  for (var name in all)
22
40
  __defProp(target, name, {
23
41
  get: all[name],
24
42
  enumerable: true,
25
43
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
44
+ set: __exportSetter.bind(all, name)
27
45
  });
28
46
  };
29
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -57,6 +75,14 @@ ${body}`;
57
75
  return full;
58
76
  return buildContent(TRUNCATED_USER_MSG);
59
77
  }
78
+ function parseTimestampMs(value) {
79
+ if (typeof value === "number" && Number.isFinite(value))
80
+ return value;
81
+ if (typeof value !== "string" || value.trim().length === 0)
82
+ return null;
83
+ const parsed = Date.parse(value);
84
+ return Number.isFinite(parsed) ? parsed : null;
85
+ }
60
86
  function isNarration(text) {
61
87
  const stripped = text.trim();
62
88
  if (stripped.length < MIN_SUBSTANTIVE)
@@ -453,6 +479,7 @@ __export(exports_api, {
453
479
  uploadBulk: () => uploadBulk,
454
480
  listRemoteMemories: () => listRemoteMemories,
455
481
  getRemoteMemoryCount: () => getRemoteMemoryCount,
482
+ getRemoteChatMemoryCount: () => getRemoteChatMemoryCount,
456
483
  getBillingStatus: () => getBillingStatus,
457
484
  deleteMemory: () => deleteMemory,
458
485
  deleteMemories: () => deleteMemories,
@@ -549,6 +576,17 @@ async function getRemoteMemoryCount(apiKey) {
549
576
  return null;
550
577
  }
551
578
  }
579
+ async function getRemoteChatMemoryCount(apiKey) {
580
+ try {
581
+ const data = await apiRequest("/api/containers", apiKey);
582
+ if (!Array.isArray(data.containers))
583
+ return 0;
584
+ const chatContainers = new Set(["claude-chats", "codex-chats", "cursor-chats"]);
585
+ return data.containers.filter((c) => c.tag && chatContainers.has(c.tag)).reduce((sum, c) => sum + (c.count || 0), 0);
586
+ } catch {
587
+ return null;
588
+ }
589
+ }
552
590
  async function getBillingStatus(apiKey) {
553
591
  try {
554
592
  return await apiRequest("/api/billing/status", apiKey);
@@ -569,13 +607,14 @@ async function uploadItems(apiKey, items) {
569
607
  }
570
608
  return data.results;
571
609
  } catch (error) {
610
+ if (error instanceof ApiError && (error.statusCode === 402 || error.message.includes("quota_exceeded"))) {
611
+ const message = error.message || "Plan limit reached";
612
+ return items.map(() => ({
613
+ success: false,
614
+ error: message.includes("conare.ai") ? message : `${message}. Upgrade at https://conare.ai/pricing`
615
+ }));
616
+ }
572
617
  if (error instanceof ApiError && error.statusCode === 429) {
573
- if (error.message.includes("quota_exceeded")) {
574
- return items.map(() => ({
575
- success: false,
576
- error: "Memory limit reached. Upgrade at https://conare.ai/pricing"
577
- }));
578
- }
579
618
  retries--;
580
619
  await new Promise((r) => setTimeout(r, 5000));
581
620
  continue;
@@ -592,6 +631,9 @@ async function uploadItems(apiKey, items) {
592
631
  }
593
632
  return items.map(() => ({ success: false, error: "Upload failed" }));
594
633
  }
634
+ function isPlanLimitError(error) {
635
+ return Boolean(error && /(quota|limit reached|upgrade your plan|pricing)/i.test(error));
636
+ }
595
637
  async function uploadBulk(apiKey, memories, onProgress) {
596
638
  let success = 0;
597
639
  let failed = 0;
@@ -600,11 +642,11 @@ async function uploadBulk(apiKey, memories, onProgress) {
600
642
  const batches = createUploadBatches(memories);
601
643
  for (const batch of batches) {
602
644
  let batchResults = await uploadItems(apiKey, batch.items);
603
- if (batch.items.length > 1 && batchResults.some((result) => !result.success)) {
645
+ if (batch.items.length > 1 && batchResults.some((result) => !result.success && !isPlanLimitError(result.error))) {
604
646
  const retriedResults = [];
605
647
  for (let i = 0;i < batch.items.length; i++) {
606
648
  const result = batchResults[i];
607
- if (result.success) {
649
+ if (result.success || isPlanLimitError(result.error)) {
608
650
  retriedResults.push(result);
609
651
  continue;
610
652
  }
@@ -2174,6 +2216,9 @@ function extractText(content) {
2174
2216
  function parseSession(lines) {
2175
2217
  const rounds = [];
2176
2218
  let date = null;
2219
+ let startedAt = null;
2220
+ let updatedAt = null;
2221
+ let sourceTimestamp = null;
2177
2222
  let currentUser = null;
2178
2223
  let currentAssistant = [];
2179
2224
  for (const line of lines) {
@@ -2185,8 +2230,15 @@ function parseSession(lines) {
2185
2230
  } catch {
2186
2231
  continue;
2187
2232
  }
2188
- if (!date && obj.timestamp)
2233
+ if (obj.timestamp) {
2234
+ if (!startedAt)
2235
+ startedAt = obj.timestamp;
2236
+ updatedAt = obj.timestamp;
2237
+ const parsed = parseTimestampMs(obj.timestamp);
2238
+ if (parsed !== null)
2239
+ sourceTimestamp = parsed;
2189
2240
  date = obj.timestamp.slice(0, 10);
2241
+ }
2190
2242
  if (obj.type === "user") {
2191
2243
  const text = cleanText(extractText(obj.message?.content));
2192
2244
  if (text.length >= MIN_TURN_LEN) {
@@ -2212,7 +2264,7 @@ function parseSession(lines) {
2212
2264
 
2213
2265
  `)
2214
2266
  })).filter((t) => t.assistant.length >= MIN_TURN_LEN);
2215
- return { turns, date };
2267
+ return { turns, date, sourceTimestamp, startedAt, updatedAt };
2216
2268
  }
2217
2269
  function getParentUuid(lines) {
2218
2270
  for (const line of lines) {
@@ -2261,7 +2313,7 @@ function ingestClaude() {
2261
2313
  filtered++;
2262
2314
  continue;
2263
2315
  }
2264
- const { turns, date } = parseSession(lines);
2316
+ const { turns, date, sourceTimestamp, startedAt, updatedAt } = parseSession(lines);
2265
2317
  if (turns.length === 0) {
2266
2318
  filtered++;
2267
2319
  continue;
@@ -2284,8 +2336,12 @@ function ingestClaude() {
2284
2336
  source: "claude-code",
2285
2337
  sessionId,
2286
2338
  project,
2287
- date: date || "unknown"
2288
- }
2339
+ date: date || "unknown",
2340
+ ...sourceTimestamp ? { sourceTimestamp } : {},
2341
+ ...startedAt ? { sessionStartedAt: startedAt } : {},
2342
+ ...updatedAt ? { sessionUpdatedAt: updatedAt } : {}
2343
+ },
2344
+ ...sourceTimestamp ? { created_at: sourceTimestamp, updated_at: sourceTimestamp } : {}
2289
2345
  });
2290
2346
  sessionIds.push(sessionId);
2291
2347
  }
@@ -2341,6 +2397,9 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
2341
2397
  const lines = readFileSync4(join4(dir, entry.name), "utf-8").split(`
2342
2398
  `).filter(Boolean);
2343
2399
  let date = null;
2400
+ let startedAt = null;
2401
+ let updatedAt = null;
2402
+ let sourceTimestamp = null;
2344
2403
  let project = null;
2345
2404
  const rounds = [];
2346
2405
  let currentUser = null;
@@ -2348,8 +2407,15 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
2348
2407
  for (const line of lines) {
2349
2408
  try {
2350
2409
  const obj = JSON.parse(line);
2351
- if (!date && obj.timestamp)
2352
- date = typeof obj.timestamp === "string" ? obj.timestamp.slice(0, 10) : null;
2410
+ if (typeof obj.timestamp === "string") {
2411
+ if (!startedAt)
2412
+ startedAt = obj.timestamp;
2413
+ updatedAt = obj.timestamp;
2414
+ const parsed = parseTimestampMs(obj.timestamp);
2415
+ if (parsed !== null)
2416
+ sourceTimestamp = parsed;
2417
+ date = obj.timestamp.slice(0, 10);
2418
+ }
2353
2419
  if (obj.type === "session_meta" && obj.payload?.cwd) {
2354
2420
  project = projectFromCwd(obj.payload.cwd);
2355
2421
  }
@@ -2429,8 +2495,12 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
2429
2495
  source: "codex-session",
2430
2496
  sessionId,
2431
2497
  date: date || "unknown",
2498
+ ...sourceTimestamp ? { sourceTimestamp } : {},
2499
+ ...startedAt ? { sessionStartedAt: startedAt } : {},
2500
+ ...updatedAt ? { sessionUpdatedAt: updatedAt } : {},
2432
2501
  ...project ? { project } : {}
2433
- }
2502
+ },
2503
+ ...sourceTimestamp ? { created_at: sourceTimestamp, updated_at: sourceTimestamp } : {}
2434
2504
  });
2435
2505
  sessionIds.push(sessionId);
2436
2506
  } catch {}
@@ -2447,6 +2517,12 @@ import { createRequire as createRequire3 } from "node:module";
2447
2517
  var MAX_DB_SIZE = 2 * 1024 * 1024 * 1024;
2448
2518
  var WARN_DB_SIZE = 500 * 1024 * 1024;
2449
2519
  var MIN_TURN_LEN2 = 50;
2520
+ function parseCursorTimestamp(value) {
2521
+ const parsed = parseTimestampMs(value);
2522
+ if (parsed === null)
2523
+ return null;
2524
+ return parsed < 10000000000 ? parsed * 1000 : parsed;
2525
+ }
2450
2526
  function loadSqlJs(wasmDir) {
2451
2527
  try {
2452
2528
  if (wasmDir) {
@@ -2557,7 +2633,9 @@ async function ingestCursor(dbPath, wasmDir) {
2557
2633
  continue;
2558
2634
  }
2559
2635
  const sessionName = parsed.name || "Cursor Chat";
2560
- const date = parsed.createdAt ? new Date(parsed.createdAt).toISOString().slice(0, 10) : "unknown";
2636
+ const sourceTimestamp = parseCursorTimestamp(parsed.lastUpdatedAt) || parseCursorTimestamp(parsed.updatedAt) || parseCursorTimestamp(parsed.createdAt);
2637
+ const startedTimestamp = parseCursorTimestamp(parsed.createdAt);
2638
+ const date = sourceTimestamp ? new Date(sourceTimestamp).toISOString().slice(0, 10) : "unknown";
2561
2639
  const header = `# ${sessionName} | ${date}`;
2562
2640
  const content = fitContent(header, turns);
2563
2641
  const contentHash = createContentHash(content);
@@ -2576,8 +2654,12 @@ async function ingestCursor(dbPath, wasmDir) {
2576
2654
  source: "cursor",
2577
2655
  sessionId: composerId,
2578
2656
  name: sessionName,
2579
- date
2580
- }
2657
+ date,
2658
+ ...sourceTimestamp ? { sourceTimestamp } : {},
2659
+ ...startedTimestamp ? { sessionStartedAt: new Date(startedTimestamp).toISOString() } : {},
2660
+ ...sourceTimestamp ? { sessionUpdatedAt: new Date(sourceTimestamp).toISOString() } : {}
2661
+ },
2662
+ ...sourceTimestamp ? { created_at: sourceTimestamp, updated_at: sourceTimestamp } : {}
2581
2663
  });
2582
2664
  sessionIds.push(composerId);
2583
2665
  }
@@ -2634,29 +2716,38 @@ function getServerConfig(apiKey) {
2634
2716
  };
2635
2717
  }
2636
2718
  function configureClaude(apiKey) {
2637
- const claudeConfigPath = join7(homedir5(), ".claude.json");
2638
- const claudeMcpPath = join7(homedir5(), ".claude", "mcp.json");
2639
2719
  if (spawnSync("claude", ["mcp", "add-json", SERVER_NAME, "--scope", "user", JSON.stringify(getServerConfig(apiKey))], {
2640
2720
  stdio: "ignore",
2641
2721
  shell: platform5() === "win32"
2642
2722
  }).status === 0) {
2643
2723
  return "\x1B[32m✓\x1B[0m Claude Code";
2644
2724
  }
2725
+ if (spawnSync("claude", [
2726
+ "mcp",
2727
+ "add",
2728
+ SERVER_NAME,
2729
+ "--transport",
2730
+ "http",
2731
+ "--scope",
2732
+ "user",
2733
+ "--header",
2734
+ `Authorization: Bearer ${apiKey}`,
2735
+ "--",
2736
+ `${CONARE_URL}/mcp`
2737
+ ], {
2738
+ stdio: "ignore",
2739
+ shell: platform5() === "win32"
2740
+ }).status === 0) {
2741
+ return "\x1B[32m✓\x1B[0m Claude Code";
2742
+ }
2743
+ const claudeConfigPath = join7(homedir5(), ".claude.json");
2645
2744
  const config = readJsonFile(claudeConfigPath);
2646
2745
  if (!config.mcpServers || typeof config.mcpServers !== "object") {
2647
2746
  config.mcpServers = {};
2648
2747
  }
2649
2748
  config.mcpServers[SERVER_NAME] = getServerConfig(apiKey);
2650
2749
  writeJsonFile(claudeConfigPath, config);
2651
- if (existsSync6(join7(homedir5(), ".claude"))) {
2652
- const mcpConfig = readJsonFile(claudeMcpPath);
2653
- if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== "object") {
2654
- mcpConfig.mcpServers = {};
2655
- }
2656
- mcpConfig.mcpServers[SERVER_NAME] = getServerConfig(apiKey);
2657
- writeJsonFile(claudeMcpPath, mcpConfig);
2658
- }
2659
- return "\x1B[32m✓\x1B[0m Claude Code";
2750
+ return "\x1B[32m✓\x1B[0m Claude Code (json fallback)";
2660
2751
  }
2661
2752
  function configureCodex(apiKey) {
2662
2753
  const configPath = join7(homedir5(), ".codex", "config.toml");
@@ -2849,7 +2940,7 @@ description: Load prior project context, search past sessions, save durable pref
2849
2940
  compatibility: Requires the Conare MCP server tools (\`recall\`, \`search\`, \`save\`, \`list\`, \`forget\`) to be installed and connected.
2850
2941
  metadata:
2851
2942
  author: Conare
2852
- version: 1.2.0
2943
+ version: 1.3.0
2853
2944
  mcp-server: conare
2854
2945
  homepage: https://conare.ai
2855
2946
  ---
@@ -2868,22 +2959,33 @@ This skill teaches the agent the default workflow, tool-selection rules, and que
2868
2959
 
2869
2960
  | Situation | Tool | Example |
2870
2961
  |-----------|------|---------|
2871
- | Start of conversation | \`recall\` | Always call first with conversation context |
2872
- | User asks about past work | \`search\` | "What did we do last week?" |
2962
+ | Start of conversation | \`recall\` | Always call first with conversation context + \`prompt\` |
2963
+ | User asks about past work | \`search\` | query + \`prompt\` steering the angle |
2873
2964
  | User says "remember this" | \`save\` | Save preferences, rules, decisions |
2874
2965
  | User says "forget this" | \`forget\` | Remove a specific memory |
2875
2966
  | Browse what's stored | \`list\` | "Show me recent memories" |
2876
- | Narrative/summary question | \`search\` with \`deep: true\` | "What happened with the auth rewrite?" |
2877
- | Deep + specific format | \`search\` with \`deep\` + \`prompt\` | query="auth rewrite bug", prompt="timeline with dates" |
2967
+ | Exact-string raw lookup | \`search\` with \`deep: false\` | Verbatim memory text (rare) |
2968
+
2969
+ ## How recall & search Work
2970
+
2971
+ Both return an LLM-synthesized answer by default — a noise-removed, detail-preserving brief distilled from the matched memories. The synthesizer is **not a summarizer**: it strips redundancy and superseded claims while preserving every specific number, file path, CLI command, code block, and the WHY behind each decision.
2972
+
2973
+ **Two axes, always pair them:**
2878
2974
 
2879
- ## Deep Mode
2975
+ - \`query\` / \`context\` → keyword-dense retrieval phrase (finds the right memories)
2976
+ - \`prompt\` → synthesis instruction (what to emphasize / how to structure it)
2880
2977
 
2881
- \`deep: true\` on \`search\` or \`recall\` returns an LLM-synthesized answer instead of raw memories.
2978
+ Example:
2979
+ \`\`\`
2980
+ search({
2981
+ query: "auth rewrite middleware compliance",
2982
+ prompt: "focus on the final decision and why; preserve all file paths and config values"
2983
+ })
2984
+ \`\`\`
2882
2985
 
2883
- - **Use for**: narratives, summaries, "what happened with X", comparing approaches
2884
- - **Don't use for**: exact values, config lookups, file paths — raw preserves full detail
2986
+ Pass \`prompt\` on almost every call. Without it the synthesizer picks a sensible default, but with it you get exactly the angle the user cares about.
2885
2987
 
2886
- \`prompt\` (deep only) separates retrieval from synthesis. Keep \`query\` keyword-dense to find the right memories. Use \`prompt\` to instruct the LLM on format: "chronological timeline with dates", "only shipped changes, not proposals", "compare X vs Y". Omit \`prompt\` to let the LLM infer format from the query.
2988
+ **Opt out of synthesis** with \`deep: false\` only when you need raw memory text for an exact-string lookup. Prefer leaving it unset.
2887
2989
 
2888
2990
  ## Critical Rules
2889
2991
 
@@ -3508,6 +3610,11 @@ function uninstallSync() {
3508
3610
  // src/index.ts
3509
3611
  init_interactive();
3510
3612
  var CONARE_URL2 = "https://conare.ai";
3613
+ var CHAT_CONTAINER_LABELS = {
3614
+ "claude-chats": "Claude Code",
3615
+ "codex-chats": "Codex",
3616
+ "cursor-chats": "Cursor"
3617
+ };
3511
3618
  function getManifestFingerprint(memory) {
3512
3619
  const metadata = memory.metadata;
3513
3620
  if (metadata?.dedupKey && metadata?.contentHash) {
@@ -3563,6 +3670,13 @@ function renderDiscoverySummary(discovered, _filtered, deduped) {
3563
3670
  parts.push(`${deduped} already imported`);
3564
3671
  return parts.join(", ");
3565
3672
  }
3673
+ function renderSourceBreakdown(memories) {
3674
+ const counts = new Map;
3675
+ for (const memory of memories) {
3676
+ counts.set(memory.containerTag, (counts.get(memory.containerTag) || 0) + 1);
3677
+ }
3678
+ return [...counts.entries()].map(([tag, count]) => `${CHAT_CONTAINER_LABELS[tag] || tag}: ${count}`).join(", ");
3679
+ }
3566
3680
  function parseArgs() {
3567
3681
  const args = process.argv.slice(2);
3568
3682
  let key = "";
@@ -3621,6 +3735,7 @@ conare — AI memory for your coding tools
3621
3735
  Usage:
3622
3736
  conare Interactive setup with browser auth
3623
3737
  conare install Just install the MCP (all detected clients)
3738
+ conare logout Clear saved API key, sync timer, and local index history
3624
3739
  conare --key <api_key> Index chat history (key optional with browser auth)
3625
3740
  conare --key <api_key> --index [path] Index codebase
3626
3741
 
@@ -3713,10 +3828,30 @@ async function runInstall() {
3713
3828
  console.log(" \x1B[32m✓\x1B[0m MCP installed. Restart your AI tool to connect.");
3714
3829
  console.log("");
3715
3830
  }
3831
+ async function runLogout() {
3832
+ const { unlinkSync: unlinkSync2, existsSync: existsSync10 } = await import("node:fs");
3833
+ const { join: join11 } = await import("node:path");
3834
+ const { homedir: homedir8 } = await import("node:os");
3835
+ const messages = uninstallSync();
3836
+ const manifestPath = join11(homedir8(), ".conare", "ingested.json");
3837
+ if (existsSync10(manifestPath)) {
3838
+ unlinkSync2(manifestPath);
3839
+ messages.push("Removed local index history");
3840
+ }
3841
+ console.log("");
3842
+ for (const msg of messages)
3843
+ console.log(` ${msg}`);
3844
+ console.log("");
3845
+ console.log(" \x1B[32m✓\x1B[0m Logged out. API key cleared from ~/.conare/.");
3846
+ console.log("");
3847
+ }
3716
3848
  async function main() {
3717
3849
  if (process.argv[2] === "install") {
3718
3850
  return runInstall();
3719
3851
  }
3852
+ if (process.argv[2] === "logout") {
3853
+ return runLogout();
3854
+ }
3720
3855
  const opts = parseArgs();
3721
3856
  let configFileKey;
3722
3857
  if (opts.configFile) {
@@ -4009,6 +4144,10 @@ Nothing new to index.`);
4009
4144
  }
4010
4145
  }
4011
4146
  allMemories.sort((a, b3) => {
4147
+ const ta = a.metadata?.sourceTimestamp || a.updated_at || 0;
4148
+ const tb = b3.metadata?.sourceTimestamp || b3.updated_at || 0;
4149
+ if (ta !== tb)
4150
+ return tb - ta;
4012
4151
  const da = a.metadata?.date || "0000";
4013
4152
  const db = b3.metadata?.date || "0000";
4014
4153
  return db.localeCompare(da);
@@ -4016,10 +4155,20 @@ Nothing new to index.`);
4016
4155
  const billing = await getBillingStatus(apiKey);
4017
4156
  if (billing && billing.plan === "free" && billing.limits.uploadedChats > 0) {
4018
4157
  const chatLimit = billing.limits.uploadedChats;
4019
- if (allMemories.length > chatLimit) {
4020
- const skipped = allMemories.length - chatLimit;
4021
- allMemories.splice(chatLimit);
4022
- log(`\x1B[33m⚠\x1B[0m Free plan: uploading ${chatLimit} most recent sessions (${skipped} older sessions skipped)`);
4158
+ const remoteChatCount = opts.dryRun ? 0 : await getRemoteChatMemoryCount(apiKey);
4159
+ const knownRemoteChatCount = remoteChatCount ?? billing.usage?.memories ?? 0;
4160
+ const remaining = Math.max(0, chatLimit - knownRemoteChatCount);
4161
+ if (allMemories.length > remaining) {
4162
+ const skipped = allMemories.length - remaining;
4163
+ allMemories.splice(remaining);
4164
+ if (remaining > 0) {
4165
+ const breakdown = renderSourceBreakdown(allMemories);
4166
+ log(`\x1B[33m⚠\x1B[0m Free plan: uploading ${remaining} newest remaining sessions (${knownRemoteChatCount}/${chatLimit} already imported; ${skipped} older sessions skipped)`);
4167
+ if (breakdown)
4168
+ log(` Selected now: ${breakdown}`);
4169
+ } else {
4170
+ log(`\x1B[33m⚠\x1B[0m Free plan: chat upload limit already reached (${knownRemoteChatCount}/${chatLimit}); ${skipped} new local sessions skipped`);
4171
+ }
4023
4172
  log(` Upgrade to Pro for unlimited uploads → \x1B[4mhttps://conare.ai/pricing\x1B[0m`);
4024
4173
  log();
4025
4174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conare",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Conare CLI for indexing AI chat history and configuring memory at conare.ai",
5
5
  "type": "module",
6
6
  "bin": {