conare 0.0.1 → 0.0.3

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 +318 -14
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2,6 +2,10 @@
2
2
  import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
+ // src/index.ts
6
+ import { existsSync as existsSync7 } from "node:fs";
7
+ import { join as join9 } from "node:path";
8
+
5
9
  // src/detect.ts
6
10
  import { existsSync, readdirSync } from "node:fs";
7
11
  import { join } from "node:path";
@@ -876,16 +880,201 @@ async function uploadBulk(apiKey, memories, onProgress) {
876
880
  return { success, failed, results };
877
881
  }
878
882
 
883
+ // src/configure.ts
884
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
885
+ import { dirname, join as join7 } from "node:path";
886
+ import { homedir as homedir5 } from "node:os";
887
+ import { spawnSync } from "node:child_process";
888
+ var CONARE_URL = "https://mcp.conare.ai";
889
+ var SERVER_NAME = "conare-memory";
890
+ var MCP_TARGETS = [
891
+ { id: "claude", label: "Claude Code" },
892
+ { id: "cursor", label: "Cursor" },
893
+ { id: "codex", label: "Codex" }
894
+ ];
895
+ function readJsonFile(path) {
896
+ try {
897
+ return JSON.parse(readFileSync6(path, "utf-8"));
898
+ } catch {
899
+ return {};
900
+ }
901
+ }
902
+ function writeJsonFile(path, data) {
903
+ mkdirSync2(dirname(path), { recursive: true });
904
+ writeFileSync2(path, JSON.stringify(data, null, 2) + `
905
+ `);
906
+ }
907
+ function getServerConfig(apiKey) {
908
+ return {
909
+ type: "http",
910
+ url: `${CONARE_URL}/mcp`,
911
+ headers: {
912
+ Authorization: `Bearer ${apiKey}`
913
+ }
914
+ };
915
+ }
916
+ function upsertMcpServer(path, apiKey) {
917
+ const config = readJsonFile(path);
918
+ if (!config.mcpServers || typeof config.mcpServers !== "object") {
919
+ config.mcpServers = {};
920
+ }
921
+ config.mcpServers[SERVER_NAME] = getServerConfig(apiKey);
922
+ writeJsonFile(path, config);
923
+ }
924
+ function configureClaude(apiKey) {
925
+ const claudeConfigPath = join7(homedir5(), ".claude.json");
926
+ const claudeMcpPath = join7(homedir5(), ".claude", "mcp.json");
927
+ if (spawnSync("claude", ["mcp", "add-json", SERVER_NAME, "--scope", "user", JSON.stringify(getServerConfig(apiKey))], {
928
+ stdio: "ignore"
929
+ }).status === 0) {
930
+ return "Claude Code configured via `claude mcp add-json`";
931
+ }
932
+ upsertMcpServer(claudeConfigPath, apiKey);
933
+ if (existsSync5(join7(homedir5(), ".claude"))) {
934
+ upsertMcpServer(claudeMcpPath, apiKey);
935
+ }
936
+ return `Claude Code configured at ${claudeConfigPath}`;
937
+ }
938
+ function configureJsonClient(path, apiKey, label) {
939
+ upsertMcpServer(path, apiKey);
940
+ return `${label} configured at ${path}`;
941
+ }
942
+ function configureMcp(apiKey, targets = ["claude", "cursor", "codex"]) {
943
+ const results = [];
944
+ if (targets.includes("claude")) {
945
+ results.push(configureClaude(apiKey));
946
+ }
947
+ if (targets.includes("cursor")) {
948
+ results.push(configureJsonClient(join7(homedir5(), ".cursor", "mcp.json"), apiKey, "Cursor"));
949
+ }
950
+ if (targets.includes("codex")) {
951
+ results.push(configureJsonClient(join7(homedir5(), ".codex", "mcp.json"), apiKey, "Codex"));
952
+ }
953
+ return results;
954
+ }
955
+
956
+ // src/config.ts
957
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
958
+ import { join as join8 } from "node:path";
959
+ import { homedir as homedir6 } from "node:os";
960
+ var CONFIG_DIR = join8(homedir6(), ".conare");
961
+ var CONFIG_PATH = join8(CONFIG_DIR, "config.json");
962
+ function readConfig() {
963
+ try {
964
+ if (!existsSync6(CONFIG_PATH))
965
+ return {};
966
+ return JSON.parse(readFileSync7(CONFIG_PATH, "utf-8"));
967
+ } catch {
968
+ return {};
969
+ }
970
+ }
971
+ function saveApiKey(apiKey) {
972
+ mkdirSync3(CONFIG_DIR, { recursive: true });
973
+ writeFileSync3(CONFIG_PATH, JSON.stringify({ apiKey }, null, 2) + `
974
+ `);
975
+ }
976
+ function getSavedApiKey() {
977
+ return readConfig().apiKey;
978
+ }
979
+
980
+ // src/interactive.ts
981
+ import { stdin as input, stdout as output } from "node:process";
982
+ import { createInterface } from "node:readline/promises";
983
+ function normalizeYesNo(value, defaultValue) {
984
+ const normalized = value.trim().toLowerCase();
985
+ if (!normalized)
986
+ return defaultValue;
987
+ if (["y", "yes"].includes(normalized))
988
+ return true;
989
+ if (["n", "no"].includes(normalized))
990
+ return false;
991
+ return defaultValue;
992
+ }
993
+ async function runInteractiveSetup(options) {
994
+ const rl = createInterface({ input, output });
995
+ try {
996
+ output.write(`
997
+ Conare setup
998
+
999
+ `);
1000
+ const apiKeyPrompt = options.savedApiKey ? `API key [press enter to use saved key ending in ${options.savedApiKey.slice(-6)}]: ` : "API key: ";
1001
+ const apiKeyAnswer = await rl.question(apiKeyPrompt);
1002
+ const apiKey = apiKeyAnswer.trim() || options.savedApiKey;
1003
+ output.write(`
1004
+ Clients to configure:
1005
+ `);
1006
+ options.detectedTargets.forEach((target, index) => {
1007
+ const availability = target.available === false ? " (not detected locally)" : "";
1008
+ const recommendation = target.recommended ? " [default]" : "";
1009
+ output.write(` ${index + 1}. ${target.label}${recommendation}${availability}
1010
+ `);
1011
+ });
1012
+ const defaultSelection = options.detectedTargets.map((target, index) => target.recommended ? String(index + 1) : null).filter((value) => !!value).join(",");
1013
+ const targetAnswer = await rl.question(`Select clients [${defaultSelection || "none"}]: `);
1014
+ const selectedIndexes = (targetAnswer.trim() || defaultSelection).split(",").map((part) => parseInt(part.trim(), 10)).filter((value) => Number.isFinite(value) && value >= 1 && value <= options.detectedTargets.length);
1015
+ const configureTargets = [...new Set(selectedIndexes)].map((index) => options.detectedTargets[index - 1]?.id).filter((value) => !!value);
1016
+ const ingestChats = normalizeYesNo(await rl.question("Ingest chat history? [Y/n]: "), true);
1017
+ const indexCodebase2 = normalizeYesNo(await rl.question("Index current codebase too? [y/N]: "), false);
1018
+ let indexPath;
1019
+ if (indexCodebase2) {
1020
+ const pathAnswer = await rl.question("Codebase path [.]: ");
1021
+ indexPath = pathAnswer.trim() || ".";
1022
+ }
1023
+ output.write(`
1024
+ `);
1025
+ return {
1026
+ apiKey,
1027
+ configureTargets,
1028
+ ingestChats,
1029
+ indexCodebase: indexCodebase2,
1030
+ indexPath
1031
+ };
1032
+ } finally {
1033
+ rl.close();
1034
+ }
1035
+ }
1036
+
879
1037
  // src/index.ts
