conare 0.0.2 → 0.0.4

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 +220 -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,140 @@ 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
+ let apiKey = options.providedApiKey;
1001
+ if (apiKey) {
1002
+ output.write(`Using API key from --key
1003
+ `);
1004
+ } else {
1005
+ const apiKeyPrompt = options.savedApiKey ? `API key [press enter to use saved key ending in ${options.savedApiKey.slice(-6)}]: ` : "API key: ";
1006
+ const apiKeyAnswer = await rl.question(apiKeyPrompt);
1007
+ apiKey = apiKeyAnswer.trim() || options.savedApiKey;
1008
+ }
1009
+ output.write(`
1010
+ Clients to configure:
1011
+ `);
1012
+ options.detectedTargets.forEach((target, index) => {
1013
+ const availability = target.available === false ? " (not detected locally)" : "";
1014
+ const recommendation = target.recommended ? " [default]" : "";
1015
+ output.write(` ${index + 1}. ${target.label}${recommendation}${availability}
1016
+ `);
1017
+ });
1018
+ const defaultSelection = options.detectedTargets.map((target, index) => target.recommended ? String(index + 1) : null).filter((value) => !!value).join(",");
1019
+ const targetAnswer = await rl.question(`Select clients [${defaultSelection || "none"}]: `);
1020
+ const selectedIndexes = (targetAnswer.trim() || defaultSelection).split(",").map((part) => parseInt(part.trim(), 10)).filter((value) => Number.isFinite(value) && value >= 1 && value <= options.detectedTargets.length);
1021
+ const configureTargets = [...new Set(selectedIndexes)].map((index) => options.detectedTargets[index - 1]?.id).filter((value) => !!value);
1022
+ const ingestChats = normalizeYesNo(await rl.question("Ingest chat history? [Y/n]: "), true);
1023
+ const indexCodebase2 = normalizeYesNo(await rl.question("Index current codebase too? [y/N]: "), false);
1024
+ let indexPath;
1025
+ if (indexCodebase2) {
1026
+ const pathAnswer = await rl.question("Codebase path [.]: ");
1027
+ indexPath = pathAnswer.trim() || ".";
1028
+ }
1029
+ output.write(`
1030
+ `);
1031
+ return {
1032
+ apiKey,
1033
+ configureTargets,
1034
+ ingestChats,
1035
+ indexCodebase: indexCodebase2,
1036
+ indexPath
1037
+ };
1038
+ } finally {
1039
+ rl.close();
1040
+ }
1041
+ }
1042
+
945
1043
  // src/index.ts
946
1044
  function getDedupKey(memory) {
947
1045
  const metadata = memory.metadata;
948
1046
  return metadata?.sessionId || metadata?.fileHash || null;
949
1047
  }
