conare 0.3.1 → 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 +189 -180
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -875,10 +875,10 @@ 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, existsSync as existsSync3 } 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";
@@ -1036,6 +1027,9 @@ function cleanText(raw) {
1036
1027
  text = text.replace(/<good-behaviour>[\s\S]*?<\/good-behaviour>/g, "");
1037
1028
  text = text.replace(/<frontend-design>[\s\S]*?<\/frontend-design>/g, "");
1038
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, "");
1039
1033
  return text.trim();
1040
1034
  }
1041
1035
  function createContentHash(content) {
@@ -1044,7 +1038,7 @@ function createContentHash(content) {
1044
1038
  function getIngested() {
1045
1039
  try {
1046
1040
  if (existsSync2(MANIFEST_PATH)) {
1047
- return JSON.parse(readFileSync(MANIFEST_PATH, "utf-8"));
1041
+ return JSON.parse(readFileSync2(MANIFEST_PATH, "utf-8"));
1048
1042
  }
1049
1043
  } catch {}
1050
1044
  return {};
@@ -1083,8 +1077,17 @@ var MAX_CONTENT = 48000;
1083
1077
  var MIN_TURN_LEN = 50;
1084
1078
  function resolveProjectName(dirName) {
1085
1079
  const segments = dirName.replace(/^-/, "").split("-");
1086
- let resolved = "/";
1087
- let i = 0;
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;
1088
1091
  while (i < segments.length) {
1089
1092
  let found = false;
1090
1093
  for (let end = segments.length;end > i; end--) {
@@ -1103,10 +1106,11 @@ function resolveProjectName(dirName) {
1103
1106
  }
1104
1107
  }
1105
1108
  const home = homedir3();
1106
- if (resolved.startsWith(home + "/")) {
1107
- return resolved.slice(home.length + 1);
1109
+ const sep = isWindows ? "\\" : "/";
1110
+ if (resolved.startsWith(home + sep)) {
1111
+ return resolved.slice(home.length + 1).replace(/\\/g, "/");
1108
1112
  }
1109
- return resolved;
1113
+ return resolved.replace(/\\/g, "/");
1110
1114
  }
1111
1115
  function extractText(content) {
1112
1116
  if (typeof content === "string")
@@ -1182,7 +1186,7 @@ function ingestClaude() {
1182
1186
  }
1183
1187
  for (const file of files) {
1184
1188
  const sessionId = basename(file, ".jsonl");
1185
- const raw = readFileSync2(join3(projPath, file), "utf-8");
1189
+ const raw = readFileSync3(join3(projPath, file), "utf-8");
1186
1190
  const { turns, date } = parseSession(raw.split(`
1187
1191
  `));
1188
1192
  if (turns.length === 0) {
@@ -1191,8 +1195,7 @@ function ingestClaude() {
1191
1195
  }
1192
1196
  const header = `# Chat: ${project}${date ? ` | ${date}` : ""}`;
1193
1197
  const body = turns.map((t) => {
1194
- const q = t.user.length > 300 ? t.user.slice(0, 300) + "..." : t.user;
1195
- return `## Q: ${q}
1198
+ return `## Q: ${t.user}
1196
1199
 
1197
1200
  ${t.assistant}`;
1198
1201
  }).join(`
@@ -1233,103 +1236,31 @@ ${body}`;
1233
1236
  }
1234
1237
 
1235
1238
  // src/ingest/codex.ts
1236
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "node:fs";
1239
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
1237
1240
  import { join as join4, basename as basename2 } from "node:path";
1238
1241
  import { homedir as homedir4 } from "node:os";
1239
1242
  var MAX_CONTENT2 = 48000;
1240
1243
  function isCodexBoilerplate(text) {
1241
1244
  return text.startsWith("# AGENTS.md instructions for") || text.startsWith("<INSTRUCTIONS>") || text.startsWith("<user_instructions>") || text.startsWith("<user_action>");
1242
1245
  }
1243
- function stripEnvironmentContext(text) {
1244
- let cwd = null;
1245
- const cwdMatch = text.match(/<cwd>([^<]+)<\/cwd>/);
1246
- if (cwdMatch)
1247
- cwd = cwdMatch[1];
1248
- const cleaned = text.replace(/<environment_context>[\s\S]*?<\/environment_context>/g, "").trim();
1249
- return { text: cleaned, cwd };
1246
+ function extractCwd(text) {
1247
+ const match = text.match(/<cwd>([^<]+)<\/cwd>/);
1248
+ return match ? match[1] : null;
1250
1249
  }
1251
1250
  function projectFromCwd(cwd) {
1252
- 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\/[^/]+\//, "");
1253
1258
  }
1254
1259
  function ingestCodex() {
1255
1260
  const memories = [];
1256
1261
  const sessionIds = [];
1257
1262
  let filtered = 0;
1258
1263
  let deduped = 0;
1259
- const historyPath = join4(homedir4(), ".codex", "history.jsonl");
1260
- if (existsSync4(historyPath)) {
1261
- try {
1262
- const lines = readFileSync3(historyPath, "utf-8").split(`
1263
- `).filter(Boolean);
1264
- const sessions = new Map;
1265
- for (const line of lines) {
1266
- try {
1267
- const obj = JSON.parse(line);
1268
- if (!obj.session_id || !obj.text)
1269
- continue;
1270
- if (!sessions.has(obj.session_id))
1271
- sessions.set(obj.session_id, []);
1272
- sessions.get(obj.session_id).push({ ts: obj.ts, text: obj.text });
1273
- } catch {
1274
- continue;
1275
- }
1276
- }
1277
- for (const [sessionId, entries] of sessions) {
1278
- entries.sort((a, b) => a.ts - b.ts);
1279
- const date = new Date(entries[0].ts * 1000).toISOString().slice(0, 10);
1280
- let project = null;
1281
- const cleanEntries = [];
1282
- for (const e of entries) {
1283
- let text = cleanText(e.text);
1284
- if (isCodexBoilerplate(text))
1285
- continue;
1286
- const env = stripEnvironmentContext(text);
1287
- text = env.text;
1288
- if (!project && env.cwd)
1289
- project = projectFromCwd(env.cwd);
1290
- if (text.length === 0)
1291
- continue;
1292
- cleanEntries.push(text.length > 300 ? text.slice(0, 300) + "..." : text);
1293
- }
1294
- const body = cleanEntries.filter(Boolean).join(`
1295
-
1296
- ---
1297
-
1298
- `);
1299
- let content = `# Codex Chat | ${date}
1300
-
1301
- ${body}`;
1302
- if (content.length > MAX_CONTENT2)
1303
- content = content.slice(0, MAX_CONTENT2) + `
1304
-
1305
- [truncated]`;
1306
- if (content.length < 100) {
1307
- filtered++;
1308
- continue;
1309
- }
1310
- const contentHash = createContentHash(content);
1311
- const dedupKey = `codex:${sessionId}`;
1312
- const fingerprint = `${dedupKey}:${contentHash}`;
1313
- if (isIngested("codex", fingerprint)) {
1314
- deduped++;
1315
- continue;
1316
- }
1317
- memories.push({
1318
- content,
1319
- containerTag: "codex-chats",
1320
- metadata: {
1321
- dedupKey,
1322
- contentHash,
1323
- source: "codex",
1324
- sessionId,
1325
- date,
1326
- ...project ? { project } : {}
1327
- }
1328
- });
1329
- sessionIds.push(sessionId);
1330
- }
1331
- } catch {}
1332
- }
1333
1264
  const sessionsDir = join4(homedir4(), ".codex", "sessions");
1334
1265
  if (existsSync4(sessionsDir)) {
1335
1266
  try {
@@ -1348,10 +1279,8 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
1348
1279
  walkCodexSessions(join4(dir, entry.name), memories, sessionIds, stats);
1349
1280
  } else if (entry.name.endsWith(".jsonl")) {
1350
1281
  const sessionId = basename2(entry.name, ".jsonl");
1351
- if (sessionIds.includes(sessionId))
1352
- continue;
1353
1282
  try {
1354
- const lines = readFileSync3(join4(dir, entry.name), "utf-8").split(`
1283
+ const lines = readFileSync4(join4(dir, entry.name), "utf-8").split(`
1355
1284
  `).filter(Boolean);
1356
1285
  let date = null;
1357
1286
  let project = null;
@@ -1362,19 +1291,29 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
1362
1291
  try {
1363
1292
  const obj = JSON.parse(line);
1364
1293
  if (!date && obj.timestamp)
1365
- date = obj.timestamp.slice(0, 10);
1294
+ date = typeof obj.timestamp === "string" ? obj.timestamp.slice(0, 10) : null;
1366
1295
  if (obj.type === "session_meta" && obj.payload?.cwd) {
1367
1296
  project = projectFromCwd(obj.payload.cwd);
1368
1297
  }
1369
- if (obj.type !== "response_item" || obj.payload?.type !== "message")
1370
- continue;
1371
- const role = obj.payload.role;
1372
- const msgContent = obj.payload.content;
1373
- 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)
1374
1308
  continue;
1375
1309
  if (role === "user") {
1376
1310
  for (const block of msgContent) {
1377
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
+ }
1378
1317
  const text = cleanText(block.text);
1379
1318
  if (isCodexBoilerplate(text))
1380
1319
  continue;
@@ -1409,11 +1348,10 @@ function walkCodexSessions(dir, memories, sessionIds, stats) {
1409
1348
  continue;
1410
1349
  }
1411
1350
  const body = rounds.map((r) => {
1412
- const q = r.user.length > 300 ? r.user.slice(0, 300) + "..." : r.user;
1413
1351
  const assistant = r.assistantParts.join(`
1414
1352
 
1415
1353
  `);
1416
- return `## Q: ${q}
1354
+ return `## Q: ${r.user}
1417
1355
 
1418
1356
  ${assistant}`;
1419
1357
  }).join(`
@@ -1455,9 +1393,9 @@ ${body}`;
1455
1393
  }
1456
1394
 
1457
1395
  // src/ingest/cursor.ts
1458
- import { readFileSync as readFileSync4, statSync } from "node:fs";
1396
+ import { readFileSync as readFileSync5, statSync } from "node:fs";
1459
1397
  import { join as join5 } from "node:path";
1460
- import { createRequire as createRequire2 } from "node:module";
1398
+ import { createRequire as createRequire3 } from "node:module";
1461
1399
  var MAX_CONTENT3 = 48000;
1462
1400
  var MAX_DB_SIZE = 2 * 1024 * 1024 * 1024;
1463
1401
  var WARN_DB_SIZE = 500 * 1024 * 1024;
@@ -1465,10 +1403,10 @@ var MIN_TURN_LEN2 = 50;
1465
1403
  function loadSqlJs(wasmDir) {
1466
1404
  try {
1467
1405
  if (wasmDir) {
1468
- const require2 = createRequire2(join5(wasmDir, "sql.js", "package.json"));
1406
+ const require2 = createRequire3(join5(wasmDir, "sql.js", "package.json"));
1469
1407
  return require2("sql.js");
1470
1408
  } else {
1471
- const require2 = createRequire2(import.meta.url);
1409
+ const require2 = createRequire3(import.meta.url);
1472
1410
  return require2("sql.js");
1473
1411
  }
1474
1412
  } catch {
@@ -1481,7 +1419,7 @@ function openDb(initSqlJs, dbPath, wasmDir) {
1481
1419
  locateOpts.locateFile = (file) => join5(wasmDir, "sql.js", "dist", file);
1482
1420
  }
1483
1421
  return initSqlJs(locateOpts).then((SQL) => {
1484
- const buffer = readFileSync4(dbPath);
1422
+ const buffer = readFileSync5(dbPath);
1485
1423
  return new SQL.Database(buffer);
1486
1424
  });
1487
1425
  }
@@ -1575,8 +1513,7 @@ async function ingestCursor(dbPath, wasmDir) {
1575
1513
  const date = parsed.createdAt ? new Date(parsed.createdAt).toISOString().slice(0, 10) : "unknown";
1576
1514
  const header = `# ${sessionName} | ${date}`;
1577
1515
  const body = turns.map((t) => {
1578
- const q = t.user.length > 300 ? t.user.slice(0, 300) + "..." : t.user;
1579
- return `## Q: ${q}
1516
+ return `## Q: ${t.user}
1580
1517
 
1581
1518
  ${t.assistant}`;
1582
1519
  }).join(`
@@ -1622,7 +1559,7 @@ ${body}`;
1622
1559
 
1623
1560
  // src/ingest/codebase.ts
1624
1561
  import { createHash as createHash2 } from "node:crypto";
1625
- import { readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync2, existsSync as existsSync5 } from "node:fs";
1562
+ import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync2, existsSync as existsSync5 } from "node:fs";
1626
1563
  import { join as join6, relative, extname, resolve } from "node:path";
1627
1564
  var DEFAULT_IGNORE = new Set([
1628
1565
  "node_modules",
@@ -1712,7 +1649,7 @@ function parseGitignore(rootPath) {
1712
1649
  if (!existsSync5(gitignorePath))
1713
1650
  return patterns;
1714
1651
  try {
1715
- const content = readFileSync5(gitignorePath, "utf-8");
1652
+ const content = readFileSync6(gitignorePath, "utf-8");
1716
1653
  for (const line of content.split(`
1717
1654
  `)) {
1718
1655
  const trimmed = line.trim();
@@ -1825,7 +1762,7 @@ function indexCodebase(rootPath) {
1825
1762
  }
1826
1763
  let raw;
1827
1764
  try {
1828
- raw = readFileSync5(fullPath, "utf-8");
1765
+ raw = readFileSync6(fullPath, "utf-8");
1829
1766
  } catch {
1830
1767
  skipped++;
1831
1768
  continue;
@@ -1995,10 +1932,10 @@ async function uploadBulk(apiKey, memories, onProgress) {
1995
1932
  }
1996
1933
 
1997
1934
  // src/configure.ts
1998
- import { existsSync as existsSync6, 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";
1999
1936
  import { dirname, join as join7 } from "node:path";
2000
- import { homedir as homedir5 } from "node:os";
2001
- 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";
2002
1939
  var CONARE_URL = "https://mcp.conare.ai";
2003
1940
  var SERVER_NAME = "conare-memory";
2004
1941
  var MCP_TARGETS = [
@@ -2009,7 +1946,7 @@ var MCP_TARGETS = [
2009
1946
  ];
2010
1947
  function readJsonFile(path) {
2011
1948
  try {
2012
- return JSON.parse(readFileSync6(path, "utf-8"));
1949
+ return JSON.parse(readFileSync7(path, "utf-8"));
2013
1950
  } catch {
2014
1951
  return {};
2015
1952
  }
@@ -2039,8 +1976,9 @@ function upsertMcpServer(path, apiKey) {
2039
1976
  function configureClaude(apiKey) {
2040
1977
  const claudeConfigPath = join7(homedir5(), ".claude.json");
2041
1978
  const claudeMcpPath = join7(homedir5(), ".claude", "mcp.json");
2042
- if (spawnSync2("claude", ["mcp", "add-json", SERVER_NAME, "--scope", "user", JSON.stringify(getServerConfig(apiKey))], {
2043
- stdio: "ignore"
1979
+ if (spawnSync("claude", ["mcp", "add-json", SERVER_NAME, "--scope", "user", JSON.stringify(getServerConfig(apiKey))], {
1980
+ stdio: "ignore",
1981
+ shell: platform4() === "win32"
2044
1982
  }).status === 0) {
2045
1983
  return "Claude Code configured via `claude mcp add-json`";
2046
1984
  }
@@ -2072,7 +2010,7 @@ function configureMcp(apiKey, targets = ["claude", "cursor", "codex"]) {
2072
2010
  }
2073
2011
 
2074
2012
  // src/config.ts
2075
- import { existsSync as existsSync7, 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";
2076
2014
  import { join as join8 } from "node:path";
2077
2015
  import { homedir as homedir6 } from "node:os";
2078
2016
  var CONFIG_DIR = join8(homedir6(), ".conare");
@@ -2081,7 +2019,7 @@ function readConfig() {
2081
2019
  try {
2082
2020
  if (!existsSync7(CONFIG_PATH))
2083
2021
  return {};
2084
- return JSON.parse(readFileSync7(CONFIG_PATH, "utf-8"));
2022
+ return JSON.parse(readFileSync8(CONFIG_PATH, "utf-8"));
2085
2023
  } catch {
2086
2024
  return {};
2087
2025
  }
@@ -2096,9 +2034,9 @@ function getSavedApiKey() {
2096
2034
  }
2097
2035
 
2098
2036
  // src/sync.ts
2099
- import { existsSync as existsSync8, 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";
2100
2038
  import { join as join9, dirname as dirname2 } from "node:path";
2101
- import { homedir as homedir7, platform as platform2 } from "node:os";
2039
+ import { homedir as homedir7, platform as platform5 } from "node:os";
2102
2040
  import { execSync } from "node:child_process";
2103
2041
  var CONARE_DIR = join9(homedir7(), ".conare");
2104
2042
  var BIN_DIR = join9(CONARE_DIR, "bin");
@@ -2108,6 +2046,40 @@ var PLIST_PATH = join9(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.pl
2108
2046
  var SYSTEMD_DIR = join9(homedir7(), ".config", "systemd", "user");
2109
2047
  var SYSTEMD_SERVICE = join9(SYSTEMD_DIR, "conare-sync.service");
2110
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
+ `;
2111
2083
  var RUN_SH = `#!/bin/bash
2112
2084
  # Conare Memory — background sync wrapper
2113
2085
  # Resolves node at runtime to survive nvm/fnm upgrades
@@ -2238,7 +2210,7 @@ function persistBinary(apiKey) {
2238
2210
  throw new Error("Could not locate CLI bundle. Run from an installed conare package.");
2239
2211
  }
2240
2212
  const dest = join9(BIN_DIR, "conare-ingest.mjs");
2241
- const content = readFileSync8(cliEntry, "utf-8");
2213
+ const content = readFileSync9(cliEntry, "utf-8");
2242
2214
  writeFileSync4(dest, content);
2243
2215
  const sqlJsDir = findSqlJs();
2244
2216
  if (sqlJsDir) {
@@ -2250,7 +2222,11 @@ function persistBinary(apiKey) {
2250
2222
  }
2251
2223
  const runShPath = join9(BIN_DIR, "run.sh");
2252
2224
  writeFileSync4(runShPath, RUN_SH);
2253
- chmodSync(runShPath, 493);
2225
+ try {
2226
+ chmodSync(runShPath, 493);
2227
+ } catch {}
2228
+ const runCmdPath = join9(BIN_DIR, "run.cmd");
2229
+ writeFileSync4(runCmdPath, RUN_CMD);
2254
2230
  writeFileSync4(CONFIG_PATH2, JSON.stringify({ apiKey }, null, 2) + `
2255
2231
  `);
2256
2232
  }
@@ -2323,6 +2299,24 @@ function setupLinuxCron(intervalMinutes) {
2323
2299
  }
2324
2300
  }
2325
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
+ }
2326
2320
  const wrapper = join9(BIN_DIR, "conare");
2327
2321
  const content = `#!/bin/bash
2328
2322
  # Conare global command — runs the persisted CLI bundle
@@ -2362,7 +2356,7 @@ exec "$NODE" "$CONARE_DIR/bin/conare-ingest.mjs" "$@"
2362
2356
  const shellProfile = getShellProfile();
2363
2357
  if (shellProfile) {
2364
2358
  try {
2365
- const profileContent = existsSync8(shellProfile) ? readFileSync8(shellProfile, "utf-8") : "";
2359
+ const profileContent = existsSync8(shellProfile) ? readFileSync9(shellProfile, "utf-8") : "";
2366
2360
  const exportLine = `export PATH="$HOME/.conare/bin:$PATH"`;
2367
2361
  if (!profileContent.includes(".conare/bin")) {
2368
2362
  appendFileSync(shellProfile, `
@@ -2386,7 +2380,7 @@ function getShellProfile() {
2386
2380
  return join9(home, ".zshrc");
2387
2381
  if (shell.includes("bash")) {
2388
2382
  const profile = join9(home, ".bash_profile");
2389
- if (platform2() === "darwin" && existsSync8(profile))
2383
+ if (platform5() === "darwin" && existsSync8(profile))
2390
2384
  return profile;
2391
2385
  return join9(home, ".bashrc");
2392
2386
  }
@@ -2396,9 +2390,16 @@ function getShellProfile() {
2396
2390
  return join9(home, ".bashrc");
2397
2391
  return null;
2398
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
+ }
2399
2400
  function installSync(apiKey, intervalMinutes = 10) {
2400
2401
  const messages = [];
2401
- const os = platform2();
2402
+ const os = platform5();
2402
2403
  persistBinary(apiKey);
2403
2404
  messages.push("Persisted CLI to ~/.conare/bin/");
2404
2405
  messages.push("Saved config to ~/.conare/config.json");
@@ -2409,6 +2410,9 @@ function installSync(apiKey, intervalMinutes = 10) {
2409
2410
  setupMacOS(intervalMinutes);
2410
2411
  messages.push(`Installed launchd agent (every ${intervalMinutes} min)`);
2411
2412
  messages.push(`Plist: ${PLIST_PATH}`);
2413
+ } else if (os === "win32") {
2414
+ setupWindows(intervalMinutes);
2415
+ messages.push(`Installed Windows Task Scheduler (every ${intervalMinutes} min)`);
2412
2416
  } else if (os === "linux") {
2413
2417
  if (hasSystemd()) {
2414
2418
  setupLinuxSystemd(intervalMinutes);
@@ -2424,7 +2428,7 @@ function installSync(apiKey, intervalMinutes = 10) {
2424
2428
  }
2425
2429
  function uninstallSync() {
2426
2430
  const messages = [];
2427
- const os = platform2();
2431
+ const os = platform5();
2428
2432
  if (os === "darwin") {
2429
2433
  try {
2430
2434
  execSync(`launchctl bootout gui/${uid()} "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
@@ -2433,6 +2437,11 @@ function uninstallSync() {
2433
2437
  unlinkSync(PLIST_PATH);
2434
2438
  messages.push("Removed launchd agent");
2435
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 {}
2436
2445
  } else if (os === "linux") {
2437
2446
  if (hasSystemd()) {
2438
2447
  try {
@@ -2658,8 +2667,8 @@ async function main() {
2658
2667
  let configFileKey;
2659
2668
  if (opts.configFile) {
2660
2669
  try {
2661
- const { readFileSync: readFileSync9 } = await import("node:fs");
2662
- 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"));
2663
2672
  configFileKey = raw.apiKey || raw.key;
2664
2673
  if (!configFileKey) {
2665
2674
  console.error(`Error: no apiKey/key found in ${opts.configFile}`);
@@ -2691,7 +2700,7 @@ async function main() {
2691
2700
  }));
2692
2701
  let interactiveMode = false;
2693
2702
  if (shouldRunInteractive) {
2694
- const detectedTools = detect();
2703
+ const detectedTools = await detect();
2695
2704
  interactiveTargets = MCP_TARGETS.map((target) => {
2696
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");
2697
2706
  return {
@@ -2828,7 +2837,7 @@ Nothing new to index.`);
2828
2837
  }
2829
2838
  log();
2830
2839
  }
2831
- const tools = detect();
2840
+ const tools = await detect();
2832
2841
  if (!interactiveMode) {
2833
2842
  log("Detected AI tools:");
2834
2843
  for (const t of tools) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conare",
3
- "version": "0.3.1",
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": {