880
1038
  function getDedupKey(memory) {
881
1039
  const metadata = memory.metadata;
882
1040
  return metadata?.sessionId || metadata?.fileHash || null;
883
1041
  }
1042
+ function printMissingKeyError() {
1043
+ console.error("Error: no API key configured.");
1044
+ console.error("");
1045
+ console.error("Run one of these:");
1046
+ console.error(" bunx conare@latest --key YOUR_API_KEY");
1047
+ console.error(" npx conare@latest --key YOUR_API_KEY");
1048
+ console.error("");
1049
+ console.error("For a built-in command, install globally:");
1050
+ console.error(" bun add -g conare");
1051
+ console.error(" npm i -g conare");
1052
+ console.error("");
1053
+ console.error("Get your API key at https://mcp.conare.ai");
1054
+ }
1055
+ function printFailureSummary(results, memories) {
1056
+ const failures = results.map((result, index) => ({ ...result, memory: memories[index] })).filter((result) => !result.success);
1057
+ if (failures.length === 0)
1058
+ return;
1059
+ console.log("");
1060
+ console.log(" Failure summary:");
1061
+ for (const failure of failures.slice(0, 3)) {
1062
+ const metadata = failure.memory.metadata;
1063
+ const label = metadata?.filePath || metadata?.sessionId || failure.memory.containerTag;
1064
+ console.log(` - ${label}: ${failure.error || "Upload failed"}`);
1065
+ }
1066
+ if (failures.length > 3) {
1067
+ console.log(` - ... and ${failures.length - 3} more`);
1068
+ }
1069
+ }
884
1070
  function parseArgs() {
885
1071
  const args = process.argv.slice(2);
886
1072
  let key = "";
887
1073
  let dryRun = false;
888
1074
  let force = false;
1075
+ let ingestOnly = false;
1076
+ let configOnly = false;
1077
+ let interactive = false;
889
1078
  let source;
890
1079
  let wasmDir;
891
1080
  let indexPath;
@@ -902,7 +1091,13 @@ function parseArgs() {
902
1091
  wasmDir = args[++i];
903
1092
  } else if (args[i] === "--index") {
904
1093
  indexPath = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : ".";
905
- } else if (args[i] === "--ingest-only") {} else if (args[i] === "--config-only") {} else if (args[i] === "--help" || args[i] === "-h") {
1094
+ } else if (args[i] === "--ingest-only") {
1095
+ ingestOnly = true;
1096
+ } else if (args[i] === "--config-only") {
1097
+ configOnly = true;
1098
+ } else if (args[i] === "--interactive") {
1099
+ interactive = true;
1100
+ } else if (args[i] === "--help" || args[i] === "-h") {
906
1101
  console.log(`
907
1102
  conare — Ingest AI chat history into Conare
908
1103
 
@@ -915,6 +1110,9 @@ Options:
915
1110
  --index [path] Index codebase at path (default: current directory)
916
1111
  --dry-run Preview what would be uploaded
917
1112
  --force Re-ingest all / re-index all (bypass dedup)
1113
+ --ingest-only Ingest memories without MCP configuration
1114
+ --config-only Configure MCP only, skip ingestion
1115
+ --interactive Run guided setup prompts
918
1116
  --source <name> Only ingest from: claude, codex, cursor
919
1117
  --wasm-dir <path> Path to sql.js module (for Cursor ingestion)
920
1118
 
@@ -923,29 +1121,86 @@ Get your API key at https://mcp.conare.ai
923
1121
  process.exit(0);
924
1122
  }
925
1123
  }
926
- if (!key) {
927
- console.error("Error: --key is required. Get your API key at https://mcp.conare.ai");
1124
+ if (key && !key.startsWith("cmem_")) {
1125
+ console.error("Error: API key must start with cmem_");
928
1126
  process.exit(1);
929
1127
  }
930
- if (!key.startsWith("cmem_")) {
931
- console.error("Error: API key must start with cmem_");
1128
+ if (ingestOnly && configOnly) {
1129
+ console.error("Error: --ingest-only and --config-only cannot be used together");
932
1130
  process.exit(1);
933
1131
  }
934
- return { key, dryRun, force, source, wasmDir, indexPath };
1132
+ return { key, dryRun, force, ingestOnly, configOnly, interactive, source, wasmDir, indexPath };
935
1133
  }
936
1134
  async function main() {
937
1135
  const opts = parseArgs();
1136
+ const savedApiKey = getSavedApiKey();
1137
+ const shouldRunInteractive = (opts.interactive || process.argv.slice(2).length === 0 || process.argv.slice(2).length <= 2 && !!opts.key) && !opts.dryRun && !opts.force && !opts.source && !opts.indexPath && !opts.configOnly && !opts.ingestOnly && !!process.stdin.isTTY && !!process.stdout.isTTY;
1138
+ let selectedTargets = MCP_TARGETS.map((target) => target.id);
1139
+ let effectiveConfigOnly = opts.configOnly;
1140
+ let effectiveIngestOnly = opts.ingestOnly;
1141
+ let effectiveIndexPath = opts.indexPath;
1142
+ let postIngestIndexPath;
1143
+ let apiKey = opts.key || process.env.CONARE_API_KEY || savedApiKey;
1144
+ if (shouldRunInteractive) {
1145
+ const detectedTools = detect();
1146
+ const answers = await runInteractiveSetup({
1147
+ savedApiKey,
1148
+ detectedTargets: MCP_TARGETS.map((target) => {
1149
+ const detected = detectedTools.find((tool) => target.id === "claude" && tool.name === "Claude Code" || target.id === "cursor" && tool.name === "Cursor" || target.id === "codex" && tool.name === "Codex");
1150
+ return {
1151
+ id: target.id,
1152
+ label: target.label,
1153
+ available: detected?.available,
1154
+ recommended: detected?.available !== false
1155
+ };
1156
+ })
1157
+ });
1158
+ apiKey = answers.apiKey || apiKey;
1159
+ selectedTargets = answers.configureTargets.length > 0 ? answers.configureTargets : [];
1160
+ effectiveConfigOnly = !answers.ingestChats && !answers.indexCodebase;
1161
+ effectiveIngestOnly = answers.ingestChats && answers.configureTargets.length === 0;
1162
+ effectiveIndexPath = !answers.ingestChats && answers.indexCodebase ? answers.indexPath || "." : undefined;
1163
+ postIngestIndexPath = answers.ingestChats && answers.indexCodebase ? answers.indexPath || "." : undefined;
1164
+ }
1165
+ if (!apiKey) {
1166
+ printMissingKeyError();
1167
+ process.exit(1);
1168
+ }
1169
+ console.log("");
1170
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1171
+ console.log(" Conare Installer");
1172
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1173
+ console.log("");
938
1174
  process.stdout.write("Validating API key... ");
939
- const auth = await validateKey(opts.key);
1175
+ const auth = await validateKey(apiKey);
940
1176
  if (!auth.valid) {
941
1177
  console.error("INVALID. Check your key at https://mcp.conare.ai");
942
1178
  process.exit(1);
943
1179
  }
944
- console.log(`OK (${auth.email})`);
1180
+ console.log(auth.email ? `OK (${auth.email})` : "OK");
1181
+ saveApiKey(apiKey);
945
1182
  console.log();
946
- if (opts.indexPath !== undefined) {
1183
+ if (!opts.wasmDir && existsSync7(join9(process.cwd(), "node_modules", "sql.js"))) {
1184
+ opts.wasmDir = join9(process.cwd(), "node_modules");
1185
+ }
1186
+ if (effectiveConfigOnly) {
1187
+ if (!opts.dryRun) {
1188
+ console.log("─── Configuring MCP ───");
1189
+ console.log("");
1190
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1191
+ console.log(` ✓ ${line}`);
1192
+ }
1193
+ console.log("");
1194
+ }
1195
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1196
+ console.log(" ✓ Done! Conare MCP is configured.");
1197
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1198
+ console.log("");
1199
+ return;
1200
+ }
1201
+ if (effectiveIndexPath !== undefined) {
947
1202
  const { resolve: resolve2 } = await import("node:path");
948
- const absPath = resolve2(opts.indexPath);
1203
+ const absPath = resolve2(effectiveIndexPath);
949
1204
  console.log(`Indexing codebase: ${absPath}`);
950
1205
  if (opts.force) {
951
1206
  clearIngested("codebase");
@@ -953,7 +1208,7 @@ async function main() {
953
1208
  try {
954
1209
  await fetch("https://mcp.conare.ai/api/containers/codebase", {
955
1210
  method: "DELETE",
956
- headers: { Authorization: `Bearer ${opts.key}` }
1211
+ headers: { Authorization: `Bearer ${apiKey}` }
957
1212
  });
958
1213
  console.log("done");
959
1214
  } catch {
@@ -977,7 +1232,7 @@ Nothing new to index.`);
977
1232
  console.log(` ... and ${memories.length - 10} more`);
978
1233
  } else {
979
1234
  const barWidth = 20;
980
- const { success, failed, results } = await uploadBulk(opts.key, memories, (uploaded, total, _failed) => {
1235
+ const { success, failed, results } = await uploadBulk(apiKey, memories, (uploaded, total, _failed) => {
981
1236
  const pct = (uploaded / total * 100).toFixed(1);
982
1237
  const filled = Math.round(uploaded / total * barWidth);
983
1238
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
@@ -987,8 +1242,17 @@ Nothing new to index.`);
987
1242
  `);
988
1243
  const fileHashes = results.filter((result) => result.success).map((result) => getDedupKey(memories[result.index])).filter((key) => !!key);
989
1244
  markIngested("codebase", fileHashes);
1245
+ printFailureSummary(results, memories);
990
1246
  }
991
1247
  console.log();
1248
+ if (!opts.dryRun && !effectiveIngestOnly) {
1249
+ console.log("─── Configuring MCP ───");
1250
+ console.log("");
1251
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1252
+ console.log(` ✓ ${line}`);
1253
+ }
1254
+ console.log("");
1255
+ }
992
1256
  console.log("Done! Your codebase is now searchable via recall.");
993
1257
  return;
994
1258
  }
@@ -1045,7 +1309,7 @@ Nothing new to index.`);
1045
1309
  console.log(` ... and ${allMemories.length - 5} more`);
1046
1310
  } else {
1047
1311
  const barWidth = 20;
1048
- const { success, failed, results } = await uploadBulk(opts.key, allMemories, (uploaded, total, _failed) => {
1312
+ const { success, failed, results } = await uploadBulk(apiKey, allMemories, (uploaded, total, _failed) => {
1049
1313
  const pct = (uploaded / total * 100).toFixed(1);
1050
1314
  const filled = Math.round(uploaded / total * barWidth);
1051
1315
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
@@ -1056,6 +1320,7 @@ Nothing new to index.`);
1056
1320
  if (failed > 0) {
1057
1321
  console.log(` Re-run with --force to retry failed memories.`);
1058
1322
  }
1323
+ printFailureSummary(results, allMemories);
1059
1324
  const successfulKeysBySource = {
1060
1325
  claude: [],
1061
1326
  codex: [],
@@ -1086,7 +1351,46 @@ Nothing new to index.`);
1086
1351
  }
1087
1352
  }
1088
1353
  console.log();
1089
- console.log("Done! MCP will be configured by the install script.");
1354
+ if (!opts.dryRun && !effectiveIngestOnly) {
1355
+ console.log("─── Configuring MCP ───");
1356
+ console.log("");
1357
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1358
+ console.log(` ✓ ${line}`);
1359
+ }
1360
+ console.log("");
1361
+ }
1362
+ if (postIngestIndexPath) {
1363
+ const { resolve: resolve2 } = await import("node:path");
1364
+ const absPath = resolve2(postIngestIndexPath);
1365
+ console.log("─── Indexing codebase ───");
1366
+ console.log("");
1367
+ process.stdout.write("Scanning files... ");
1368
+ const { memories, fileCount, skipped } = indexCodebase(absPath);
1369
+ console.log(`${fileCount} files found, ${skipped} skipped`);
1370
+ if (memories.length === 0) {
1371
+ console.log(`
1372
+ Nothing new to index.`);
1373
+ } else {
1374
+ const barWidth = 20;
1375
+ const { success, failed, results } = await uploadBulk(apiKey, memories, (uploaded, total, _failed) => {
1376
+ const pct = (uploaded / total * 100).toFixed(1);
1377
+ const filled = Math.round(uploaded / total * barWidth);
1378
+ const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
1379
+ process.stdout.write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
1380
+ });
1381
+ process.stdout.write(`\r \x1B[32m✓\x1B[0m ${success} files indexed, ${failed} failed${" ".repeat(20)}
1382
+ `);
1383
+ const fileHashes = results.filter((result) => result.success).map((result) => getDedupKey(memories[result.index])).filter((key) => !!key);
1384
+ markIngested("codebase", fileHashes);
1385
+ printFailureSummary(results, memories);
1386
+ }
1387
+ console.log("");
1388
+ }
1389
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1390
+ console.log(" ✓ Done! Every new conversation now starts with context.");
1391
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1392
+ console.log(" Next time you can run `conare` after a global install.");
1393
+ console.log("");
1090
1394
  }
1091
1395
  main().catch((e) => {
1092
1396
  console.error("Error:", e.message || e);
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "conare",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Conare CLI for ingesting AI chat history and configuring memory at conare.ai",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "conare": "./dist/index.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "bun build src/index.ts --outdir dist --target node --format esm --external sql.js && cp dist/index.js ../public/install.mjs",
10
+ "build": "bun build src/index.ts --outdir dist --target node --format esm --external sql.js",
11
11
  "prepublishOnly": "bun run build"
12
12
  },
13
13
  "files": [