1048
+ function printMissingKeyError() {
1049
+ console.error("Error: no API key configured.");
1050
+ console.error("");
1051
+ console.error("Run one of these:");
1052
+ console.error(" bunx conare@latest --key YOUR_API_KEY");
1053
+ console.error(" npx conare@latest --key YOUR_API_KEY");
1054
+ console.error("");
1055
+ console.error("For a built-in command, install globally:");
1056
+ console.error(" bun add -g conare");
1057
+ console.error(" npm i -g conare");
1058
+ console.error("");
1059
+ console.error("Get your API key at https://mcp.conare.ai");
1060
+ }
1061
+ function printFailureSummary(results, memories) {
1062
+ const failures = results.map((result, index) => ({ ...result, memory: memories[index] })).filter((result) => !result.success);
1063
+ if (failures.length === 0)
1064
+ return;
1065
+ console.log("");
1066
+ console.log(" Failure summary:");
1067
+ for (const failure of failures.slice(0, 3)) {
1068
+ const metadata = failure.memory.metadata;
1069
+ const label = metadata?.filePath || metadata?.sessionId || failure.memory.containerTag;
1070
+ console.log(` - ${label}: ${failure.error || "Upload failed"}`);
1071
+ }
1072
+ if (failures.length > 3) {
1073
+ console.log(` - ... and ${failures.length - 3} more`);
1074
+ }
1075
+ }
950
1076
  function parseArgs() {
951
1077
  const args = process.argv.slice(2);
952
1078
  let key = "";
@@ -954,6 +1080,7 @@ function parseArgs() {
954
1080
  let force = false;
955
1081
  let ingestOnly = false;
956
1082
  let configOnly = false;
1083
+ let interactive = false;
957
1084
  let source;
958
1085
  let wasmDir;
959
1086
  let indexPath;
@@ -974,6 +1101,8 @@ function parseArgs() {
974
1101
  ingestOnly = true;
975
1102
  } else if (args[i] === "--config-only") {
976
1103
  configOnly = true;
1104
+ } else if (args[i] === "--interactive") {
1105
+ interactive = true;
977
1106
  } else if (args[i] === "--help" || args[i] === "-h") {
978
1107
  console.log(`
979
1108
  conare — Ingest AI chat history into Conare
@@ -989,19 +1118,16 @@ Options:
989
1118
  --force Re-ingest all / re-index all (bypass dedup)
990
1119
  --ingest-only Ingest memories without MCP configuration
991
1120
  --config-only Configure MCP only, skip ingestion
1121
+ --interactive Run guided setup prompts
992
1122
  --source <name> Only ingest from: claude, codex, cursor
993
1123
  --wasm-dir <path> Path to sql.js module (for Cursor ingestion)
994
1124
 
995
- Get your API key at https://conare.ai
1125
+ Get your API key at https://mcp.conare.ai
996
1126
  `);
997
1127
  process.exit(0);
998
1128
  }
999
1129
  }
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_")) {
1130
+ if (key && !key.startsWith("cmem_")) {
1005
1131
  console.error("Error: API key must start with cmem_");
1006
1132
  process.exit(1);
1007
1133
  }
@@ -1009,31 +1135,66 @@ Get your API key at https://conare.ai
1009
1135
  console.error("Error: --ingest-only and --config-only cannot be used together");
1010
1136
  process.exit(1);
1011
1137
  }
1012
- return { key, dryRun, force, ingestOnly, configOnly, source, wasmDir, indexPath };
1138
+ return { key, dryRun, force, ingestOnly, configOnly, interactive, source, wasmDir, indexPath };
1013
1139
  }
