codemem 0.20.4 → 0.20.5-alpha.1

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.
@@ -1,5 +1,5 @@
1
1
  export const parseSemver = (value) => {
2
- const match = String(value || "").trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
2
+ const match = String(value || "").trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-.*)?$/);
3
3
  if (!match) return null;
4
4
  return [Number(match[1]), Number(match[2]), Number(match[3])];
5
5
  };
@@ -15,7 +15,7 @@ import {
15
15
 
16
16
  const TRUTHY_VALUES = ["1", "true", "yes"];
17
17
  const DISABLED_VALUES = ["0", "false", "off"];
18
- const PINNED_BACKEND_VERSION = "0.20.4";
18
+ const PINNED_BACKEND_VERSION = "0.20.5-alpha.1";
19
19
 
20
20
  const normalizeEnvValue = (value) => (value || "").toLowerCase();
21
21
  const envHasValue = (value, truthyValues) =>
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAEN,KAAK,uBAAuB,EAG5B,MAAM,uBAAuB,CAAC;AAQ/B,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKhE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASjD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAED,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,GAAG,IAAI,CAGf;AA0KD,wBAAgB,yBAAyB,CACxC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,uBAAuB,EACnC,QAAQ,GAAE,MAAM,EAAqB,GACnC,MAAM,EAAE,CAgBV;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS9D;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUpF;AA0OD,eAAO,MAAM,YAAY,SAuBtB,CAAC"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAEN,KAAK,uBAAuB,EAG5B,MAAM,uBAAuB,CAAC;AAQ/B,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKhE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASjD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAED,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,GAAG,IAAI,CAGf;AAmLD,wBAAgB,yBAAyB,CACxC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,uBAAuB,EACnC,QAAQ,GAAE,MAAM,EAAqB,GACnC,MAAM,EAAE,CAgBV;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS9D;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUpF;AA+OD,eAAO,MAAM,YAAY,SAuBtB,CAAC"}
@@ -4,9 +4,9 @@
4
4
  * Replaces Python's install_plugin_cmd + install_mcp_cmd.
5
5
  *
6
6
  * What it does:
7
- * 1. Copies the plugin file to ~/.config/opencode/plugin/codemem.js
8
- * 2. Adds/updates the MCP entry in ~/.config/opencode/opencode.json
9
- * 3. Copies the compat lib to ~/.config/opencode/lib/compat.js
7
+ * 1. Adds "codemem" to the plugin array in ~/.config/opencode/opencode.jsonc
8
+ * 2. Adds/updates the MCP entry in ~/.config/opencode/opencode.jsonc
9
+ * 3. For Claude Code: installs MCP config and guides marketplace plugin install
10
10
  *
11
11
  * Designed to be safe to run repeatedly (idempotent unless --force).
12
12
  */
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmKpC,eAAO,MAAM,YAAY,SA6BtB,CAAC"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyQpC,eAAO,MAAM,YAAY,SA6BtB,CAAC"}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { DEFAULT_COORDINATOR_DB_PATH, MemoryStore, ObserverClient, RawEventSweeper, VERSION, backfillTagsText, backfillVectors, buildRawEventEnvelopeFromHook, connect, coordinatorCreateInviteAction, coordinatorImportInviteAction, coordinatorListJoinRequestsAction, coordinatorReviewJoinRequestAction, createCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, ensureDeviceIdentity, exportMemories, fingerprintPublicKey, getRawEventStatus, importMemories, initDatabase, isEmbeddingDisabled, loadPublicKey, loadSqliteVec, rawEventsGate, readCodememConfigFile, readCoordinatorSyncConfig, readImportPayload, resolveDbPath, resolveProject, retryRawEventFailures, runSyncDaemon, runSyncPass, schema, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
3
3
  import { Command } from "commander";
4
4
  import omelette from "omelette";
5
- import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
5
+ import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
6
6
  import { styleText } from "node:util";
7
7
  import * as p from "@clack/prompts";
8
8
  import { homedir, networkInterfaces } from "node:os";
@@ -1020,6 +1020,14 @@ async function waitForProcessExit(pid, timeoutMs = 5e3) {
1020
1020
  await new Promise((resolve) => setTimeout(resolve, 100));
1021
1021
  }
1022
1022
  }
