conare 0.3.0 → 0.3.2

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 +235 -193
  2. package/package.json +11 -2
package/dist/index.js CHANGED
@@ -871,14 +871,14 @@ var init_interactive = __esm(() => {
871
871
  });
872
872
 
873
873
  // src/index.ts
874
- import { existsSync as existsSync8 } from "node:fs";
874
+ import { existsSync as existsSync9 } from "node:fs";
875
875
  import { join as join10 } from "node:path";
876
876
 
877
877
  // src/detect.ts
878
- import { existsSync, readdirSync } from "node:fs";
879
- import { spawnSync } from "node:child_process";
878
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
880
879
  import { join } from "node:path";
881
880
  import { homedir, platform } from "node:os";
881
+ import { createRequire as createRequire2 } from "node:module";
882
882
  function countJsonlFiles(dir) {
883
883
  let count = 0;
884
884
  try {
@@ -892,50 +892,41 @@ function countJsonlFiles(dir) {
892
892
  } catch {}
893
893
  return count;
894
894
  }
895
- function countCursorSessions(dbPath) {
895
+ async function countCursorSessions(dbPath) {
896
896
  try {
897
- const result = spawnSync("sqlite3", [
898
- dbPath,
899
- `
900
- WITH composer_rows AS (
901
- SELECT substr(key, 14) AS composer_id, value AS composer_json
902
- FROM cursorDiskKV
903
- WHERE key LIKE 'composerData:%'
904
- ),
905
- headers AS (
906
- SELECT
907
- composer_id,
908
- json_extract(j.value, '$.bubbleId') AS bubble_id,
909
- json_extract(j.value, '$.type') AS type
910
- FROM composer_rows, json_each(json_extract(composer_json, '$.fullConversationHeadersOnly')) AS j
911
- ),
912
- messages AS (
913
- SELECT h.composer_id, h.type
914
- FROM headers h
915
- JOIN cursorDiskKV kv
916
- ON kv.key = 'bubbleId:' || h.composer_id || ':' || h.bubble_id
917
- WHERE h.type IN (1, 2)
918
- AND length(COALESCE(json_extract(kv.value, '$.text'), '')) >= 50
919
- )
920
- SELECT count(*)
921
- FROM (
922
- SELECT composer_id
923
- FROM messages
924
- GROUP BY composer_id
925
- HAVING SUM(CASE WHEN type = 1 THEN 1 ELSE 0 END) > 0
926
- AND SUM(CASE WHEN type = 2 THEN 1 ELSE 0 END) > 0
927
- );
928
- `.trim()
929
- ], { encoding: "utf-8" });
930
- if (result.status !== 0)
931
- return 0;
932
- const count = Number.parseInt(result.stdout.trim(), 10);
933
- return Number.isFinite(count) ? count : 0;
897
+ const require2 = createRequire2(import.meta.url);
898
+ const initSqlJs = require2("sql.js");
899
+ const SQL = await initSqlJs();
900
+ const buffer = readFileSync(dbPath);
901
+ const db = new SQL.Database(buffer);
902
+ try {
903
+ const results = db.exec("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'");
904
+ if (results.length === 0)
905
+ return 0;
906
+ let count = 0;
907
+ for (const [, value] of results[0].values) {
908
+ try {
909
+ const parsed = JSON.parse(value);
910
+ const headers = parsed.fullConversationHeadersOnly;
911
+ if (!Array.isArray(headers) || headers.length < 2)
912
+ continue;
913
+ const hasUser = headers.some((h) => h.type === 1);
914
+ const hasAssistant = headers.some((h) => h.type === 2);
915
+ if (hasUser && hasAssistant)
916
+ count++;
917
+ } catch {
918
+ continue;
919
+ }
920
+ }
921
+ return count;
922
+ } finally {
923
+ db.close();
924
+ }
934
925
  } catch {
935
926
  return 0;
936
927
  }
937
928
  }
938
- function detect() {
929
+ async function detect() {
939
930
  const home = homedir();
940
931
  const tools = [];
941
932
  const claudeDir = join(home, ".claude", "projects");
@@ -956,8 +947,8 @@ function detect() {
956
947
  let sessionCount = 0;
957
948
  if (existsSync(codexHistory)) {
958
949
  try {
959
- const { readFileSync } = __require("node:fs");
960
- const lines = readFileSync(codexHistory, "utf-8").split(`
950
+ const { readFileSync: readFileSync2 } = __require("node:fs");
951
+ const lines = readFileSync2(codexHistory, "utf-8").split(`
961
952
  `).filter(Boolean);
962
953
  const sessions = new Set(lines.map((l) => {
963
954
  try {
@@ -992,7 +983,7 @@ function detect() {
992
983
  name: "Cursor",
993
984
  available: existsSync(cursorDbPath),
994
985
  path: cursorDbPath,
995
- sessionCount: existsSync(cursorDbPath) ? countCursorSessions(cursorDbPath) : 0
986
+ sessionCount: existsSync(cursorDbPath) ? await countCursorSessions(cursorDbPath) : 0
996
987
  });
997
988
  const openclawDir = join(home, ".openclaw");
998
989
  tools.push({
@@ -1005,12 +996,12 @@ function detect() {
1005
996
  }
1006
997
 
1007
998
  // src/ingest/claude.ts
1008
- import { readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
999
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
1009
1000
  import { join as join3, basename } from "node:path";
1010
- import { homedir as homedir3 } from "node:os";
1001
+ import { homedir as homedir3, platform as platform2 } from "node:os";
1011
1002
 
1012
1003
  // src/ingest/shared.ts
1013
- import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "node:fs";
1004
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "node:fs";
1014
1005
  import { createHash } from "node:crypto";
1015
1006
  import { join as join2 } from "node:path";
1016
1007
  import { homedir as homedir2 } from "node:os";
@@ -1030,6 +1021,15 @@ function cleanText(raw) {
1030
1021
  text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
1031
1022
  text = text.replace(/<attached-context[\s\S]*?<\/attached-context>/g, "");
1032
1023
  text = text.replace(/<environment_context>[\s\S]*?<\/environment_context>/g, "");
1024
+ text = text.replace(/^\s*Base directory for this skill:[\s\S]*/g, "");
1025
+ text = text.replace(/\nBase directory for this skill:[\s\S]*/g, "");
1026
+ text = text.replace(/<!-- vibe-rules Integration -->[\s\S]*?<!-- \/vibe-rules Integration -->/g, "");
1027
+ text = text.replace(/<good-behaviour>[\s\S]*?<\/good-behaviour>/g, "");
1028
+ text = text.replace(/<frontend-design>[\s\S]*?<\/frontend-design>/g, "");
1029
+ text = text.replace(/<architecture>[\s\S]*?<\/architecture>/g, "");
1030
+ text = text.replace(/<task-notification>[\s\S]*?<\/task-notification>/g, "");
1031
+ text = text.replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, "");
1032
+ text = text.replace(/<system_instruction>[\s\S]*?<\/system_instruction>/g, "");
1033
1033
  return text.trim();
1034
1034
  }
1035
1035
  function createContentHash(content) {
@@ -1038,7 +1038,7 @@ function createContentHash(content) {
1038
1038
  function getIngested() {
1039
1039
  try {
1040
1040
  if (existsSync2(MANIFEST_PATH)) {
1041
- return JSON.parse(readFileSync(MANIFEST_PATH, "utf-8"));
1041
+ return JSON.parse(readFileSync2(MANIFEST_PATH, "utf-8"));
1042
1042
  }
1043
1043
  } catch {}
1044
1044
  return {};
@@ -1075,6 +1075,43 @@ function clearIngested(source) {
1075
1075
  // src/ingest/claude.ts
1076
1076
  var MAX_CONTENT = 48000;
1077
1077
  var MIN_TURN_LEN = 50;
1078
+ function resolveProjectName(dirName) {
1079
+ const segments = dirName.replace(/^-/, "").split("-");
1080
+ const isWindows = platform2() === "win32";
1081
+ let resolved;
1082
+ let startIdx;
1083
+ if (isWindows && segments.length > 0 && /^[A-Za-z]$/.test(segments[0])) {
1084
+ resolved = segments[0].toUpperCase() + ":\\";
1085
+ startIdx = 1;
1086
+ } else {
1087
+ resolved = "/";
1088
+ startIdx = 0;
1089
+ }
1090
+ let i = startIdx;
1091
+ while (i < segments.length) {
1092
+ let found = false;
1093
+ for (let end = segments.length;end > i; end--) {
1094
+ const candidate = segments.slice(i, end).join("-");
1095
+ const candidatePath = join3(resolved, candidate);
1096
+ if (existsSync3(candidatePath)) {
1097
+ resolved = candidatePath;
1098
+ i = end;
1099
+ found = true;
1100
+ break;
1101
+ }
1102
+ }
1103
+ if (!found) {
1104
+ resolved = join3(resolved, segments[i]);
1105
+ i++;
1106
+ }
1107
+ }
1108
+ const home = homedir3();
1109
+ const sep = isWindows ? "\\" : "/";
1110
+ if (resolved.startsWith(home + sep)) {
1111
+ return resolved.slice(home.length + 1).replace(/\\/g, "/");
1112
+ }
1113
+ return resolved.replace(/\\/g, "/");
1114
+ }
1078
1115
  function extractText(content) {
1079
1116
  if (typeof content === "string")
1080
1117
  return content;
@@ -1140,7 +1177,7 @@ function ingestClaude() {
1140
1177
  }
1141
1178
  for (const projDir of projectDirs) {
1142
1179
  const projPath = join3(projectsDir, projDir);
1143
- const project = projDir.replace(/^-Users-[^-]+-/, "").replace(/-/g, "/") || projDir;
1180
+ const project = resolveProjectName(projDir);
1144
1181
  let files;
1145
1182
  try {
1146
1183
  files = readdirSync2(projPath).filter((f) => f.endsWith(".jsonl"));
@@ -1149,7 +1186,7 @@ function ingestClaude() {
1149
1186
  }
1150
1187
  for (const file of files) {
1151
1188
  const sessionId = basename(file, ".jsonl");
1152
- const raw = readFileSync2(join3(projPath, file), "utf-8");
1189
+ const raw = readFileSync3(join3(projPath, file), "utf-8");
1153
1190
  const { turns, date } = parseSession(raw.split(`
1154
1191
  `));
1155
1192
  if (turns.length === 0) {
@@ -1158,8 +1195,7 @@ function ingestClaude() {
1158
1195
  }
1159
1196
  const header = `# Chat: ${project}${date ? ` | ${date}` : ""}`;
1160
1197
  const body = turns.map((t) => {
1161
- const q = t.user.length > 300 ? t.user.slice(0, 300) + "..." : t.user;
1162
- return `## Q: ${q}
1198
+ return `## Q: ${t.user}
1163
1199
 
1164
1200
  ${t.assistant}`;
1165
1201
  }).join(`
@@ -1200,105 +1236,33 @@ ${body}`;
1200
1236
  }
1201
1237
 
1202
1238
  // src/ingest/codex.ts
1203
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "node:fs";
1239
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
1204
1240
  import { join as join4, basename as basename2 } from "node:path";
1205
1241
  import { homedir as homedir4 } from "node:os";
1206
1242
  var MAX_CONTENT2 = 48000;
1207
1243
  function isCodexBoilerplate(text) {
1208
1244
  return text.startsWith("# AGENTS.md instructions for") || text.startsWith("<INSTRUCTIONS>") || text.startsWith("<user_instructions>") || text.startsWith("<user_action>");
1209
1245
  }
1210
- function stripEnvironmentContext(text) {
1211
- let cwd = null;
1212
- const cwdMatch = text.match(/<cwd>([^<]+)<\/cwd>/);
1213
- if (cwdMatch)
1214
- cwd = cwdMatch[1];
1215
- const cleaned = text.replace(/<environment_context>[\s\S]*?<\/environment_context>/g, "").trim();
1216
- return { text: cleaned, cwd };
1246
+ function extractCwd(text) {
1247
+ const match = text.match(/<cwd>([^<]+)<\/cwd>/);
1248
+ return match ? match[1] : null;
1217
1249
  }
1218
1250
  function projectFromCwd(cwd) {
1219
- return cwd.replace(/^\/Users\/[^/]+\//, "");
1251
+ const home = homedir4();
1252
+ const normalized = cwd.replace(/\\/g, "/");
1253
+ const normalizedHome = home.replace(/\\/g, "/");
1254
+ if (normalized.startsWith(normalizedHome + "/")) {
1255
+ return normalized.slice(normalizedHome.length + 1);
1256
+ }
1257
+ return normalized.replace(/^\/Users\/[^/]+\//, "").replace(/^\/home\/[^/]+\//, "").replace(/^[A-Za-z]:\/Users\/[^/]+\//, "");
1220
1258
  }
1221
1259
  function ingestCodex() {
1222
1260
  const memories = [];
1223
1261
  const sessionIds = [];
1224
1262
  let filtered = 0;
1225
1263
  let deduped = 0;
1226
- const historyPath = join4(homedir4(), ".codex", "history.jsonl");
1227
- if (existsSync3(historyPath)) {
1228
- try {
1229
- const lines = readFileSync3(historyPath, "utf-8").split(`
1230
- `).filter(Boolean);
1231
- const sessions = new Map;
1232
- for (const line of lines) {
1233
- try {
1234
- const obj = JSON.parse(line);
1235
- if (!obj.session_id || !obj.text)
1236
- continue;
1237
- if (!sessions.has(obj.session_id))
1238
- sessions.set(obj.session_id, []);
1239
- sessions.get(obj.session_id).push({ ts: obj.ts, text: obj.text });
1240
- } catch {
1241
- continue;
1242
- }
1243
- }
1244
- for (const [sessionId, entries] of sessions) {
1245
- entries.sort((a, b) => a.ts - b.ts);
1246
- const date = new Date(entries[0].ts * 1000).toISOString().slice(0, 10);
1247
- let project = null;
1248
- const cleanEntries = [];
1249
- for (const e of entries) {
1250
- let text = cleanText(e.text);
1251
- if (isCodexBoilerplate(text))
1252
- continue;
1253
- const env = stripEnvironmentContext(text);
1254
- text = env.text;
1255
- if (!project && env.cwd)
1256
- project = projectFromCwd(env.cwd);
1257
- if (text.length === 0)
1258
- continue;
1259
- cleanEntries.push(text.length > 300 ? text.slice(0, 300) + "..." : text);
1260
- }
1261
- const body = cleanEntries.filter(Boolean).join(`
1262
-
1263
- ---
1264
-
1265
- `);
1266
- let content = `# Codex Chat | ${date}
1267
-
1268
- ${body}`;
1269
- if (content.length > MAX_CONTENT2)
1270
- content = content.slice(0, MAX_CONTENT2) + `
1271
-
1272
- [truncated]`;
1273
- if (content.length < 100) {
1274
- filtered++;
1275
- continue;
1276
- }
1277
- const contentHash = createContentHash(content);
1278
- const dedupKey = `codex:${sessionId}`;
1279
- const fingerprint = `${dedupKey}:${contentHash}`;
1280
- if (isIngested("codex", fingerprint)) {
1281
- deduped++;
1282
- continue;
1283
- }
1284
- memories.push({
1285
- content,
1286
- containerTag: "codex-chats",
1287
- metadata: {
1288
- dedupKey,
1289
- contentHash,
1290
- source: "codex",
1291
- sessionId,
1292
- date,
1293
- ...project ? { project } : {}
1294
- }
1295
- });
1296
- sessionIds.push(sessionId);
1297
- }
1298
- } catch {}
1299
- }
1300
1264
  const sessionsDir = join4(homedir4(), ".codex", "sessions");
1301
- if (existsSync3(sessionsDir)) {
1265
+ if (existsSync4(sessionsDir)) {
1302
1266
  try {
1303
1267
  const stats = { filtered: 0, deduped: 0 };
1304
1268
  walkCodexSessions(sessionsDir, memories, sessionIds, stats);
@@ -1315,10 +1279,8 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
1315
1279
  walkCodexSessions(join4(dir, entry.name), memories, sessionIds, stats);
1316
1280
  } else if (entry.name.endsWith(".jsonl")) {
1317
1281
  const sessionId = basename2(entry.name, ".jsonl");
1318
- if (sessionIds.includes(sessionId))
1319
- continue;
1320
1282
  try {
1321
- const lines = readFileSync3(join4(dir, entry.name), "utf-8").split(`
1283
+ const lines = readFileSync4(join4(dir, entry.name), "utf-8").split(`
1322
1284
  `).filter(Boolean);
1323
1285
  let date = null;
1324
1286
  let project = null;
@@ -1329,19 +1291,29 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
1329
1291
  try {
1330
1292
  const obj = JSON.parse(line);
1331
1293
  if (!date && obj.timestamp)
1332
- date = obj.timestamp.slice(0, 10);
1294
+ date = typeof obj.timestamp === "string" ? obj.timestamp.slice(0, 10) : null;
1333
1295
  if (obj.type === "session_meta" && obj.payload?.cwd) {
1334
1296
  project = projectFromCwd(obj.payload.cwd);
1335
1297
  }
1336
- if (obj.type !== "response_item" || obj.payload?.type !== "message")
1337
- continue;
1338
- const role = obj.payload.role;
1339
- const msgContent = obj.payload.content;
1340
- if (!Array.isArray(msgContent))
1298
+ let role;
1299
+ let msgContent;
1300
+ if (obj.type === "response_item" && obj.payload?.type === "message") {
1301
+ role = obj.payload.role;
1302
+ msgContent = Array.isArray(obj.payload.content) ? obj.payload.content : undefined;
1303
+ } else if (obj.type === "message" && obj.role) {
1304
+ role = obj.role;
1305
+ msgContent = Array.isArray(obj.content) ? obj.content : undefined;
1306
+ }
1307
+ if (!role || !msgContent)
1341
1308
  continue;
1342
1309
  if (role === "user") {
1343
1310
  for (const block of msgContent) {
1344
1311
  if (block.type === "input_text" && block.text) {
1312
+ if (!project) {
1313
+ const cwd = extractCwd(block.text);
1314
+ if (cwd)
1315
+ project = projectFromCwd(cwd);
1316
+ }
1345
1317
  const text = cleanText(block.text);
1346
1318
  if (isCodexBoilerplate(text))
1347
1319
  continue;
@@ -1376,11 +1348,10 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
1376
1348
  continue;
1377
1349
  }
1378
1350
  const body = rounds.map((r) => {
1379
- const q = r.user.length > 300 ? r.user.slice(0, 300) + "..." : r.user;
1380
1351
  const assistant = r.assistantParts.join(`
1381
1352
 
1382
1353
  `);
1383
- return `## Q: ${q}
1354
+ return `## Q: ${r.user}
1384
1355
 
1385
1356
  ${assistant}`;
1386
1357
  }).join(`
@@ -1422,9 +1393,9 @@ ${body}`;
1422
1393
  }
1423
1394
 
1424
1395
  // src/ingest/cursor.ts
1425
- import { readFileSync as readFileSync4, statSync } from "node:fs";
1396
+ import { readFileSync as readFileSync5, statSync } from "node:fs";
1426
1397
  import { join as join5 } from "node:path";
1427
- import { createRequire as createRequire2 } from "node:module";
1398
+ import { createRequire as createRequire3 } from "node:module";
1428
1399
  var MAX_CONTENT3 = 48000;
1429
1400
  var MAX_DB_SIZE = 2 * 1024 * 1024 * 1024;
1430
1401
  var WARN_DB_SIZE = 500 * 1024 * 1024;
@@ -1432,10 +1403,10 @@ var MIN_TURN_LEN2 = 50;
1432
1403
  function loadSqlJs(wasmDir) {
1433
1404
  try {
1434
1405
  if (wasmDir) {
1435
- const require2 = createRequire2(join5(wasmDir, "sql.js", "package.json"));
1406
+ const require2 = createRequire3(join5(wasmDir, "sql.js", "package.json"));
1436
1407
  return require2("sql.js");
1437
1408
  } else {
1438
- const require2 = createRequire2(import.meta.url);
1409
+ const require2 = createRequire3(import.meta.url);
1439
1410
  return require2("sql.js");
1440
1411
  }
1441
1412
  } catch {
@@ -1448,7 +1419,7 @@ function openDb(initSqlJs, dbPath, wasmDir) {
1448
1419
  locateOpts.locateFile = (file) => join5(wasmDir, "sql.js", "dist", file);
1449
1420
  }
1450
1421
  return initSqlJs(locateOpts).then((SQL) => {
1451
- const buffer = readFileSync4(dbPath);
1422
+ const buffer = readFileSync5(dbPath);
1452
1423
  return new SQL.Database(buffer);
1453
1424
  });
1454
1425
  }
@@ -1542,8 +1513,7 @@ async function ingestCursor(dbPath, wasmDir) {
1542
1513
  const date = parsed.createdAt ? new Date(parsed.createdAt).toISOString().slice(0, 10) : "unknown";
1543
1514
  const header = `# ${sessionName} | ${date}`;
1544
1515
  const body = turns.map((t) => {
1545
- const q = t.user.length > 300 ? t.user.slice(0, 300) + "..." : t.user;
1546
- return `## Q: ${q}
1516
+ return `## Q: ${t.user}
1547
1517
 
1548
1518
  ${t.assistant}`;
1549
1519
  }).join(`
@@ -1589,7 +1559,7 @@ ${body}`;
1589
1559
 
1590
1560
  // src/ingest/codebase.ts
1591
1561
  import { createHash as createHash2 } from "node:crypto";
1592
- import { readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync2, existsSync as existsSync4 } from "node:fs";
1562
+ import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync2, existsSync as existsSync5 } from "node:fs";
1593
1563
  import { join as join6, relative, extname, resolve } from "node:path";
1594
1564
  var DEFAULT_IGNORE = new Set([
1595
1565
  "node_modules",
@@ -1676,10 +1646,10 @@ var MAX_FILE_SIZE = 1e5;
1676
1646
  function parseGitignore(rootPath) {
1677
1647
  const patterns = new Set;
1678
1648
  const gitignorePath = join6(rootPath, ".gitignore");
1679
- if (!existsSync4(gitignorePath))
1649
+ if (!existsSync5(gitignorePath))
1680
1650
  return patterns;
1681
1651
  try {
1682
- const content = readFileSync5(gitignorePath, "utf-8");
1652
+ const content = readFileSync6(gitignorePath, "utf-8");
1683
1653
  for (const line of content.split(`
1684
1654
  `)) {
1685
1655
  const trimmed = line.trim();
@@ -1792,7 +1762,7 @@ function indexCodebase(rootPath) {
1792
1762
  }
1793
1763
  let raw;
1794
1764
  try {
1795
- raw = readFileSync5(fullPath, "utf-8");
1765
+ raw = readFileSync6(fullPath, "utf-8");
1796
1766
  } catch {
1797
1767
  skipped++;
1798
1768
  continue;
@@ -1962,10 +1932,10 @@ async function uploadBulk(apiKey, memories, onProgress) {
1962
1932
  }
1963
1933
 
1964
1934
  // src/configure.ts
1965
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
1935
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
1966
1936
  import { dirname, join as join7 } from "node:path";
1967
- import { homedir as homedir5 } from "node:os";
1968
- import { spawnSync as spawnSync2 } from "node:child_process";
1937
+ import { homedir as homedir5, platform as platform4 } from "node:os";
1938
+ import { spawnSync } from "node:child_process";
1969
1939
  var CONARE_URL = "https://mcp.conare.ai";
1970
1940
  var SERVER_NAME = "conare-memory";
1971
1941
  var MCP_TARGETS = [
@@ -1976,7 +1946,7 @@ var MCP_TARGETS = [
1976
1946
  ];
1977
1947
  function readJsonFile(path) {
1978
1948
  try {
1979
- return JSON.parse(readFileSync6(path, "utf-8"));
1949
+ return JSON.parse(readFileSync7(path, "utf-8"));
1980
1950
  } catch {
1981
1951
  return {};
1982
1952
  }
@@ -2006,13 +1976,14 @@ function upsertMcpServer(path, apiKey) {
2006
1976
  function configureClaude(apiKey) {
2007
1977
  const claudeConfigPath = join7(homedir5(), ".claude.json");
2008
1978
  const claudeMcpPath = join7(homedir5(), ".claude", "mcp.json");
2009
- if (spawnSync2("claude", ["mcp", "add-json", SERVER_NAME, "--scope", "user", JSON.stringify(getServerConfig(apiKey))], {
2010
- stdio: "ignore"
1979
+ if (spawnSync("claude", ["mcp", "add-json", SERVER_NAME, "--scope", "user", JSON.stringify(getServerConfig(apiKey))], {
1980
+ stdio: "ignore",
1981
+ shell: platform4() === "win32"
2011
1982
  }).status === 0) {
2012
1983
  return "Claude Code configured via `claude mcp add-json`";
2013
1984
  }
2014
1985
  upsertMcpServer(claudeConfigPath, apiKey);
2015
- if (existsSync5(join7(homedir5(), ".claude"))) {
1986
+ if (existsSync6(join7(homedir5(), ".claude"))) {
2016
1987
  upsertMcpServer(claudeMcpPath, apiKey);
2017
1988
  }
2018
1989
  return `Claude Code configured at ${claudeConfigPath}`;
@@ -2039,16 +2010,16 @@ function configureMcp(apiKey, targets = ["claude", "cursor", "codex"]) {
2039
2010
  }
2040
2011
 
2041
2012
  // src/config.ts
2042
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
2013
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
2043
2014
  import { join as join8 } from "node:path";
2044
2015
  import { homedir as homedir6 } from "node:os";
2045
2016
  var CONFIG_DIR = join8(homedir6(), ".conare");
2046
2017
  var CONFIG_PATH = join8(CONFIG_DIR, "config.json");
2047
2018
  function readConfig() {
2048
2019
  try {
2049
- if (!existsSync6(CONFIG_PATH))
2020
+ if (!existsSync7(CONFIG_PATH))
2050
2021
  return {};
2051
- return JSON.parse(readFileSync7(CONFIG_PATH, "utf-8"));
2022
+ return JSON.parse(readFileSync8(CONFIG_PATH, "utf-8"));
2052
2023
  } catch {
2053
2024
  return {};
2054
2025
  }
@@ -2063,9 +2034,9 @@ function getSavedApiKey() {
2063
2034
  }
2064
2035
 
2065
2036
  // src/sync.ts
2066
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync8, chmodSync, cpSync, rmSync, symlinkSync, readlinkSync, appendFileSync } from "node:fs";
2037
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, unlinkSync, readFileSync as readFileSync9, chmodSync, cpSync, rmSync, symlinkSync, readlinkSync, appendFileSync } from "node:fs";
2067
2038
  import { join as join9, dirname as dirname2 } from "node:path";
2068
- import { homedir as homedir7, platform as platform2 } from "node:os";
2039
+ import { homedir as homedir7, platform as platform5 } from "node:os";
2069
2040
  import { execSync } from "node:child_process";
2070
2041
  var CONARE_DIR = join9(homedir7(), ".conare");
2071
2042
  var BIN_DIR = join9(CONARE_DIR, "bin");
@@ -2075,6 +2046,40 @@ var PLIST_PATH = join9(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.pl
2075
2046
  var SYSTEMD_DIR = join9(homedir7(), ".config", "systemd", "user");
2076
2047
  var SYSTEMD_SERVICE = join9(SYSTEMD_DIR, "conare-sync.service");
2077
2048
  var SYSTEMD_TIMER = join9(SYSTEMD_DIR, "conare-sync.timer");
2049
+ var TASK_NAME = "ConareMemorySync";
2050
+ var RUN_CMD = `@echo off
2051
+ REM Conare Memory — background sync wrapper (Windows)
2052
+ setlocal
2053
+
2054
+ set "CONARE_DIR=%USERPROFILE%\\.conare"
2055
+ set "LOG=%CONARE_DIR%\\ingest.log"
2056
+ set "LOCKFILE=%CONARE_DIR%\\sync.lock.d"
2057
+
2058
+ REM File lock: prevent concurrent runs
2059
+ if exist "%LOCKFILE%" (
2060
+ echo %DATE% %TIME% SKIP: another instance running >> "%LOG%"
2061
+ exit /b 0
2062
+ )
2063
+ mkdir "%LOCKFILE%" 2>nul
2064
+ if errorlevel 1 (
2065
+ echo %DATE% %TIME% SKIP: lock failed >> "%LOG%"
2066
+ exit /b 0
2067
+ )
2068
+
2069
+ REM Resolve node
2070
+ where node >nul 2>nul
2071
+ if errorlevel 1 (
2072
+ echo %DATE% %TIME% ERROR: node not found >> "%LOG%"
2073
+ rmdir "%LOCKFILE%" 2>nul
2074
+ exit /b 1
2075
+ )
2076
+
2077
+ echo %DATE% %TIME% START sync >> "%LOG%"
2078
+ node "%CONARE_DIR%\\bin\\conare-ingest.mjs" --config-file "%CONARE_DIR%\\config.json" --ingest-only --quiet 2>> "%LOG%"
2079
+ echo %DATE% %TIME% DONE sync (exit %ERRORLEVEL%) >> "%LOG%"
2080
+
2081
+ rmdir "%LOCKFILE%" 2>nul
2082
+ `;
2078
2083
  var RUN_SH = `#!/bin/bash
2079
2084
  # Conare Memory — background sync wrapper
2080
2085
  # Resolves node at runtime to survive nvm/fnm upgrades
@@ -2205,7 +2210,7 @@ function persistBinary(apiKey) {
2205
2210
  throw new Error("Could not locate CLI bundle. Run from an installed conare package.");
2206
2211
  }
2207
2212
  const dest = join9(BIN_DIR, "conare-ingest.mjs");
2208
- const content = readFileSync8(cliEntry, "utf-8");
2213
+ const content = readFileSync9(cliEntry, "utf-8");
2209
2214
  writeFileSync4(dest, content);
2210
2215
  const sqlJsDir = findSqlJs();
2211
2216
  if (sqlJsDir) {
@@ -2217,20 +2222,24 @@ function persistBinary(apiKey) {
2217
2222
  }
2218
2223
  const runShPath = join9(BIN_DIR, "run.sh");
2219
2224
  writeFileSync4(runShPath, RUN_SH);
2220
- chmodSync(runShPath, 493);
2225
+ try {
2226
+ chmodSync(runShPath, 493);
2227
+ } catch {}
2228
+ const runCmdPath = join9(BIN_DIR, "run.cmd");
2229
+ writeFileSync4(runCmdPath, RUN_CMD);
2221
2230
  writeFileSync4(CONFIG_PATH2, JSON.stringify({ apiKey }, null, 2) + `
2222
2231
  `);
2223
2232
  }
2224
2233
  function findCliBundle() {
2225
2234
  const entry = process.argv[1];
2226
- if (entry && existsSync7(entry))
2235
+ if (entry && existsSync8(entry))
2227
2236
  return entry;
2228
2237
  const candidates = [
2229
2238
  join9(dirname2(new URL(import.meta.url).pathname), "index.js"),
2230
2239
  join9(dirname2(new URL(import.meta.url).pathname), "..", "dist", "index.js")
2231
2240
  ];
2232
2241
  for (const c of candidates) {
2233
- if (existsSync7(c))
2242
+ if (existsSync8(c))
2234
2243
  return c;
2235
2244
  }
2236
2245
  return null;
@@ -2242,7 +2251,7 @@ function findSqlJs() {
2242
2251
  join9(dirname2(new URL(import.meta.url).pathname), "..", "..", "node_modules", "sql.js")
2243
2252
  ];
2244
2253
  for (const c of candidates) {
2245
- if (existsSync7(c))
2254
+ if (existsSync8(c))
2246
2255
  return c;
2247
2256
  }
2248
2257
  return null;
@@ -2290,6 +2299,24 @@ function setupLinuxCron(intervalMinutes) {
2290
2299
  }
2291
2300
  }
2292
2301
  function installGlobalCommand() {
2302
+ const isWindows = platform5() === "win32";
2303
+ if (isWindows) {
2304
+ const wrapper2 = join9(BIN_DIR, "conare.cmd");
2305
+ const content2 = `@echo off\r
2306
+ node "%USERPROFILE%\\.conare\\bin\\conare-ingest.mjs" %*\r
2307
+ `;
2308
+ writeFileSync4(wrapper2, content2);
2309
+ const pathDirs = (process.env.PATH || "").split(";");
2310
+ if (pathDirs.some((d) => d.toLowerCase() === BIN_DIR.toLowerCase())) {
2311
+ return "Global command: conare (via .conare\\bin in PATH)";
2312
+ }
2313
+ try {
2314
+ execSync(`powershell -NoProfile -Command "[Environment]::SetEnvironmentVariable('PATH', [Environment]::GetEnvironmentVariable('PATH','User') + ';${BIN_DIR}', 'User')"`, { stdio: "ignore" });
2315
+ return `Global command: conare (added .conare\\bin to user PATH — restart terminal)`;
2316
+ } catch {
2317
+ return `Global command: add ${BIN_DIR} to your PATH manually`;
2318
+ }
2319
+ }
2293
2320
  const wrapper = join9(BIN_DIR, "conare");
2294
2321
  const content = `#!/bin/bash
2295
2322
  # Conare global command — runs the persisted CLI bundle
@@ -2311,7 +2338,7 @@ exec "$NODE" "$CONARE_DIR/bin/conare-ingest.mjs" "$@"
2311
2338
  chmodSync(wrapper, 493);
2312
2339
  const symlinkTarget = "/usr/local/bin/conare";
2313
2340
  try {
2314
- if (existsSync7(symlinkTarget)) {
2341
+ if (existsSync8(symlinkTarget)) {
2315
2342
  try {
2316
2343
  const existing = readlinkSync(symlinkTarget);
2317
2344
  if (existing === wrapper)
@@ -2329,7 +2356,7 @@ exec "$NODE" "$CONARE_DIR/bin/conare-ingest.mjs" "$@"
2329
2356
  const shellProfile = getShellProfile();
2330
2357
  if (shellProfile) {
2331
2358
  try {
2332
- const profileContent = existsSync7(shellProfile) ? readFileSync8(shellProfile, "utf-8") : "";
2359
+ const profileContent = existsSync8(shellProfile) ? readFileSync9(shellProfile, "utf-8") : "";
2333
2360
  const exportLine = `export PATH="$HOME/.conare/bin:$PATH"`;
2334
2361
  if (!profileContent.includes(".conare/bin")) {
2335
2362
  appendFileSync(shellProfile, `
@@ -2353,19 +2380,26 @@ function getShellProfile() {
2353
2380
  return join9(home, ".zshrc");
2354
2381
  if (shell.includes("bash")) {
2355
2382
  const profile = join9(home, ".bash_profile");
2356
- if (platform2() === "darwin" && existsSync7(profile))
2383
+ if (platform5() === "darwin" && existsSync8(profile))
2357
2384
  return profile;
2358
2385
  return join9(home, ".bashrc");
2359
2386
  }
2360
- if (existsSync7(join9(home, ".zshrc")))
2387
+ if (existsSync8(join9(home, ".zshrc")))
2361
2388
  return join9(home, ".zshrc");
2362
- if (existsSync7(join9(home, ".bashrc")))
2389
+ if (existsSync8(join9(home, ".bashrc")))
2363
2390
  return join9(home, ".bashrc");
2364
2391
  return null;
2365
2392
  }
2393
+ function setupWindows(intervalMinutes) {
2394
+ const runCmd = join9(BIN_DIR, "run.cmd").replace(/\//g, "\\");
2395
+ try {
2396
+ execSync(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
2397
+ } catch {}
2398
+ execSync(`schtasks /Create /TN "${TASK_NAME}" /TR "${runCmd}" /SC MINUTE /MO ${intervalMinutes} /F`, { stdio: "ignore" });
2399
+ }
2366
2400
  function installSync(apiKey, intervalMinutes = 10) {
2367
2401
  const messages = [];
2368
- const os = platform2();
2402
+ const os = platform5();
2369
2403
  persistBinary(apiKey);
2370
2404
  messages.push("Persisted CLI to ~/.conare/bin/");
2371
2405
  messages.push("Saved config to ~/.conare/config.json");
@@ -2376,6 +2410,9 @@ function installSync(apiKey, intervalMinutes = 10) {
2376
2410
  setupMacOS(intervalMinutes);
2377
2411
  messages.push(`Installed launchd agent (every ${intervalMinutes} min)`);
2378
2412
  messages.push(`Plist: ${PLIST_PATH}`);
2413
+ } else if (os === "win32") {
2414
+ setupWindows(intervalMinutes);
2415
+ messages.push(`Installed Windows Task Scheduler (every ${intervalMinutes} min)`);
2379
2416
  } else if (os === "linux") {
2380
2417
  if (hasSystemd()) {
2381
2418
  setupLinuxSystemd(intervalMinutes);
@@ -2391,23 +2428,28 @@ function installSync(apiKey, intervalMinutes = 10) {
2391
2428
  }
2392
2429
  function uninstallSync() {
2393
2430
  const messages = [];
2394
- const os = platform2();
2431
+ const os = platform5();
2395
2432
  if (os === "darwin") {
2396
2433
  try {
2397
2434
  execSync(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
2398
2435
  } catch {}
2399
- if (existsSync7(PLIST_PATH)) {
2436
+ if (existsSync8(PLIST_PATH)) {
2400
2437
  unlinkSync(PLIST_PATH);
2401
2438
  messages.push("Removed launchd agent");
2402
2439
  }
2440
+ } else if (os === "win32") {
2441
+ try {
2442
+ execSync(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
2443
+ messages.push("Removed Windows scheduled task");
2444
+ } catch {}
2403
2445
  } else if (os === "linux") {
2404
2446
  if (hasSystemd()) {
2405
2447
  try {
2406
2448
  execSync("systemctl --user disable --now conare-sync.timer 2>/dev/null", { stdio: "ignore" });
2407
2449
  } catch {}
2408
- if (existsSync7(SYSTEMD_SERVICE))
2450
+ if (existsSync8(SYSTEMD_SERVICE))
2409
2451
  unlinkSync(SYSTEMD_SERVICE);
2410
- if (existsSync7(SYSTEMD_TIMER))
2452
+ if (existsSync8(SYSTEMD_TIMER))
2411
2453
  unlinkSync(SYSTEMD_TIMER);
2412
2454
  try {
2413
2455
  execSync("systemctl --user daemon-reload", { stdio: "ignore" });
@@ -2430,10 +2472,10 @@ function uninstallSync() {
2430
2472
  join9(CONARE_DIR, "sync.lock")
2431
2473
  ];
2432
2474
  for (const f of filesToRemove) {
2433
- if (existsSync7(f))
2475
+ if (existsSync8(f))
2434
2476
  unlinkSync(f);
2435
2477
  }
2436
- if (existsSync7(BIN_DIR)) {
2478
+ if (existsSync8(BIN_DIR)) {
2437
2479
  rmSync(BIN_DIR, { recursive: true, force: true });
2438
2480
  messages.push("Removed ~/.conare/bin/");
2439
2481
  }
@@ -2625,8 +2667,8 @@ async function main() {
2625
2667
  let configFileKey;
2626
2668
  if (opts.configFile) {
2627
2669
  try {
2628
- const { readFileSync: readFileSync9 } = await import("node:fs");
2629
- const raw = JSON.parse(readFileSync9(opts.configFile, "utf-8"));
2670
+ const { readFileSync: readFileSync10 } = await import("node:fs");
2671
+ const raw = JSON.parse(readFileSync10(opts.configFile, "utf-8"));
2630
2672
  configFileKey = raw.apiKey || raw.key;
2631
2673
  if (!configFileKey) {
2632
2674
  console.error(`Error: no apiKey/key found in ${opts.configFile}`);
@@ -2658,7 +2700,7 @@ async function main() {
2658
2700
  }));
2659
2701
  let interactiveMode = false;
2660
2702
  if (shouldRunInteractive) {
2661
- const detectedTools = detect();
2703
+ const detectedTools = await detect();
2662
2704
  interactiveTargets = MCP_TARGETS.map((target) => {
2663
2705
  const detected = detectedTools.find((tool) => target.id === "claude" && tool.name === "Claude Code" || target.id === "cursor" && tool.name === "Cursor" || target.id === "codex" && tool.name === "Codex" || target.id === "openclaw" && tool.name === "OpenClaw");
2664
2706
  return {
@@ -2718,7 +2760,7 @@ async function main() {
2718
2760
  log("");
2719
2761
  }
2720
2762
  }
2721
- if (!opts.wasmDir && existsSync8(join10(process.cwd(), "node_modules", "sql.js"))) {
2763
+ if (!opts.wasmDir && existsSync9(join10(process.cwd(), "node_modules", "sql.js"))) {
2722
2764
  opts.wasmDir = join10(process.cwd(), "node_modules");
2723
2765
  }
2724
2766
  if (effectiveConfigOnly) {
@@ -2795,7 +2837,7 @@ Nothing new to index.`);
2795
2837
  }
2796
2838
  log();
2797
2839
  }
2798
- const tools = detect();
2840
+ const tools = await detect();
2799
2841
  if (!interactiveMode) {
2800
2842
  log("Detected AI tools:");
2801
2843
  for (const t of tools) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conare",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Conare CLI for ingesting AI chat history and configuring memory at conare.ai",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,16 @@
13
13
  "files": [
14
14
  "dist"
15
15
  ],
16
- "keywords": ["conare", "ai", "memory", "mcp", "claude", "cursor", "codex", "context"],
16
+ "keywords": [
17
+ "conare",
18
+ "ai",
19
+ "memory",
20
+ "mcp",
21
+ "claude",
22
+ "cursor",
23
+ "codex",
24
+ "context"
25
+ ],
17
26
  "homepage": "https://conare.ai",
18
27
  "license": "MIT",
19
28
  "dependencies": {