1014
1140
  async function main() {
1015
1141
  const opts = parseArgs();
1142
+ const savedApiKey = getSavedApiKey();
1143
+ const shouldRunInteractive = !opts.dryRun && !opts.force && !opts.source && !opts.indexPath && !opts.configOnly && !opts.ingestOnly && !opts.wasmDir && !!process.stdin.isTTY && !!process.stdout.isTTY;
1144
+ let selectedTargets = MCP_TARGETS.map((target) => target.id);
1145
+ let effectiveConfigOnly = opts.configOnly;
1146
+ let effectiveIngestOnly = opts.ingestOnly;
1147
+ let effectiveIndexPath = opts.indexPath;
1148
+ let postIngestIndexPath;
1149
+ let apiKey = opts.key || process.env.CONARE_API_KEY || savedApiKey;
1150
+ if (shouldRunInteractive) {
1151
+ const detectedTools = detect();
1152
+ const answers = await runInteractiveSetup({
1153
+ savedApiKey,
1154
+ providedApiKey: opts.key,
1155
+ detectedTargets: MCP_TARGETS.map((target) => {
1156
+ const detected = detectedTools.find((tool) => target.id === "claude" && tool.name === "Claude Code" || target.id === "cursor" && tool.name === "Cursor" || target.id === "codex" && tool.name === "Codex");
1157
+ return {
1158
+ id: target.id,
1159
+ label: target.label,
1160
+ available: detected?.available,
1161
+ recommended: detected?.available !== false
1162
+ };
1163
+ })
1164
+ });
1165
+ apiKey = answers.apiKey || apiKey;
1166
+ selectedTargets = answers.configureTargets.length > 0 ? answers.configureTargets : [];
1167
+ effectiveConfigOnly = !answers.ingestChats && !answers.indexCodebase;
1168
+ effectiveIngestOnly = answers.ingestChats && answers.configureTargets.length === 0;
1169
+ effectiveIndexPath = !answers.ingestChats && answers.indexCodebase ? answers.indexPath || "." : undefined;
1170
+ postIngestIndexPath = answers.ingestChats && answers.indexCodebase ? answers.indexPath || "." : undefined;
1171
+ }
1172
+ if (!apiKey) {
1173
+ printMissingKeyError();
1174
+ process.exit(1);
1175
+ }
1016
1176
  console.log("");
1017
1177
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1018
1178
  console.log(" Conare Installer");
1019
1179
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1020
1180
  console.log("");
1021
1181
  process.stdout.write("Validating API key... ");
1022
- const auth = await validateKey(opts.key);
1182
+ const auth = await validateKey(apiKey);
1023
1183
  if (!auth.valid) {
1024
- console.error("INVALID. Check your key at https://conare.ai");
1184
+ console.error("INVALID. Check your key at https://mcp.conare.ai");
1025
1185
  process.exit(1);
1026
1186
  }
1027
- console.log(`OK (${auth.email})`);
1187
+ console.log(auth.email ? `OK (${auth.email})` : "OK");
1188
+ saveApiKey(apiKey);
1028
1189
  console.log();
1029
- if (!opts.wasmDir && existsSync6(join8(process.cwd(), "node_modules", "sql.js"))) {
1030
- opts.wasmDir = join8(process.cwd(), "node_modules");
1190
+ if (!opts.wasmDir && existsSync7(join9(process.cwd(), "node_modules", "sql.js"))) {
1191
+ opts.wasmDir = join9(process.cwd(), "node_modules");
1031
1192
  }
1032
- if (opts.configOnly) {
1193
+ if (effectiveConfigOnly) {
1033
1194
  if (!opts.dryRun) {
1034
1195
  console.log("─── Configuring MCP ───");
1035
1196
  console.log("");
1036
- for (const line of configureMcp(opts.key)) {
1197
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1037
1198
  console.log(` ✓ ${line}`);
1038
1199
  }
1039
1200
  console.log("");
@@ -1044,9 +1205,9 @@ async function main() {
1044
1205
  console.log("");
1045
1206
  return;
1046
1207
  }
1047
- if (opts.indexPath !== undefined) {
1208
+ if (effectiveIndexPath !== undefined) {
1048
1209
  const { resolve: resolve2 } = await import("node:path");
1049
- const absPath = resolve2(opts.indexPath);
1210
+ const absPath = resolve2(effectiveIndexPath);
1050
1211
  console.log(`Indexing codebase: ${absPath}`);
1051
1212
  if (opts.force) {
1052
1213
  clearIngested("codebase");
@@ -1054,7 +1215,7 @@ async function main() {
1054
1215
  try {
1055
1216
  await fetch("https://mcp.conare.ai/api/containers/codebase", {
1056
1217
  method: "DELETE",
1057
- headers: { Authorization: `Bearer ${opts.key}` }
1218
+ headers: { Authorization: `Bearer ${apiKey}` }
1058
1219
  });
1059
1220
  console.log("done");
1060
1221
  } catch {
@@ -1078,7 +1239,7 @@ Nothing new to index.`);
1078
1239
  console.log(` ... and ${memories.length - 10} more`);
1079
1240
  } else {
1080
1241
  const barWidth = 20;
1081
- const { success, failed, results } = await uploadBulk(opts.key, memories, (uploaded, total, _failed) => {
1242
+ const { success, failed, results } = await uploadBulk(apiKey, memories, (uploaded, total, _failed) => {
1082
1243
  const pct = (uploaded / total * 100).toFixed(1);
1083
1244
  const filled = Math.round(uploaded / total * barWidth);
1084
1245
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
@@ -1088,12 +1249,13 @@ Nothing new to index.`);
1088
1249
  `);
1089
1250
  const fileHashes = results.filter((result) => result.success).map((result) => getDedupKey(memories[result.index])).filter((key) => !!key);
1090
1251
  markIngested("codebase", fileHashes);
1252
+ printFailureSummary(results, memories);
1091
1253
  }
1092
1254
  console.log();
1093
- if (!opts.dryRun && !opts.ingestOnly) {
1255
+ if (!opts.dryRun && !effectiveIngestOnly) {
1094
1256
  console.log("─── Configuring MCP ───");
1095
1257
  console.log("");
1096
- for (const line of configureMcp(opts.key)) {
1258
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1097
1259
  console.log(` ✓ ${line}`);
1098
1260
  }
1099
1261
  console.log("");
@@ -1154,7 +1316,7 @@ Nothing new to index.`);
1154
1316
  console.log(` ... and ${allMemories.length - 5} more`);
1155
1317
  } else {
1156
1318
  const barWidth = 20;
1157
- const { success, failed, results } = await uploadBulk(opts.key, allMemories, (uploaded, total, _failed) => {
1319
+ const { success, failed, results } = await uploadBulk(apiKey, allMemories, (uploaded, total, _failed) => {
1158
1320
  const pct = (uploaded / total * 100).toFixed(1);
1159
1321
  const filled = Math.round(uploaded / total * barWidth);
1160
1322
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
@@ -1165,6 +1327,7 @@ Nothing new to index.`);
1165
1327
  if (failed > 0) {
1166
1328
  console.log(` Re-run with --force to retry failed memories.`);
1167
1329
  }
1330
+ printFailureSummary(results, allMemories);
1168
1331
  const successfulKeysBySource = {
1169
1332
  claude: [],
1170
1333
  codex: [],
@@ -1195,17 +1358,45 @@ Nothing new to index.`);
1195
1358
  }
1196
1359
  }
1197
1360
  console.log();
1198
- if (!opts.dryRun && !opts.ingestOnly) {
1361
+ if (!opts.dryRun && !effectiveIngestOnly) {
1199
1362
  console.log("─── Configuring MCP ───");
1200
1363
  console.log("");
1201
- for (const line of configureMcp(opts.key)) {
1364
+ for (const line of configureMcp(apiKey, selectedTargets)) {
1202
1365
  console.log(` ✓ ${line}`);
1203
1366
  }
1204
1367
  console.log("");
1205
1368
  }
1369
+ if (postIngestIndexPath) {
1370
+ const { resolve: resolve2 } = await import("node:path");
1371
+ const absPath = resolve2(postIngestIndexPath);
1372
+ console.log("─── Indexing codebase ───");
1373
+ console.log("");
1374
+ process.stdout.write("Scanning files... ");
1375
+ const { memories, fileCount, skipped } = indexCodebase(absPath);
1376
+ console.log(`${fileCount} files found, ${skipped} skipped`);
1377
+ if (memories.length === 0) {
1378
+ console.log(`
1379
+ Nothing new to index.`);
1380
+ } else {
1381
+ const barWidth = 20;
1382
+ const { success, failed, results } = await uploadBulk(apiKey, memories, (uploaded, total, _failed) => {
1383
+ const pct = (uploaded / total * 100).toFixed(1);
1384
+ const filled = Math.round(uploaded / total * barWidth);
1385
+ const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
1386
+ process.stdout.write(`\r Uploading [\x1B[36m${bar}\x1B[0m] ${uploaded}/${total} (${pct}%)`);
1387
+ });
1388
+ process.stdout.write(`\r \x1B[32m✓\x1B[0m ${success} files indexed, ${failed} failed${" ".repeat(20)}
1389
+ `);
1390
+ const fileHashes = results.filter((result) => result.success).map((result) => getDedupKey(memories[result.index])).filter((key) => !!key);
1391
+ markIngested("codebase", fileHashes);
1392
+ printFailureSummary(results, memories);
1393
+ }
1394
+ console.log("");
1395
+ }
1206
1396
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1207
1397
  console.log(" ✓ Done! Every new conversation now starts with context.");
1208
1398
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
1399
+ console.log(" Next time you can run `conare` after a global install.");
1209
1400
  console.log("");
1210
1401
  }
1211
1402
  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.4",
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": [