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.
- package/dist/index.js +195 -46
- 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: (
|
|
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: (
|
|
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 (
|
|
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 (
|
|
2352
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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\` |
|
|
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
|
-
|
|
|
2877
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
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
|
}
|