conare 0.3.4 → 0.3.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 +479 -69
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -114,7 +114,7 @@ __export(exports_codebase, {
|
|
|
114
114
|
import { createHash as createHash2 } from "node:crypto";
|
|
115
115
|
import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync2, existsSync as existsSync5 } from "node:fs";
|
|
116
116
|
import { join as join6, relative, extname, resolve, basename as basename3 } from "node:path";
|
|
117
|
-
import { execSync } from "node:child_process";
|
|
117
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
118
118
|
function parseGitignore(rootPath) {
|
|
119
119
|
const patterns = new Set;
|
|
120
120
|
const gitignorePath = join6(rootPath, ".gitignore");
|
|
@@ -180,12 +180,12 @@ function detectProjectName(rootPath) {
|
|
|
180
180
|
}
|
|
181
181
|
function getChangedFiles(rootPath) {
|
|
182
182
|
try {
|
|
183
|
-
const committed =
|
|
183
|
+
const committed = execSync2("git diff --name-only HEAD 2>/dev/null || true", {
|
|
184
184
|
cwd: rootPath,
|
|
185
185
|
encoding: "utf-8",
|
|
186
186
|
stdio: ["pipe", "pipe", "pipe"]
|
|
187
187
|
}).trim();
|
|
188
|
-
const staged =
|
|
188
|
+
const staged = execSync2("git diff --name-only --cached 2>/dev/null || true", {
|
|
189
189
|
cwd: rootPath,
|
|
190
190
|
encoding: "utf-8",
|
|
191
191
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -436,7 +436,7 @@ __export(exports_api, {
|
|
|
436
436
|
ApiError: () => ApiError
|
|
437
437
|
});
|
|
438
438
|
async function apiRequest(path, apiKey, init) {
|
|
439
|
-
const res = await fetch(`${
|
|
439
|
+
const res = await fetch(`${API_URL2}${path}`, {
|
|
440
440
|
...init,
|
|
441
441
|
headers: {
|
|
442
442
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -593,7 +593,7 @@ async function uploadBulk(apiKey, memories, onProgress) {
|
|
|
593
593
|
}
|
|
594
594
|
return { success, failed, results };
|
|
595
595
|
}
|
|
596
|
-
var
|
|
596
|
+
var API_URL2 = "https://mcp.conare.ai", ApiError, PAGE_SIZE = 200, DELETE_CONCURRENCY = 5;
|
|
597
597
|
var init_api = __esm(() => {
|
|
598
598
|
ApiError = class ApiError extends Error {
|
|
599
599
|
statusCode;
|
|
@@ -1425,6 +1425,7 @@ __export(exports_interactive, {
|
|
|
1425
1425
|
showDetectedApps: () => showDetectedApps,
|
|
1426
1426
|
selectMcpTargets: () => selectMcpTargets,
|
|
1427
1427
|
selectChatSources: () => selectChatSources,
|
|
1428
|
+
promptAuth: () => promptAuth,
|
|
1428
1429
|
promptApiKey: () => promptApiKey,
|
|
1429
1430
|
finishSetup: () => finishSetup,
|
|
1430
1431
|
confirmIndexCodebase: () => confirmIndexCodebase,
|
|
@@ -1470,6 +1471,54 @@ async function promptApiKey(options) {
|
|
|
1470
1471
|
});
|
|
1471
1472
|
return ensureValue(keyPrompt).trim() || options.savedApiKey;
|
|
1472
1473
|
}
|
|
1474
|
+
async function promptAuth(options) {
|
|
1475
|
+
if (options.providedApiKey) {
|
|
1476
|
+
return options.providedApiKey;
|
|
1477
|
+
}
|
|
1478
|
+
if (options.savedApiKey) {
|
|
1479
|
+
const useSaved = await ye({
|
|
1480
|
+
message: `Use saved API key ending in ${options.savedApiKey.slice(-6)}?`,
|
|
1481
|
+
initialValue: true
|
|
1482
|
+
});
|
|
1483
|
+
if (pD(useSaved)) {
|
|
1484
|
+
xe("Setup cancelled.");
|
|
1485
|
+
process.exit(0);
|
|
1486
|
+
}
|
|
1487
|
+
if (useSaved)
|
|
1488
|
+
return options.savedApiKey;
|
|
1489
|
+
}
|
|
1490
|
+
const method = await ve({
|
|
1491
|
+
message: "How would you like to authenticate?",
|
|
1492
|
+
options: [
|
|
1493
|
+
{ value: "browser", label: "Sign in with browser", hint: "recommended" },
|
|
1494
|
+
{ value: "manual", label: "Enter API key manually" }
|
|
1495
|
+
]
|
|
1496
|
+
});
|
|
1497
|
+
if (pD(method)) {
|
|
1498
|
+
xe("Setup cancelled.");
|
|
1499
|
+
process.exit(0);
|
|
1500
|
+
}
|
|
1501
|
+
if (method === "browser") {
|
|
1502
|
+
return "__BROWSER_AUTH__";
|
|
1503
|
+
}
|
|
1504
|
+
const keyPrompt = await ge({
|
|
1505
|
+
message: "API key",
|
|
1506
|
+
mask: "*",
|
|
1507
|
+
validate(value) {
|
|
1508
|
+
const resolved = value.trim();
|
|
1509
|
+
if (!resolved)
|
|
1510
|
+
return "Enter an API key from https://mcp.conare.ai";
|
|
1511
|
+
if (!resolved.startsWith("cmem_"))
|
|
1512
|
+
return "API keys start with cmem_";
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
if (pD(keyPrompt)) {
|
|
1517
|
+
xe("Setup cancelled.");
|
|
1518
|
+
process.exit(0);
|
|
1519
|
+
}
|
|
1520
|
+
return keyPrompt.trim();
|
|
1521
|
+
}
|
|
1473
1522
|
async function selectChatSources(targets) {
|
|
1474
1523
|
return ensureValue(await fe({
|
|
1475
1524
|
message: "Select chat sources",
|
|
@@ -1630,26 +1679,98 @@ async function detect() {
|
|
|
1630
1679
|
path: cursorDbPath,
|
|
1631
1680
|
sessionCount: existsSync(cursorDbPath) ? await countCursorSessions(cursorDbPath) : 0
|
|
1632
1681
|
});
|
|
1633
|
-
const openclawDir = join(home, ".openclaw");
|
|
1634
|
-
tools.push({
|
|
1635
|
-
name: "OpenClaw",
|
|
1636
|
-
available: existsSync(openclawDir),
|
|
1637
|
-
path: openclawDir,
|
|
1638
|
-
sessionCount: 0
|
|
1639
|
-
});
|
|
1640
1682
|
return tools;
|
|
1641
1683
|
}
|
|
1642
1684
|
|
|
1685
|
+
// src/auth.ts
|
|
1686
|
+
import { execSync } from "node:child_process";
|
|
1687
|
+
import { platform as platform2 } from "node:os";
|
|
1688
|
+
var API_URL = "https://mcp.conare.ai";
|
|
1689
|
+
async function browserAuth() {
|
|
1690
|
+
const stateBytes = new Uint8Array(16);
|
|
1691
|
+
crypto.getRandomValues(stateBytes);
|
|
1692
|
+
const state = Array.from(stateBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1693
|
+
const sessionRes = await fetch(`${API_URL}/api/auth/cli-session`, {
|
|
1694
|
+
method: "POST",
|
|
1695
|
+
headers: { "Content-Type": "application/json" },
|
|
1696
|
+
body: JSON.stringify({ state })
|
|
1697
|
+
});
|
|
1698
|
+
if (!sessionRes.ok) {
|
|
1699
|
+
throw new Error(`Failed to create auth session: HTTP ${sessionRes.status}`);
|
|
1700
|
+
}
|
|
1701
|
+
const { code, expiresAt } = await sessionRes.json();
|
|
1702
|
+
const authUrl = `${API_URL}/cli-auth?code=${code}&state=${state}`;
|
|
1703
|
+
const opened = openBrowser(authUrl);
|
|
1704
|
+
if (!opened) {
|
|
1705
|
+
console.log("");
|
|
1706
|
+
console.log(" Open this URL in your browser to sign in:");
|
|
1707
|
+
console.log("");
|
|
1708
|
+
console.log(` ${authUrl}`);
|
|
1709
|
+
console.log("");
|
|
1710
|
+
}
|
|
1711
|
+
const timeout = Math.max(expiresAt - Date.now(), 0);
|
|
1712
|
+
const deadline = Date.now() + timeout;
|
|
1713
|
+
while (Date.now() < deadline) {
|
|
1714
|
+
await sleep(2000);
|
|
1715
|
+
const exchangeRes = await fetch(`${API_URL}/api/auth/cli-exchange`, {
|
|
1716
|
+
method: "POST",
|
|
1717
|
+
headers: { "Content-Type": "application/json" },
|
|
1718
|
+
body: JSON.stringify({ code, state })
|
|
1719
|
+
});
|
|
1720
|
+
if (exchangeRes.status === 202) {
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
if (exchangeRes.ok) {
|
|
1724
|
+
const data = await exchangeRes.json();
|
|
1725
|
+
return data.apiKey;
|
|
1726
|
+
}
|
|
1727
|
+
if (exchangeRes.status === 410) {
|
|
1728
|
+
throw new Error("Authentication code was already used. Please try again.");
|
|
1729
|
+
}
|
|
1730
|
+
if (exchangeRes.status === 404) {
|
|
1731
|
+
throw new Error("Authentication session expired. Please try again.");
|
|
1732
|
+
}
|
|
1733
|
+
throw new Error(`Authentication failed: HTTP ${exchangeRes.status}`);
|
|
1734
|
+
}
|
|
1735
|
+
throw new Error("Authentication timed out. Please try again.");
|
|
1736
|
+
}
|
|
1737
|
+
function openBrowser(url) {
|
|
1738
|
+
try {
|
|
1739
|
+
const os = platform2();
|
|
1740
|
+
if (os === "darwin") {
|
|
1741
|
+
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
1742
|
+
} else if (os === "win32") {
|
|
1743
|
+
execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
1744
|
+
} else {
|
|
1745
|
+
try {
|
|
1746
|
+
execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
1747
|
+
} catch {
|
|
1748
|
+
try {
|
|
1749
|
+
execSync(`wslview "${url}"`, { stdio: "ignore" });
|
|
1750
|
+
} catch {
|
|
1751
|
+
return false;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
return true;
|
|
1756
|
+
} catch {
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function sleep(ms) {
|
|
1761
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1643
1764
|
// src/ingest/claude.ts
|
|
1644
1765
|
init_shared();
|
|
1645
1766
|
import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
|
|
1646
1767
|
import { join as join3, basename } from "node:path";
|
|
1647
|
-
import { homedir as homedir3, platform as
|
|
1768
|
+
import { homedir as homedir3, platform as platform3 } from "node:os";
|
|
1648
1769
|
var MAX_CONTENT = 48000;
|
|
1649
1770
|
var MIN_TURN_LEN = 50;
|
|
1650
1771
|
function resolveProjectName(dirName) {
|
|
1651
1772
|
const segments = dirName.replace(/^-/, "").split("-");
|
|
1652
|
-
const isWindows =
|
|
1773
|
+
const isWindows = platform3() === "win32";
|
|
1653
1774
|
let resolved;
|
|
1654
1775
|
let startIdx;
|
|
1655
1776
|
if (isWindows && segments.length > 0 && /^[A-Za-z]$/.test(segments[0])) {
|
|
@@ -1735,6 +1856,22 @@ function parseSession(lines) {
|
|
|
1735
1856
|
})).filter((t) => t.assistant.length >= MIN_TURN_LEN);
|
|
1736
1857
|
return { turns, date };
|
|
1737
1858
|
}
|
|
1859
|
+
function getParentUuid(lines) {
|
|
1860
|
+
for (const line of lines) {
|
|
1861
|
+
if (!line.trim())
|
|
1862
|
+
continue;
|
|
1863
|
+
let obj;
|
|
1864
|
+
try {
|
|
1865
|
+
obj = JSON.parse(line);
|
|
1866
|
+
} catch {
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
if (obj && typeof obj === "object" && "parentUuid" in obj) {
|
|
1870
|
+
return obj.parentUuid;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1738
1875
|
function ingestClaude() {
|
|
1739
1876
|
const projectsDir = join3(homedir3(), ".claude", "projects");
|
|
1740
1877
|
const memories = [];
|
|
@@ -1759,8 +1896,14 @@ function ingestClaude() {
|
|
|
1759
1896
|
for (const file of files) {
|
|
1760
1897
|
const sessionId = basename(file, ".jsonl");
|
|
1761
1898
|
const raw = readFileSync3(join3(projPath, file), "utf-8");
|
|
1762
|
-
const
|
|
1763
|
-
`)
|
|
1899
|
+
const lines = raw.split(`
|
|
1900
|
+
`);
|
|
1901
|
+
const parentUuid = getParentUuid(lines);
|
|
1902
|
+
if (parentUuid != null) {
|
|
1903
|
+
filtered++;
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
const { turns, date } = parseSession(lines);
|
|
1764
1907
|
if (turns.length === 0) {
|
|
1765
1908
|
filtered++;
|
|
1766
1909
|
continue;
|
|
@@ -2137,17 +2280,16 @@ init_shared();
|
|
|
2137
2280
|
init_api();
|
|
2138
2281
|
|
|
2139
2282
|
// src/configure.ts
|
|
2140
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2283
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2, symlinkSync, readlinkSync, rmSync } from "node:fs";
|
|
2141
2284
|
import { dirname, join as join7 } from "node:path";
|
|
2142
|
-
import { homedir as homedir5, platform as
|
|
2285
|
+
import { homedir as homedir5, platform as platform5 } from "node:os";
|
|
2143
2286
|
import { spawnSync } from "node:child_process";
|
|
2144
2287
|
var CONARE_URL = "https://mcp.conare.ai";
|
|
2145
|
-
var SERVER_NAME = "conare
|
|
2288
|
+
var SERVER_NAME = "conare";
|
|
2146
2289
|
var MCP_TARGETS = [
|
|
2147
2290
|
{ id: "claude", label: "Claude Code" },
|
|
2148
2291
|
{ id: "cursor", label: "Cursor" },
|
|
2149
|
-
{ id: "codex", label: "Codex" }
|
|
2150
|
-
{ id: "openclaw", label: "OpenClaw" }
|
|
2292
|
+
{ id: "codex", label: "Codex" }
|
|
2151
2293
|
];
|
|
2152
2294
|
function readJsonFile(path) {
|
|
2153
2295
|
try {
|
|
@@ -2183,7 +2325,7 @@ function configureClaude(apiKey) {
|
|
|
2183
2325
|
const claudeMcpPath = join7(homedir5(), ".claude", "mcp.json");
|
|
2184
2326
|
if (spawnSync("claude", ["mcp", "add-json", SERVER_NAME, "--scope", "user", JSON.stringify(getServerConfig(apiKey))], {
|
|
2185
2327
|
stdio: "ignore",
|
|
2186
|
-
shell:
|
|
2328
|
+
shell: platform5() === "win32"
|
|
2187
2329
|
}).status === 0) {
|
|
2188
2330
|
return "Claude Code configured via `claude mcp add-json`";
|
|
2189
2331
|
}
|
|
@@ -2197,6 +2339,200 @@ function configureJsonClient(path, apiKey, label) {
|
|
|
2197
2339
|
upsertMcpServer(path, apiKey);
|
|
2198
2340
|
return `${label} configured at ${path}`;
|
|
2199
2341
|
}
|
|
2342
|
+
var SKILL_MD = `---
|
|
2343
|
+
name: conare
|
|
2344
|
+
description: Complements the Conare MCP server by teaching the agent when and how to load prior project context, search past sessions, save durable preferences, list stored memories, and forget saved items. Use when the user asks what they worked on before, wants prior context loaded at the start of a task, asks to remember or forget something, or needs past conversations, decisions, or code recalled from memory.
|
|
2345
|
+
compatibility: Requires the Conare MCP server tools (\`recall\`, \`search\`, \`save\`, \`list\`, \`forget\`) to be installed and connected.
|
|
2346
|
+
metadata:
|
|
2347
|
+
author: Conare
|
|
2348
|
+
version: 1.1.0
|
|
2349
|
+
mcp-server: conare
|
|
2350
|
+
homepage: https://conare.ai
|
|
2351
|
+
---
|
|
2352
|
+
|
|
2353
|
+
# Conare
|
|
2354
|
+
|
|
2355
|
+
This skill complements the Conare MCP server. The MCP provides memory tools and live access to stored memories; this skill teaches the agent the default workflow, tool-selection rules, and query patterns that make those tools reliable.
|
|
2356
|
+
|
|
2357
|
+
## Primary Use Cases
|
|
2358
|
+
|
|
2359
|
+
1. Start a new coding task with relevant history already loaded through \`recall\`.
|
|
2360
|
+
2. Answer questions about prior work, decisions, bugs, architecture, or preferences through \`search\`.
|
|
2361
|
+
3. Persist durable information the user wants carried into future sessions through \`save\`.
|
|
2362
|
+
|
|
2363
|
+
## When To Use Each Tool
|
|
2364
|
+
|
|
2365
|
+
| Situation | Tool | Example |
|
|
2366
|
+
|-----------|------|---------|
|
|
2367
|
+
| Start of conversation | \`recall\` | Always call first with conversation context |
|
|
2368
|
+
| User asks about past work | \`search\` | "What did we do last week?" |
|
|
2369
|
+
| User says "remember this" | \`save\` | Save preferences, rules, decisions |
|
|
2370
|
+
| User says "forget this" | \`forget\` | Remove a specific memory |
|
|
2371
|
+
| Browse what's stored | \`list\` | "Show me recent memories" |
|
|
2372
|
+
|
|
2373
|
+
## Critical Rules
|
|
2374
|
+
|
|
2375
|
+
1. **Always call \`recall\` at conversation start** — pass a specific description of the conversation topic, not generic text
|
|
2376
|
+
2. **\`search\` is global** — it searches ALL memories across all projects. Use it when the user asks about specific topics or past conversations
|
|
2377
|
+
3. **\`recall\` is scoped** — it returns project-relevant context + recent sessions + preferences. Use the \`project\` param when available
|
|
2378
|
+
4. **Write descriptive queries** — "how does the billing webhook handle refunds" beats "billing"
|
|
2379
|
+
5. **Rephrase and retry** — if first search misses, try different phrasing. Semantic search responds to synonyms and related concepts
|
|
2380
|
+
|
|
2381
|
+
## Workflow
|
|
2382
|
+
|
|
2383
|
+
### Step 1: Load context at the start
|
|
2384
|
+
|
|
2385
|
+
- Call \`recall\` at conversation start with a specific description of the current task.
|
|
2386
|
+
- Include the \`project\` parameter when the project is known or inferable from the workspace.
|
|
2387
|
+
- Use the returned context to avoid re-asking for things the user already told the agent in earlier sessions.
|
|
2388
|
+
|
|
2389
|
+
Expected outcome: the agent begins with recent sessions, saved preferences, and project-relevant memory already in context.
|
|
2390
|
+
|
|
2391
|
+
### Step 2: Search when the user asks about prior work
|
|
2392
|
+
|
|
2393
|
+
- Use \`search\` for questions about past conversations, earlier implementations, prior bugs, design decisions, or work done in a time range.
|
|
2394
|
+
- Start with a descriptive natural-language query.
|
|
2395
|
+
- If results are weak, retry with 2-3 rephrasings from different angles.
|
|
2396
|
+
|
|
2397
|
+
Expected outcome: the agent can cite or summarize the most relevant prior work without broad manual browsing.
|
|
2398
|
+
|
|
2399
|
+
### Step 3: Save durable facts intentionally
|
|
2400
|
+
|
|
2401
|
+
- Use \`save\` proactively for information that should persist across sessions: preferences, standing rules, important decisions, long-lived project facts, and user-specific context that will help in future work.
|
|
2402
|
+
- When the user shares durable context or says to remember something, prefer capturing it with \`save\` so future \`recall\` calls can surface it automatically.
|
|
2403
|
+
- Avoid cluttering memory with purely transient scratch notes unless the user explicitly wants them remembered.
|
|
2404
|
+
|
|
2405
|
+
Expected outcome: future \`recall\` calls surface the information automatically when relevant.
|
|
2406
|
+
|
|
2407
|
+
## Search Tips
|
|
2408
|
+
|
|
2409
|
+
- Use \`after\`/\`before\` (Unix ms) for time-scoped searches: "what did we work on this week?"
|
|
2410
|
+
- Use \`containerTag\` to filter by source: \`claude-chats\`, \`codex-chats\`, \`cursor-chats\`, \`codebase\`, \`preferences\`
|
|
2411
|
+
- Use \`project\` for cross-project filtering (partial names work: "conare" matches "fun/conare-memory-engine")
|
|
2412
|
+
- Keep \`limit\` low (3-5) for focused results, higher (10-15) for broad exploration
|
|
2413
|
+
- When user asks to "remember" something, save it with \`save\` — it goes to the \`preferences\` container and gets surfaced by \`recall\` in future sessions
|
|
2414
|
+
|
|
2415
|
+
## Examples
|
|
2416
|
+
|
|
2417
|
+
### Example 1: Start with context
|
|
2418
|
+
|
|
2419
|
+
User says: "Help me continue the OAuth migration in this repo."
|
|
2420
|
+
|
|
2421
|
+
Actions:
|
|
2422
|
+
1. Call \`recall\` with context like "continue OAuth migration in the current repository".
|
|
2423
|
+
2. Review returned memories for prior migration decisions, unresolved blockers, and saved preferences.
|
|
2424
|
+
3. Proceed with implementation using that context.
|
|
2425
|
+
|
|
2426
|
+
Result: the agent starts with the relevant project history instead of asking the user to restate it.
|
|
2427
|
+
|
|
2428
|
+
### Example 2: Find prior work
|
|
2429
|
+
|
|
2430
|
+
User says: "What did we decide last week about billing webhooks?"
|
|
2431
|
+
|
|
2432
|
+
Actions:
|
|
2433
|
+
1. Call \`search\` with a descriptive query such as "billing webhook decision refunds retries last week".
|
|
2434
|
+
2. If results are weak, retry with alternatives like "refund webhook handling" or "billing retry policy".
|
|
2435
|
+
3. Summarize the decision and note uncertainty if memories conflict.
|
|
2436
|
+
|
|
2437
|
+
Result: the agent retrieves prior decisions from memory rather than guessing.
|
|
2438
|
+
|
|
2439
|
+
### Example 3: Save a durable preference
|
|
2440
|
+
|
|
2441
|
+
User says: "Remember that I prefer ripgrep over grep."
|
|
2442
|
+
|
|
2443
|
+
Actions:
|
|
2444
|
+
1. Call \`save\` with the preference in durable wording.
|
|
2445
|
+
2. Confirm the preference was saved.
|
|
2446
|
+
|
|
2447
|
+
Result: future \`recall\` results can surface that preference automatically.
|
|
2448
|
+
|
|
2449
|
+
## Troubleshooting
|
|
2450
|
+
|
|
2451
|
+
### Weak or irrelevant search results
|
|
2452
|
+
|
|
2453
|
+
Cause:
|
|
2454
|
+
- Query is too short or too generic.
|
|
2455
|
+
- Search needs a different phrasing or time scope.
|
|
2456
|
+
|
|
2457
|
+
Response:
|
|
2458
|
+
1. Rewrite the query with concrete nouns, entities, and actions.
|
|
2459
|
+
2. Add \`after\`/\`before\` when the user implies a time window.
|
|
2460
|
+
3. Add \`project\` or \`containerTag\` when the scope is known.
|
|
2461
|
+
|
|
2462
|
+
### \`recall\` returns little useful context
|
|
2463
|
+
|
|
2464
|
+
Cause:
|
|
2465
|
+
- Conversation context passed to \`recall\` was too vague.
|
|
2466
|
+
- The project parameter was omitted when it should have been included.
|
|
2467
|
+
|
|
2468
|
+
Response:
|
|
2469
|
+
1. Retry \`recall\` with a more specific task description.
|
|
2470
|
+
2. Include \`project\` if the workspace or repository is known.
|
|
2471
|
+
3. Fall back to \`search\` for the exact topic the user cares about.
|
|
2472
|
+
|
|
2473
|
+
### Unsure whether to save something
|
|
2474
|
+
|
|
2475
|
+
Cause:
|
|
2476
|
+
- The information may be temporary rather than durable.
|
|
2477
|
+
|
|
2478
|
+
Response:
|
|
2479
|
+
1. Save it when it is likely to help in future sessions, especially if it reflects a preference, rule, decision, or durable project fact.
|
|
2480
|
+
2. Skip only clearly transient implementation notes unless the user explicitly asks to retain them.
|
|
2481
|
+
|
|
2482
|
+
## Triggering Tests
|
|
2483
|
+
|
|
2484
|
+
Use these tests to validate whether the skill description is tuned correctly.
|
|
2485
|
+
|
|
2486
|
+
Should trigger:
|
|
2487
|
+
- "What did we work on in this repo last week?"
|
|
2488
|
+
- "Load context for the auth refactor before we continue."
|
|
2489
|
+
- "Remember that I prefer pnpm in JavaScript projects."
|
|
2490
|
+
- "Forget the note about using staging Stripe keys."
|
|
2491
|
+
- "Search my past sessions for the DO migration fix."
|
|
2492
|
+
|
|
2493
|
+
Should not trigger:
|
|
2494
|
+
- "Write a sorting function in TypeScript."
|
|
2495
|
+
- "What is the weather in San Francisco?"
|
|
2496
|
+
- "Explain how PostgreSQL indexes work."
|
|
2497
|
+
- "Create a landing page from this mockup."
|
|
2498
|
+
|
|
2499
|
+
Success criteria:
|
|
2500
|
+
- Triggers on memory-oriented requests and start-of-task context loading.
|
|
2501
|
+
- Does not trigger on generic coding or research tasks with no memory component.
|
|
2502
|
+
- Uses \`recall\` first for new task continuation, and uses \`search\` only when the user asks about specific past work.
|
|
2503
|
+
|
|
2504
|
+
## Setup
|
|
2505
|
+
|
|
2506
|
+
Install with a single command:
|
|
2507
|
+
|
|
2508
|
+
\`\`\`bash
|
|
2509
|
+
bunx conare@latest
|
|
2510
|
+
\`\`\`
|
|
2511
|
+
|
|
2512
|
+
The wizard handles everything: account creation, API key, MCP configuration, background sync setup.
|
|
2513
|
+
|
|
2514
|
+
For manual setup, visit [conare.ai](https://conare.ai).
|
|
2515
|
+
`;
|
|
2516
|
+
function installSkill() {
|
|
2517
|
+
const skillDir = join7(homedir5(), ".agents", "skills", "conare");
|
|
2518
|
+
mkdirSync2(skillDir, { recursive: true });
|
|
2519
|
+
writeFileSync2(join7(skillDir, "SKILL.md"), SKILL_MD);
|
|
2520
|
+
const claudeSkillsDir = join7(homedir5(), ".claude", "skills");
|
|
2521
|
+
const claudeSkillDir = join7(claudeSkillsDir, "conare");
|
|
2522
|
+
try {
|
|
2523
|
+
if (existsSync6(claudeSkillsDir)) {
|
|
2524
|
+
if (existsSync6(claudeSkillDir)) {
|
|
2525
|
+
try {
|
|
2526
|
+
if (readlinkSync(claudeSkillDir) === skillDir)
|
|
2527
|
+
return "Agent Skill installed";
|
|
2528
|
+
} catch {}
|
|
2529
|
+
rmSync(claudeSkillDir, { recursive: true, force: true });
|
|
2530
|
+
}
|
|
2531
|
+
symlinkSync(skillDir, claudeSkillDir);
|
|
2532
|
+
}
|
|
2533
|
+
} catch {}
|
|
2534
|
+
return "Agent Skill installed";
|
|
2535
|
+
}
|
|
2200
2536
|
function configureMcp(apiKey, targets = ["claude", "cursor", "codex"]) {
|
|
2201
2537
|
const results = [];
|
|
2202
2538
|
if (targets.includes("claude")) {
|
|
@@ -2208,9 +2544,9 @@ function configureMcp(apiKey, targets = ["claude", "cursor", "codex"]) {
|
|
|
2208
2544
|
if (targets.includes("codex")) {
|
|
2209
2545
|
results.push(configureJsonClient(join7(homedir5(), ".codex", "mcp.json"), apiKey, "Codex"));
|
|
2210
2546
|
}
|
|
2211
|
-
|
|
2212
|
-
results.push(
|
|
2213
|
-
}
|
|
2547
|
+
try {
|
|
2548
|
+
results.push(installSkill());
|
|
2549
|
+
} catch {}
|
|
2214
2550
|
return results;
|
|
2215
2551
|
}
|
|
2216
2552
|
|
|
@@ -2229,20 +2565,35 @@ function readConfig() {
|
|
|
2229
2565
|
return {};
|
|
2230
2566
|
}
|
|
2231
2567
|
}
|
|
2568
|
+
function writeConfig(config) {
|
|
2569
|
+
mkdirSync3(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
2570
|
+
writeFileSync3(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
2571
|
+
`, { mode: 384 });
|
|
2572
|
+
}
|
|
2232
2573
|
function saveApiKey(apiKey) {
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2574
|
+
const config = readConfig();
|
|
2575
|
+
config.apiKey = apiKey;
|
|
2576
|
+
writeConfig(config);
|
|
2236
2577
|
}
|
|
2237
2578
|
function getSavedApiKey() {
|
|
2238
2579
|
return readConfig().apiKey;
|
|
2239
2580
|
}
|
|
2581
|
+
function addIndexedPath(absPath) {
|
|
2582
|
+
const config = readConfig();
|
|
2583
|
+
const paths = new Set(config.indexedPaths || []);
|
|
2584
|
+
paths.add(absPath);
|
|
2585
|
+
config.indexedPaths = [...paths];
|
|
2586
|
+
writeConfig(config);
|
|
2587
|
+
}
|
|
2588
|
+
function getIndexedPaths() {
|
|
2589
|
+
return readConfig().indexedPaths || [];
|
|
2590
|
+
}
|
|
2240
2591
|
|
|
2241
2592
|
// src/sync.ts
|
|
2242
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync9, chmodSync, cpSync, rmSync, symlinkSync, readlinkSync, appendFileSync } from "node:fs";
|
|
2593
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync9, chmodSync, cpSync, rmSync as rmSync2, symlinkSync as symlinkSync2, readlinkSync as readlinkSync2, appendFileSync } from "node:fs";
|
|
2243
2594
|
import { join as join9, dirname as dirname2 } from "node:path";
|
|
2244
|
-
import { homedir as homedir7, platform as
|
|
2245
|
-
import { execSync as
|
|
2595
|
+
import { homedir as homedir7, platform as platform6 } from "node:os";
|
|
2596
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
2246
2597
|
var CONARE_DIR = join9(homedir7(), ".conare");
|
|
2247
2598
|
var BIN_DIR = join9(CONARE_DIR, "bin");
|
|
2248
2599
|
var CONFIG_PATH2 = join9(CONARE_DIR, "config.json");
|
|
@@ -2398,7 +2749,7 @@ WantedBy=timers.target
|
|
|
2398
2749
|
}
|
|
2399
2750
|
function hasSystemd() {
|
|
2400
2751
|
try {
|
|
2401
|
-
|
|
2752
|
+
execSync3("systemctl --user status 2>/dev/null", { stdio: "ignore" });
|
|
2402
2753
|
return true;
|
|
2403
2754
|
} catch {
|
|
2404
2755
|
return false;
|
|
@@ -2406,7 +2757,7 @@ function hasSystemd() {
|
|
|
2406
2757
|
}
|
|
2407
2758
|
function uid() {
|
|
2408
2759
|
try {
|
|
2409
|
-
return
|
|
2760
|
+
return execSync3("id -u", { encoding: "utf-8" }).trim();
|
|
2410
2761
|
} catch {
|
|
2411
2762
|
return "501";
|
|
2412
2763
|
}
|
|
@@ -2438,7 +2789,7 @@ function persistBinary(apiKey) {
|
|
|
2438
2789
|
const runVbsPath = join9(BIN_DIR, "run.vbs");
|
|
2439
2790
|
writeFileSync4(runVbsPath, RUN_VBS);
|
|
2440
2791
|
writeFileSync4(CONFIG_PATH2, JSON.stringify({ apiKey }, null, 2) + `
|
|
2441
|
-
|
|
2792
|
+
`, { mode: 384 });
|
|
2442
2793
|
}
|
|
2443
2794
|
function isValidJsBundle(path) {
|
|
2444
2795
|
if (!existsSync8(path))
|
|
@@ -2483,13 +2834,13 @@ function setupMacOS(intervalMinutes) {
|
|
|
2483
2834
|
writeFileSync4(PLIST_PATH, makePlist(intervalMinutes));
|
|
2484
2835
|
const id = uid();
|
|
2485
2836
|
try {
|
|
2486
|
-
|
|
2837
|
+
execSync3(`launchctl bootout gui/${id} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
|
|
2487
2838
|
} catch {}
|
|
2488
2839
|
try {
|
|
2489
|
-
|
|
2840
|
+
execSync3(`launchctl bootstrap gui/${id} "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
2490
2841
|
} catch {
|
|
2491
2842
|
try {
|
|
2492
|
-
|
|
2843
|
+
execSync3(`launchctl load "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
2493
2844
|
} catch {
|
|
2494
2845
|
throw new Error("Failed to load launchd agent. Try manually: launchctl load " + PLIST_PATH);
|
|
2495
2846
|
}
|
|
@@ -2499,28 +2850,28 @@ function setupLinuxSystemd(intervalMinutes) {
|
|
|
2499
2850
|
mkdirSync4(SYSTEMD_DIR, { recursive: true });
|
|
2500
2851
|
writeFileSync4(SYSTEMD_SERVICE, SYSTEMD_SERVICE_CONTENT);
|
|
2501
2852
|
writeFileSync4(SYSTEMD_TIMER, makeSystemdTimer(intervalMinutes));
|
|
2502
|
-
|
|
2503
|
-
|
|
2853
|
+
execSync3("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
2854
|
+
execSync3("systemctl --user enable --now conare-sync.timer", { stdio: "ignore" });
|
|
2504
2855
|
}
|
|
2505
2856
|
function setupLinuxCron(intervalMinutes) {
|
|
2506
2857
|
const cronCmd = `${homedir7()}/.conare/bin/run.sh`;
|
|
2507
2858
|
const cronLine = `*/${intervalMinutes} * * * * ${cronCmd}`;
|
|
2508
2859
|
try {
|
|
2509
|
-
const existing =
|
|
2860
|
+
const existing = execSync3("crontab -l 2>/dev/null", { encoding: "utf-8" });
|
|
2510
2861
|
const filtered = existing.split(`
|
|
2511
2862
|
`).filter((l) => !l.includes("conare")).join(`
|
|
2512
2863
|
`);
|
|
2513
2864
|
const newCrontab = (filtered.trim() ? filtered.trim() + `
|
|
2514
2865
|
` : "") + cronLine + `
|
|
2515
2866
|
`;
|
|
2516
|
-
|
|
2867
|
+
execSync3("crontab -", { input: newCrontab, stdio: ["pipe", "ignore", "ignore"] });
|
|
2517
2868
|
} catch {
|
|
2518
|
-
|
|
2869
|
+
execSync3("crontab -", { input: cronLine + `
|
|
2519
2870
|
`, stdio: ["pipe", "ignore", "ignore"] });
|
|
2520
2871
|
}
|
|
2521
2872
|
}
|
|
2522
2873
|
function installGlobalCommand() {
|
|
2523
|
-
const isWindows =
|
|
2874
|
+
const isWindows = platform6() === "win32";
|
|
2524
2875
|
if (isWindows) {
|
|
2525
2876
|
const wrapper2 = join9(BIN_DIR, "conare.cmd");
|
|
2526
2877
|
const content2 = `@echo off\r
|
|
@@ -2538,7 +2889,7 @@ node "%USERPROFILE%\\.conare\\bin\\conare-ingest.mjs" %*\r
|
|
|
2538
2889
|
}
|
|
2539
2890
|
const binDirWin = BIN_DIR.replace(/\//g, "\\");
|
|
2540
2891
|
try {
|
|
2541
|
-
|
|
2892
|
+
execSync3(`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" });
|
|
2542
2893
|
return `Global command: conare (added .conare\\bin to user PATH — restart terminal)`;
|
|
2543
2894
|
} catch {
|
|
2544
2895
|
return `Global command: add ${binDirWin} to your PATH manually`;
|
|
@@ -2567,13 +2918,13 @@ exec "$NODE" "$CONARE_DIR/bin/conare-ingest.mjs" "$@"
|
|
|
2567
2918
|
try {
|
|
2568
2919
|
if (existsSync8(symlinkTarget)) {
|
|
2569
2920
|
try {
|
|
2570
|
-
const existing =
|
|
2921
|
+
const existing = readlinkSync2(symlinkTarget);
|
|
2571
2922
|
if (existing === wrapper)
|
|
2572
2923
|
return "Global command: conare (already linked)";
|
|
2573
2924
|
} catch {}
|
|
2574
2925
|
unlinkSync(symlinkTarget);
|
|
2575
2926
|
}
|
|
2576
|
-
|
|
2927
|
+
symlinkSync2(wrapper, symlinkTarget);
|
|
2577
2928
|
return "Global command: conare (linked to /usr/local/bin)";
|
|
2578
2929
|
} catch {
|
|
2579
2930
|
const pathDirs = (process.env.PATH || "").split(":");
|
|
@@ -2607,7 +2958,7 @@ function getShellProfile() {
|
|
|
2607
2958
|
return join9(home, ".zshrc");
|
|
2608
2959
|
if (shell.includes("bash")) {
|
|
2609
2960
|
const profile = join9(home, ".bash_profile");
|
|
2610
|
-
if (
|
|
2961
|
+
if (platform6() === "darwin" && existsSync8(profile))
|
|
2611
2962
|
return profile;
|
|
2612
2963
|
return join9(home, ".bashrc");
|
|
2613
2964
|
}
|
|
@@ -2620,9 +2971,9 @@ function getShellProfile() {
|
|
|
2620
2971
|
function setupWindows(intervalMinutes) {
|
|
2621
2972
|
const runVbs = join9(BIN_DIR, "run.vbs").replace(/\//g, "\\");
|
|
2622
2973
|
try {
|
|
2623
|
-
|
|
2974
|
+
execSync3(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
|
|
2624
2975
|
} catch {}
|
|
2625
|
-
|
|
2976
|
+
execSync3(`schtasks /Create /TN "${TASK_NAME}" /TR "wscript.exe \\"${runVbs}\\"" /SC MINUTE /MO ${intervalMinutes} /F`, { stdio: "ignore" });
|
|
2626
2977
|
}
|
|
2627
2978
|
function persistAndInstallGlobal(apiKey) {
|
|
2628
2979
|
const messages = [];
|
|
@@ -2636,7 +2987,7 @@ function persistAndInstallGlobal(apiKey) {
|
|
|
2636
2987
|
}
|
|
2637
2988
|
function installSync(apiKey, intervalMinutes = 10) {
|
|
2638
2989
|
const messages = persistAndInstallGlobal(apiKey);
|
|
2639
|
-
const os =
|
|
2990
|
+
const os = platform6();
|
|
2640
2991
|
if (os === "darwin") {
|
|
2641
2992
|
setupMacOS(intervalMinutes);
|
|
2642
2993
|
messages.push(`Installed launchd agent (every ${intervalMinutes} min)`);
|
|
@@ -2659,10 +3010,10 @@ function installSync(apiKey, intervalMinutes = 10) {
|
|
|
2659
3010
|
}
|
|
2660
3011
|
function uninstallSync() {
|
|
2661
3012
|
const messages = [];
|
|
2662
|
-
const os =
|
|
3013
|
+
const os = platform6();
|
|
2663
3014
|
if (os === "darwin") {
|
|
2664
3015
|
try {
|
|
2665
|
-
|
|
3016
|
+
execSync3(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
|
|
2666
3017
|
} catch {}
|
|
2667
3018
|
if (existsSync8(PLIST_PATH)) {
|
|
2668
3019
|
unlinkSync(PLIST_PATH);
|
|
@@ -2670,29 +3021,29 @@ function uninstallSync() {
|
|
|
2670
3021
|
}
|
|
2671
3022
|
} else if (os === "win32") {
|
|
2672
3023
|
try {
|
|
2673
|
-
|
|
3024
|
+
execSync3(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
|
|
2674
3025
|
messages.push("Removed Windows scheduled task");
|
|
2675
3026
|
} catch {}
|
|
2676
3027
|
} else if (os === "linux") {
|
|
2677
3028
|
if (hasSystemd()) {
|
|
2678
3029
|
try {
|
|
2679
|
-
|
|
3030
|
+
execSync3("systemctl --user disable --now conare-sync.timer 2>/dev/null", { stdio: "ignore" });
|
|
2680
3031
|
} catch {}
|
|
2681
3032
|
if (existsSync8(SYSTEMD_SERVICE))
|
|
2682
3033
|
unlinkSync(SYSTEMD_SERVICE);
|
|
2683
3034
|
if (existsSync8(SYSTEMD_TIMER))
|
|
2684
3035
|
unlinkSync(SYSTEMD_TIMER);
|
|
2685
3036
|
try {
|
|
2686
|
-
|
|
3037
|
+
execSync3("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
2687
3038
|
} catch {}
|
|
2688
3039
|
messages.push("Removed systemd timer");
|
|
2689
3040
|
} else {
|
|
2690
3041
|
try {
|
|
2691
|
-
const existing =
|
|
3042
|
+
const existing = execSync3("crontab -l 2>/dev/null", { encoding: "utf-8" });
|
|
2692
3043
|
const filtered = existing.split(`
|
|
2693
3044
|
`).filter((l) => !l.includes("conare")).join(`
|
|
2694
3045
|
`);
|
|
2695
|
-
|
|
3046
|
+
execSync3("crontab -", { input: filtered.trim() + `
|
|
2696
3047
|
`, stdio: ["pipe", "ignore", "ignore"] });
|
|
2697
3048
|
messages.push("Removed cron job");
|
|
2698
3049
|
} catch {}
|
|
@@ -2707,7 +3058,7 @@ function uninstallSync() {
|
|
|
2707
3058
|
unlinkSync(f);
|
|
2708
3059
|
}
|
|
2709
3060
|
if (existsSync8(BIN_DIR)) {
|
|
2710
|
-
|
|
3061
|
+
rmSync2(BIN_DIR, { recursive: true, force: true });
|
|
2711
3062
|
messages.push("Removed ~/.conare/bin/");
|
|
2712
3063
|
}
|
|
2713
3064
|
if (messages.length === 0) {
|
|
@@ -2827,13 +3178,13 @@ function parseArgs() {
|
|
|
2827
3178
|
conare — AI memory for your coding tools
|
|
2828
3179
|
|
|
2829
3180
|
Usage:
|
|
2830
|
-
conare
|
|
2831
|
-
conare install
|
|
2832
|
-
conare --key <api_key> Ingest chat history
|
|
3181
|
+
conare Interactive setup with browser auth
|
|
3182
|
+
conare install Just install the MCP
|
|
3183
|
+
conare --key <api_key> Ingest chat history (key optional with browser auth)
|
|
2833
3184
|
conare --key <api_key> --index [path] Index codebase
|
|
2834
3185
|
|
|
2835
3186
|
Options:
|
|
2836
|
-
--key <key> Your Conare API key (
|
|
3187
|
+
--key <key> Your Conare API key (optional if using browser auth, starts with cmem_)
|
|
2837
3188
|
--config-file <path> Read API key from JSON config file (e.g. ~/.conare/config.json)
|
|
2838
3189
|
--index [path] Index codebase at path (default: current directory)
|
|
2839
3190
|
--project <name> Project name for codebase indexing (auto-detected if omitted)
|
|
@@ -2877,8 +3228,22 @@ async function runInstall() {
|
|
|
2877
3228
|
let apiKey = key || process.env.CONARE_API_KEY || savedApiKey;
|
|
2878
3229
|
const hasTty = !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
2879
3230
|
if (!apiKey && hasTty) {
|
|
2880
|
-
const {
|
|
2881
|
-
|
|
3231
|
+
const { promptAuth: promptAuthFn } = await Promise.resolve().then(() => (init_interactive(), exports_interactive));
|
|
3232
|
+
const { spinner: clackSpinner } = await Promise.resolve().then(() => (init_interactive(), exports_interactive));
|
|
3233
|
+
const authResult = await promptAuthFn({ savedApiKey });
|
|
3234
|
+
if (authResult === "__BROWSER_AUTH__") {
|
|
3235
|
+
const authSpinner = clackSpinner();
|
|
3236
|
+
authSpinner.start("Waiting for browser sign-in...");
|
|
3237
|
+
try {
|
|
3238
|
+
apiKey = await browserAuth();
|
|
3239
|
+
authSpinner.stop("Signed in successfully.");
|
|
3240
|
+
} catch (e2) {
|
|
3241
|
+
authSpinner.stop(`Authentication failed: ${e2.message}`);
|
|
3242
|
+
process.exit(1);
|
|
3243
|
+
}
|
|
3244
|
+
} else {
|
|
3245
|
+
apiKey = authResult || "";
|
|
3246
|
+
}
|
|
2882
3247
|
}
|
|
2883
3248
|
if (!apiKey) {
|
|
2884
3249
|
printMissingKeyError();
|
|
@@ -2941,7 +3306,7 @@ async function main() {
|
|
|
2941
3306
|
if (shouldRunInteractive) {
|
|
2942
3307
|
const detectedTools = await detect();
|
|
2943
3308
|
interactiveTargets = MCP_TARGETS.map((target) => {
|
|
2944
|
-
const detected = detectedTools.find((tool) => target.id === "claude" && tool.name === "Claude Code" || target.id === "cursor" && tool.name === "Cursor" || target.id === "codex" && tool.name === "Codex"
|
|
3309
|
+
const detected = detectedTools.find((tool) => target.id === "claude" && tool.name === "Claude Code" || target.id === "cursor" && tool.name === "Cursor" || target.id === "codex" && tool.name === "Codex");
|
|
2945
3310
|
return {
|
|
2946
3311
|
id: target.id,
|
|
2947
3312
|
label: target.label,
|
|
@@ -2951,10 +3316,23 @@ async function main() {
|
|
|
2951
3316
|
};
|
|
2952
3317
|
});
|
|
2953
3318
|
startSetup();
|
|
2954
|
-
|
|
3319
|
+
const authResult = await promptAuth({
|
|
2955
3320
|
savedApiKey,
|
|
2956
3321
|
providedApiKey: opts.key
|
|
2957
|
-
})
|
|
3322
|
+
});
|
|
3323
|
+
if (authResult === "__BROWSER_AUTH__") {
|
|
3324
|
+
const authSpinner = Y2();
|
|
3325
|
+
authSpinner.start("Waiting for browser sign-in...");
|
|
3326
|
+
try {
|
|
3327
|
+
apiKey = await browserAuth();
|
|
3328
|
+
authSpinner.stop("Signed in successfully.");
|
|
3329
|
+
} catch (e2) {
|
|
3330
|
+
authSpinner.stop(`Authentication failed: ${e2.message}`);
|
|
3331
|
+
process.exit(1);
|
|
3332
|
+
}
|
|
3333
|
+
} else {
|
|
3334
|
+
apiKey = authResult || apiKey;
|
|
3335
|
+
}
|
|
2958
3336
|
showDetectedApps(interactiveTargets);
|
|
2959
3337
|
selectedSources = await selectChatSources(interactiveTargets);
|
|
2960
3338
|
interactiveMode = true;
|
|
@@ -2966,8 +3344,20 @@ async function main() {
|
|
|
2966
3344
|
return;
|
|
2967
3345
|
}
|
|
2968
3346
|
if (!apiKey) {
|
|
2969
|
-
|
|
2970
|
-
|
|
3347
|
+
if (hasTty) {
|
|
3348
|
+
const authSpinner = Y2();
|
|
3349
|
+
authSpinner.start("Waiting for browser sign-in...");
|
|
3350
|
+
try {
|
|
3351
|
+
apiKey = await browserAuth();
|
|
3352
|
+
authSpinner.stop("Signed in successfully.");
|
|
3353
|
+
} catch (e2) {
|
|
3354
|
+
authSpinner.stop(`Authentication failed: ${e2.message}`);
|
|
3355
|
+
process.exit(1);
|
|
3356
|
+
}
|
|
3357
|
+
} else {
|
|
3358
|
+
printMissingKeyError();
|
|
3359
|
+
process.exit(1);
|
|
3360
|
+
}
|
|
2971
3361
|
}
|
|
2972
3362
|
if (opts.installSync) {
|
|
2973
3363
|
const auth2 = await validateKey(apiKey);
|
|
@@ -3090,6 +3480,7 @@ Nothing new to index.`);
|
|
|
3090
3480
|
write(renderProgressSummary(success, failed, "indexed"));
|
|
3091
3481
|
const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
|
|
3092
3482
|
markIngested("codebase", fileHashes);
|
|
3483
|
+
addIndexedPath(absPath);
|
|
3093
3484
|
if (!opts.quiet)
|
|
3094
3485
|
printFailureSummary(results, memories);
|
|
3095
3486
|
}
|
|
@@ -3221,6 +3612,24 @@ Nothing new to index.`);
|
|
|
3221
3612
|
}
|
|
3222
3613
|
}
|
|
3223
3614
|
log();
|
|
3615
|
+
if (effectiveIngestOnly && !opts.dryRun) {
|
|
3616
|
+
const savedPaths = getIndexedPaths();
|
|
3617
|
+
for (const savedPath of savedPaths) {
|
|
3618
|
+
if (!existsSync9(savedPath))
|
|
3619
|
+
continue;
|
|
3620
|
+
try {
|
|
3621
|
+
const { memories: codeMemories, project } = indexCodebase(savedPath, { changedOnly: true });
|
|
3622
|
+
if (codeMemories.length === 0)
|
|
3623
|
+
continue;
|
|
3624
|
+
log(` Re-indexing ${project}: ${codeMemories.length} changed files`);
|
|
3625
|
+
const { success, failed, results } = await uploadBulk(apiKey, codeMemories, () => {});
|
|
3626
|
+
if (!opts.quiet)
|
|
3627
|
+
log(` ${project}: ${success} indexed, ${failed} failed`);
|
|
3628
|
+
const fileHashes = results.filter((r2) => r2.success).map((r2) => getManifestFingerprint(codeMemories[r2.index])).filter((k3) => !!k3);
|
|
3629
|
+
markIngested("codebase", fileHashes);
|
|
3630
|
+
} catch {}
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3224
3633
|
if (interactiveMode) {
|
|
3225
3634
|
selectedTargets = await selectMcpTargets(interactiveTargets);
|
|
3226
3635
|
const shouldIndexCurrentCodebase = await confirmIndexCodebase();
|
|
@@ -3264,6 +3673,7 @@ Nothing new to index.`);
|
|
|
3264
3673
|
write(renderProgressSummary(success, failed, "indexed"));
|
|
3265
3674
|
const fileHashes = results.filter((result) => result.success).map((result) => getManifestFingerprint(memories[result.index])).filter((key) => !!key);
|
|
3266
3675
|
markIngested("codebase", fileHashes);
|
|
3676
|
+
addIndexedPath(absPath);
|
|
3267
3677
|
if (!opts.quiet)
|
|
3268
3678
|
printFailureSummary(results, memories);
|
|
3269
3679
|
}
|