conare 0.0.2 → 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 +213 -29
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/index.ts
6
- import { existsSync as existsSync6 } from "node:fs";
7
- import { join as join8 } from "node:path";
6
+ import { existsSync as existsSync7 } from "node:fs";
7
+ import { join as join9 } from "node:path";
8
8
 
9
9
  // src/detect.ts
10
10
  import { existsSync, readdirSync } from "node:fs";
@@ -887,6 +887,11 @@ import { homedir as homedir5 } from "node:os";
887
887
  import { spawnSync } from "node:child_process";
888
888
  var CONARE_URL = "https://mcp.conare.ai";
889
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
+ ];
890
895
  function readJsonFile(path) {
891
896
  try {
892
897
  return JSON.parse(readFileSync6(path, "utf-8"));
@@ -934,19 +939,134 @@ function configureJsonClient(path, apiKey, label) {
934
939
  upsertMcpServer(path, apiKey);
935
940
  return `${label} configured at ${path}`;
936
941
  }
937
- function configureMcp(apiKey) {
942
+ function configureMcp(apiKey, targets = ["claude", "cursor", "codex"]) {
938
943
  const results = [];
939
- results.push(configureClaude(apiKey));
940
- results.push(configureJsonClient(join7(homedir5(), ".cursor", "mcp.json"), apiKey, "Cursor"));
941
- results.push(configureJsonClient(join7(homedir5(), ".codex", "mcp.json"), apiKey, "Codex"));
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
+ }
942
953
  return results;
943
954
  }
944
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
+
945
1037
  // src/index.ts
946
1038
  function getDedupKey(memory) {
947
1039
  const metadata = memory.metadata;
948
1040
  return metadata?.sessionId || metadata?.fileHash || null;
949
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
+ }
950
1070
  function parseArgs() {
951
1071
  const args = process.argv.slice(2);
952
1072
  let key = "";
@@ -954,6 +1074,7 @@ function parseArgs() {
954
1074
  let force = false;
955
1075
  let ingestOnly = false;
956
1076
  let configOnly = false;
1077
+ let interactive = false;
957
1078
  let source;
958
1079
  let wasmDir;
959
1080
  let indexPath;
@@ -974,6 +1095,8 @@ function parseArgs() {
974
1095
  ingestOnly = true;
975
1096
  } else if (args[i] === "--config-only") {
976
1097
  configOnly = true;
1098
+ } else if (args[i] === "--interactive") {
1099
+ interactive = true;
977
1100
  } else if (args[i] === "--help" || args[i] === "-h") {
978
1101
  console.log(`
979
1102
  conare — Ingest AI chat history into Conare
@@ -989,19 +1112,16 @@ Options:
989
1112
  --force Re-ingest all / re-index all (bypass dedup)
990
1113
  --ingest-only Ingest memories without MCP configuration
991
1114
  --config-only Configure MCP only, skip ingestion
1115
+ --interactive Run guided setup prompts
992
1116
  --source <name> Only ingest from: claude, codex, cursor
993
1117
  --wasm-dir <path> Path to sql.js module (for Cursor ingestion)
994
1118
 
995
- Get your API key at https://conare.ai
1119
+ Get your API key at https://mcp.conare.ai
996
1120
  `);
997
1121
  process.exit(0);
998
1122
  }
999
1123
  }
1000
- if (!key) {
1001
- console.error("Error: --key is required. Get your API key at https://conare.ai");
1002
- process.exit(1);
1003
- }
1004
- if (!key.startsWith("cmem_")) {
1124
+ if (key && !key.startsWith("cmem_")) {
1005
1125
  console.error("Error: API key must start with cmem_");
1006
1126
  process.exit(1);
1007
1127
  }
@@ -1009,31 +1129,65 @@ Get your API key at https://conare.ai
1009
1129
  console.error("Error: --ingest-only and --config-only cannot be used together");
1010
1130
  process.exit(1);
1011
1131
  }
1012
- return { key, dryRun, force, ingestOnly, configOnly, source, wasmDir, indexPath };
1132
+ return { key, dryRun, force, ingestOnly, configOnly, interactive, source, wasmDir, indexPath };
1013
1133
  }
1014
1134
  async function main() {
1015
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
+ }
1016
1169
  console.log("");
1017
1170
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1018
1171
  console.log(" Conare Installer");
1019
1172
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1020
1173
  console.log("");
1021
1174
  process.stdout.write("Validating API key... ");
1022
- const auth = await validateKey(opts.key);
1175
+ const auth = await validateKey(apiKey);
1023
1176
  if (!auth.valid) {
1024
- console.error("INVALID. Check your key at https://conare.ai");
1177
+ console.error("INVALID. Check your key at https://mcp.conare.ai");
1025
1178
  process.exit(1);
1026
1179
  }
1027
- console.log(`OK (${auth.email})`);
1180
+ console.log(auth.email ? `OK (${auth.email})` : "OK");
1181
+ saveApiKey(apiKey);
1028
1182
  console.log();
1029
- if (!opts.wasmDir && existsSync6(join8(process.cwd(), "node_modules", "sql.js"))) {
1030
- opts.wasmDir = join8(process.cwd(), "node_modules");
1183
+ if (!opts.wasmDir && existsSync7(join9(process.cwd(), "node_modules", "sql.js"))) {
1184
+ opts.wasmDir = join9(process.cwd(), "node_modules");
1031
1185
  }
1032
- if (opts.configOnly) {
1186
+ if (effectiveConfigOnly) {
1033
1187
  if (!opts.dryRun) {
1034
1188
  console.log("─── Configuring MCP ───");
1035
1189
  console.log("");
1036
- for (const line of configureMcp(opts.key)) {
1190
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1037
1191
  console.log(` ✓ ${line}`);
1038
1192
  }
1039
1193
  console.log("");
@@ -1044,9 +1198,9 @@ async function main() {
1044
1198
  console.log("");
1045
1199
  return;
1046
1200
  }
1047
- if (opts.indexPath !== undefined) {
1201
+ if (effectiveIndexPath !== undefined) {
1048
1202
  const { resolve: resolve2 } = await import("node:path");
1049
- const absPath = resolve2(opts.indexPath);
1203
+ const absPath = resolve2(effectiveIndexPath);
1050
1204
  console.log(`Indexing codebase: ${absPath}`);
1051
1205
  if (opts.force) {
1052
1206
  clearIngested("codebase");
@@ -1054,7 +1208,7 @@ async function main() {
1054
1208
  try {
1055
1209
  await fetch("https://mcp.conare.ai/api/containers/codebase", {
1056
1210
  method: "DELETE",
1057
- headers: { Authorization: `Bearer ${opts.key}` }
1211
+ headers: { Authorization: `Bearer ${apiKey}` }
1058
1212
  });
1059
1213
  console.log("done");
1060
1214
  } catch {
@@ -1078,7 +1232,7 @@ Nothing new to index.`);
1078
1232
  console.log(` ... and ${memories.length - 10} more`);
1079
1233
  } else {
1080
1234
  const barWidth = 20;
1081
- const { success, failed, results } = await uploadBulk(opts.key, memories, (uploaded, total, _failed) => {
1235
+ const { success, failed, results } = await uploadBulk(apiKey, memories, (uploaded, total, _failed) => {
1082
1236
  const pct = (uploaded / total * 100).toFixed(1);
1083
1237
  const filled = Math.round(uploaded / total * barWidth);
1084
1238
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
@@ -1088,12 +1242,13 @@ Nothing new to index.`);
1088
1242
  `);
1089
1243
  const fileHashes = results.filter((result) => result.success).map((result) => getDedupKey(memories[result.index])).filter((key) => !!key);
1090
1244
  markIngested("codebase", fileHashes);
1245
+ printFailureSummary(results, memories);
1091
1246
  }
1092
1247
  console.log();
1093
- if (!opts.dryRun && !opts.ingestOnly) {
1248
+ if (!opts.dryRun && !effectiveIngestOnly) {
1094
1249
  console.log("─── Configuring MCP ───");
1095
1250
  console.log("");
1096
- for (const line of configureMcp(opts.key)) {
1251
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1097
1252
  console.log(` ✓ ${line}`);
1098
1253
  }
1099
1254
  console.log("");
@@ -1154,7 +1309,7 @@ Nothing new to index.`);
1154
1309
  console.log(` ... and ${allMemories.length - 5} more`);
1155
1310
  } else {
1156
1311
  const barWidth = 20;
1157
- const { success, failed, results } = await uploadBulk(opts.key, allMemories, (uploaded, total, _failed) => {
1312
+ const { success, failed, results } = await uploadBulk(apiKey, allMemories, (uploaded, total, _failed) => {
1158
1313
  const pct = (uploaded / total * 100).toFixed(1);
1159
1314
  const filled = Math.round(uploaded / total * barWidth);
1160
1315
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
@@ -1165,6 +1320,7 @@ Nothing new to index.`);
1165
1320
  if (failed > 0) {
1166
1321
  console.log(` Re-run with --force to retry failed memories.`);
1167
1322
  }
1323
+ printFailureSummary(results, allMemories);
1168
1324
  const successfulKeysBySource = {
1169
1325
  claude: [],
1170
1326
  codex: [],
@@ -1195,17 +1351,45 @@ Nothing new to index.`);
1195
1351
  }
1196
1352
  }
1197
1353
  console.log();
1198
- if (!opts.dryRun && !opts.ingestOnly) {
1354
+ if (!opts.dryRun && !effectiveIngestOnly) {
1199
1355
  console.log("─── Configuring MCP ───");
1200
1356
  console.log("");
1201
- for (const line of configureMcp(opts.key)) {
1357
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1202
1358
  console.log(` ✓ ${line}`);
1203
1359
  }
1204
1360
  console.log("");
1205
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
+ }
1206
1389
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1207
1390
  console.log(" ✓ Done! Every new conversation now starts with context.");
1208
1391
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1392
+ console.log(" Next time you can run `conare` after a global install.");
1209
1393
  console.log("");
1210
1394
  }
1211
1395
  main().catch((e) => {
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "conare",
3
- "version": "0.0.2",
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": [