1023
+ async function waitForPortRelease(host, port, timeoutMs = 1e4) {
1024
+ const deadline = Date.now() + timeoutMs;
1025
+ while (Date.now() < deadline) {
1026
+ if (!await isPortOpen(host, port)) return true;
1027
+ await new Promise((resolve) => setTimeout(resolve, 200));
1028
+ }
1029
+ return false;
1030
+ }
1023
1031
  async function stopExistingViewer(dbPath, target) {
1024
1032
  const pidPath = pidFilePath(dbPath);
1025
1033
  const record = readViewerPidRecord(dbPath);
@@ -1250,6 +1258,7 @@ async function runServeInvocation(invocation) {
1250
1258
  p.outro("done");
1251
1259
  return;
1252
1260
  }
1261
+ if (!await waitForPortRelease(invocation.host, invocation.port)) p.log.warn(`Port ${invocation.port} still in use after stop — restart may fail`);
1253
1262
  } else if (invocation.mode === "stop") {
1254
1263
  p.intro("codemem viewer");
1255
1264
  p.outro("No background viewer found");
@@ -1313,9 +1322,9 @@ function writeJsonConfig(path, data) {
1313
1322
  * Replaces Python's install_plugin_cmd + install_mcp_cmd.
1314
1323
  *
1315
1324
  * What it does:
1316
- * 1. Copies the plugin file to ~/.config/opencode/plugin/codemem.js
1317
- * 2. Adds/updates the MCP entry in ~/.config/opencode/opencode.json
1318
- * 3. Copies the compat lib to ~/.config/opencode/lib/compat.js
1325
+ * 1. Adds "codemem" to the plugin array in ~/.config/opencode/opencode.jsonc
1326
+ * 2. Adds/updates the MCP entry in ~/.config/opencode/opencode.jsonc
1327
+ * 3. For Claude Code: installs MCP config and guides marketplace plugin install
1319
1328
  *
1320
1329
  * Designed to be safe to run repeatedly (idempotent unless --force).
1321
1330
  */
@@ -1325,57 +1334,93 @@ function opencodeConfigDir() {
1325
1334
  function claudeConfigDir() {
1326
1335
  return join(homedir(), ".claude");
1327
1336
  }
1328
- /**
1329
- * Find the plugin source file — walk up from this module's location
1330
- * to find the .opencode/plugins/codemem.js in the package tree.
1331
- */
1332
- function findPluginSource() {
1333
- let dir = dirname(import.meta.url.replace("file://", ""));
1334
- for (let i = 0; i < 6; i++) {
1335
- const candidate = join(dir, ".opencode", "plugins", "codemem.js");
1336
- if (existsSync(candidate)) return candidate;
1337
- const nmCandidate = join(dir, "node_modules", "codemem", ".opencode", "plugins", "codemem.js");
1338
- if (existsSync(nmCandidate)) return nmCandidate;
1339
- const parent = dirname(dir);
1340
- if (parent === dir) break;
1341
- dir = parent;
1337
+ /** The npm package name used in the OpenCode plugin array. */
1338
+ var OPENCODE_PLUGIN_SPEC = "codemem";
1339
+ /** Remove legacy copied plugin JS file from ~/.config/opencode/plugins/codemem.js */
1340
+ function migrateLegacyOpencodePlugin() {
1341
+ const legacyPlugin = join(opencodeConfigDir(), "plugins", "codemem.js");
1342
+ const legacyCompat = join(opencodeConfigDir(), "lib", "compat.js");
1343
+ if (existsSync(legacyPlugin)) try {
1344
+ rmSync(legacyPlugin);
1345
+ p.log.step("Removed legacy copied plugin: ~/.config/opencode/plugins/codemem.js");
1346
+ } catch {
1347
+ p.log.warn("Could not remove legacy plugin file — remove manually if needed");
1342
1348
  }
1343
- return null;
1349
+ if (existsSync(legacyCompat)) try {
1350
+ rmSync(legacyCompat);
1351
+ p.log.step("Removed legacy compat lib: ~/.config/opencode/lib/compat.js");
1352
+ } catch {}
1353
+ }
1354
+ /** Detect and upgrade legacy uvx/uv-based MCP entries in OpenCode config. */
1355
+ function migrateLegacyOpencodeMcp(config) {
1356
+ const mcpConfig = config.mcp;
1357
+ if (!mcpConfig || typeof mcpConfig !== "object") return false;
1358
+ const entry = mcpConfig.codemem;
1359
+ if (!entry || typeof entry !== "object") return false;
1360
+ const command = entry.command;
1361
+ if (Array.isArray(command) && command.some((arg) => typeof arg === "string" && (arg === "uvx" || arg === "uv")) || typeof command === "string" && (command === "uvx" || command === "uv")) {
1362
+ p.log.step("Upgrading legacy uvx MCP entry to npx");
1363
+ mcpConfig.codemem = {
1364
+ type: "local",
1365
+ command: [
1366
+ "npx",
1367
+ "codemem",
1368
+ "mcp"
1369
+ ],
1370
+ enabled: true
1371
+ };
1372
+ return true;
1373
+ }
1374
+ return false;
1344
1375
  }
1345
- function findCompatSource() {
1346
- let dir = dirname(import.meta.url.replace("file://", ""));
1347
- for (let i = 0; i < 6; i++) {
1348
- const candidate = join(dir, ".opencode", "lib", "compat.js");
1349
- if (existsSync(candidate)) return candidate;
1350
- const nmCandidate = join(dir, "node_modules", "codemem", ".opencode", "lib", "compat.js");
1351
- if (existsSync(nmCandidate)) return nmCandidate;
1352
- const legacyCandidate = join(dir, "node_modules", "@kunickiaj", "codemem", ".opencode", "lib", "compat.js");
1353
- if (existsSync(legacyCandidate)) return legacyCandidate;
1354
- const parent = dirname(dir);
1355
- if (parent === dir) break;
1356
- dir = parent;
1376
+ /** Detect and upgrade legacy uvx-based MCP entries in Claude settings. */
1377
+ function migrateLegacyClaudeMcp(settings) {
1378
+ const mcpServers = settings.mcpServers;
1379
+ if (!mcpServers || typeof mcpServers !== "object") return false;
1380
+ const entry = mcpServers.codemem;
1381
+ if (!entry || typeof entry !== "object") return false;
1382
+ const command = entry.command;
1383
+ const args = entry.args;
1384
+ if (typeof command === "string" && (command === "uvx" || command === "uv") || Array.isArray(args) && args.some((arg) => typeof arg === "string" && (arg.startsWith("codemem==") || arg === "uvx"))) {
1385
+ p.log.step("Upgrading legacy uvx Claude MCP entry to npx");
1386
+ mcpServers.codemem = {
1387
+ command: "npx",
1388
+ args: [
1389
+ "-y",
1390
+ "codemem",
1391
+ "mcp"
1392
+ ]
1393
+ };
1394
+ return true;
1357
1395
  }
1358
- return null;
1396
+ return false;
1359
1397
  }
1360
1398
  function installPlugin(force) {
1361
- const source = findPluginSource();
1362
- if (!source) {
1363
- p.log.error("Plugin file not found in package tree");
1399
+ migrateLegacyOpencodePlugin();
1400
+ const configPath = resolveOpencodeConfigPath(opencodeConfigDir());
1401
+ let config;
1402
+ try {
1403
+ config = loadJsoncConfig(configPath);
1404
+ } catch (err) {
1405
+ p.log.error(`Failed to parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
1364
1406
  return false;
1365
1407
  }
1366
- const destDir = join(opencodeConfigDir(), "plugins");
1367
- const dest = join(destDir, "codemem.js");
1368
- if (existsSync(dest) && !force) p.log.info(`Plugin already installed at ${dest}`);
1369
- else {
1370
- mkdirSync(destDir, { recursive: true });
1371
- copyFileSync(source, dest);
1372
- p.log.success(`Plugin installed: ${dest}`);
1408
+ let plugins = config.plugin;
1409
+ if (!Array.isArray(plugins)) plugins = [];
1410
+ const hasCodemem = plugins.some((entry) => typeof entry === "string" && (entry === OPENCODE_PLUGIN_SPEC || entry.startsWith(`${OPENCODE_PLUGIN_SPEC}@`)));
1411
+ if (hasCodemem && !force) {
1412
+ p.log.info(`Plugin "${OPENCODE_PLUGIN_SPEC}" already in plugin array`);
1413
+ return true;
1373
1414
  }
1374
- const compatSource = findCompatSource();
1375
- if (compatSource) {
1376
- const compatDir = join(opencodeConfigDir(), "lib");
1377
- mkdirSync(compatDir, { recursive: true });
1378
- copyFileSync(compatSource, join(compatDir, "compat.js"));
1415
+ if (hasCodemem && force) plugins = plugins.filter((entry) => typeof entry !== "string" || entry !== OPENCODE_PLUGIN_SPEC && !entry.startsWith(`${OPENCODE_PLUGIN_SPEC}@`));
1416
+ plugins.push(OPENCODE_PLUGIN_SPEC);
1417
+ config.plugin = plugins;
1418
+ try {
1419
+ writeJsonConfig(configPath, config);
1420
+ p.log.success(`Plugin "${OPENCODE_PLUGIN_SPEC}" added to ${configPath}`);
1421
+ } catch (err) {
1422
+ p.log.error(`Failed to write ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
1423
+ return false;
1379
1424
  }
1380
1425
  return true;
1381
1426
  }
@@ -1390,20 +1435,23 @@ function installMcp(force) {
1390
1435
  }
1391
1436
  let mcpConfig = config.mcp;
1392
1437
  if (mcpConfig == null || typeof mcpConfig !== "object" || Array.isArray(mcpConfig)) mcpConfig = {};
1393
- if ("codemem" in mcpConfig && !force) {
1438
+ const migrated = migrateLegacyOpencodeMcp(config);
1439
+ if ("codemem" in mcpConfig && !force && !migrated) {
1394
1440
  p.log.info(`MCP entry already exists in ${configPath}`);
1395
1441
  return true;
1396
1442
  }
1397
- mcpConfig.codemem = {
1398
- type: "local",
1399
- command: [
1400
- "npx",
1401
- "codemem",
1402
- "mcp"
1403
- ],
1404
- enabled: true
1405
- };
1406
- config.mcp = mcpConfig;
1443
+ if (!migrated) {
1444
+ mcpConfig.codemem = {
1445
+ type: "local",
1446
+ command: [
1447
+ "npx",
1448
+ "codemem",
1449
+ "mcp"
1450
+ ],
1451
+ enabled: true
1452
+ };
1453
+ config.mcp = mcpConfig;
1454
+ }
1407
1455
  try {
1408
1456
  writeJsonConfig(configPath, config);
1409
1457
  p.log.success(`MCP entry installed: ${configPath}`);
@@ -1413,6 +1461,12 @@ function installMcp(force) {
1413
1461
  }
1414
1462
  return true;
1415
1463
  }
1464
+ function isClaudeHooksPluginInstalled() {
1465
+ const pluginDir = join(claudeConfigDir(), "plugins", "codemem");
1466
+ if (existsSync(pluginDir)) return true;
1467
+ if (existsSync(join(pluginDir, "hooks", "hooks.json"))) return true;
1468
+ return false;
1469
+ }
1416
1470
  function installClaudeMcp(force) {
1417
1471
  const settingsPath = join(claudeConfigDir(), "settings.json");
1418
1472
  let settings;
@@ -1423,22 +1477,36 @@ function installClaudeMcp(force) {
1423
1477
  }
1424
1478
  let mcpServers = settings.mcpServers;
1425
1479
  if (mcpServers == null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) mcpServers = {};
1426
- if ("codemem" in mcpServers && !force) {
1427
- p.log.info(`Claude MCP entry already exists in ${settingsPath}`);
1428
- return true;
1429
- }
1430
- mcpServers.codemem = {
1431
- command: "npx",
1432
- args: ["codemem", "mcp"]
1433
- };
1434
- settings.mcpServers = mcpServers;
1435
- try {
1436
- writeJsonConfig(settingsPath, settings);
1437
- p.log.success(`Claude MCP entry installed: ${settingsPath}`);
1438
- } catch (err) {
1439
- p.log.error(`Failed to write ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`);
1440
- return false;
1480
+ const migrated = migrateLegacyClaudeMcp(settings);
1481
+ if ("codemem" in mcpServers && !force && !migrated) p.log.info(`Claude MCP entry already exists in ${settingsPath}`);
1482
+ else {
1483
+ if (!migrated) {
1484
+ mcpServers.codemem = {
1485
+ command: "npx",
1486
+ args: [
1487
+ "-y",
1488
+ "codemem",
1489
+ "mcp"
1490
+ ]
1491
+ };
1492
+ settings.mcpServers = mcpServers;
1493
+ }
1494
+ try {
1495
+ writeJsonConfig(settingsPath, settings);
1496
+ p.log.success(`Claude MCP entry installed: ${settingsPath}`);
1497
+ } catch (err) {
1498
+ p.log.error(`Failed to write ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`);
1499
+ return false;
1500
+ }
1441
1501
  }
1502
+ if (!isClaudeHooksPluginInstalled() || force) {
1503
+ p.log.info("To install the Claude Code hooks plugin, run in Claude Code:");
1504
+ p.log.info(" /plugin marketplace add kunickiaj/codemem");
1505
+ p.log.info(" /plugin install codemem");
1506
+ p.log.info("");
1507
+ p.log.info("To update an existing install:");
1508
+ p.log.info(" /plugin marketplace update codemem-marketplace");
1509
+ } else p.log.info("Claude Code hooks plugin appears to be installed");
1442
1510
  return true;
1443
1511
  }
1444
1512
  var setupCommand = new Command("setup").configureHelp(helpStyle).description("Install codemem plugin + MCP config for OpenCode and Claude Code").option("--force", "overwrite existing installations").option("--opencode-only", "only install for OpenCode").option("--claude-only", "only install for Claude Code").action((opts) => {