codemem 0.20.3 → 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.
- package/.opencode/lib/compat.js +1 -1
- package/.opencode/plugins/codemem.js +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/setup.d.ts +3 -3
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/index.js +186 -101
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/.opencode/lib/compat.js
CHANGED
|
@@ -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.
|
|
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":"
|
|
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"}
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Replaces Python's install_plugin_cmd + install_mcp_cmd.
|
|
5
5
|
*
|
|
6
6
|
* What it does:
|
|
7
|
-
* 1.
|
|
8
|
-
* 2. Adds/updates the MCP entry in ~/.config/opencode/opencode.
|
|
9
|
-
* 3.
|
|
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;
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AA8BH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AA8BH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwHpC,eAAO,MAAM,WAAW,SAE+B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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, readImportPayload, resolveDbPath, resolveProject, retryRawEventFailures, runSyncDaemon, runSyncPass, schema, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
|
|
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 {
|
|
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);
|
|
@@ -1112,7 +1120,7 @@ async function startBackgroundViewer(invocation) {
|
|
|
1112
1120
|
p.outro(`Viewer started in background (pid ${child.pid}) at http://${invocation.host}:${invocation.port}`);
|
|
1113
1121
|
}
|
|
1114
1122
|
async function startForegroundViewer(invocation) {
|
|
1115
|
-
const { createApp, closeStore, getStore } = await import("@codemem/server");
|
|
1123
|
+
const { createApp, createSyncApp, closeStore, getStore } = await import("@codemem/server");
|
|
1116
1124
|
const { serve } = await import("@hono/node-server");
|
|
1117
1125
|
if (invocation.dbPath) process.env.CODEMEM_DB = invocation.dbPath;
|
|
1118
1126
|
if (await isPortOpen(invocation.host, invocation.port)) {
|
|
@@ -1137,15 +1145,16 @@ async function startForegroundViewer(invocation) {
|
|
|
1137
1145
|
sweeper.start();
|
|
1138
1146
|
const syncAbort = new AbortController();
|
|
1139
1147
|
let syncRunning = false;
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1148
|
+
const syncConfig = readCoordinatorSyncConfig(readCodememConfigFile());
|
|
1149
|
+
const syncEnabled = syncConfig.syncEnabled;
|
|
1150
|
+
if (syncEnabled) {
|
|
1142
1151
|
syncRunning = true;
|
|
1143
|
-
const syncIntervalS =
|
|
1152
|
+
const syncIntervalS = syncConfig.syncIntervalS;
|
|
1144
1153
|
runSyncDaemon({
|
|
1145
1154
|
dbPath: resolveDbPath(invocation.dbPath ?? void 0),
|
|
1146
1155
|
intervalS: syncIntervalS,
|
|
1147
|
-
host:
|
|
1148
|
-
port:
|
|
1156
|
+
host: syncConfig.syncHost,
|
|
1157
|
+
port: syncConfig.syncPort,
|
|
1149
1158
|
signal: syncAbort.signal
|
|
1150
1159
|
}).catch((err) => {
|
|
1151
1160
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1154,13 +1163,30 @@ async function startForegroundViewer(invocation) {
|
|
|
1154
1163
|
syncRunning = false;
|
|
1155
1164
|
});
|
|
1156
1165
|
}
|
|
1157
|
-
const
|
|
1166
|
+
const appOpts = {
|
|
1158
1167
|
storeFactory: () => store,
|
|
1159
1168
|
sweeper,
|
|
1160
1169
|
observer
|
|
1161
|
-
}
|
|
1170
|
+
};
|
|
1171
|
+
const app = createApp(appOpts);
|
|
1162
1172
|
const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
|
|
1163
1173
|
const pidPath = pidFilePath(dbPath);
|
|
1174
|
+
let syncServer = null;
|
|
1175
|
+
let syncListenerReady = false;
|
|
1176
|
+
if (syncEnabled) {
|
|
1177
|
+
syncServer = serve({
|
|
1178
|
+
fetch: createSyncApp(appOpts).fetch,
|
|
1179
|
+
hostname: syncConfig.syncHost,
|
|
1180
|
+
port: syncConfig.syncPort
|
|
1181
|
+
}, (info) => {
|
|
1182
|
+
syncListenerReady = true;
|
|
1183
|
+
p.log.step(`Sync protocol listening on http://${info.address}:${info.port}`);
|
|
1184
|
+
});
|
|
1185
|
+
syncServer.on("error", (err) => {
|
|
1186
|
+
if (!syncListenerReady && err.code === "EADDRINUSE") p.log.warn(`Sync port ${syncConfig.syncPort} already in use; peer sync protocol unavailable`);
|
|
1187
|
+
else p.log.warn(`Sync listener error: ${err.message}`);
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1164
1190
|
const server = serve({
|
|
1165
1191
|
fetch: app.fetch,
|
|
1166
1192
|
hostname: invocation.host,
|
|
@@ -1186,13 +1212,21 @@ async function startForegroundViewer(invocation) {
|
|
|
1186
1212
|
p.outro("shutting down");
|
|
1187
1213
|
syncAbort.abort();
|
|
1188
1214
|
await sweeper.stop();
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1215
|
+
await new Promise((resolve) => {
|
|
1216
|
+
let remaining = syncServer ? 2 : 1;
|
|
1217
|
+
const done = () => {
|
|
1218
|
+
if (--remaining === 0) resolve();
|
|
1219
|
+
};
|
|
1220
|
+
syncServer?.close(done);
|
|
1221
|
+
server.close(done);
|
|
1222
|
+
}).catch(() => {});
|
|
1223
|
+
try {
|
|
1224
|
+
rmSync(pidPath);
|
|
1225
|
+
} catch {}
|
|
1226
|
+
closeStore();
|
|
1227
|
+
process.exit(0);
|
|
1228
|
+
};
|
|
1229
|
+
const forceShutdown = () => {
|
|
1196
1230
|
setTimeout(() => {
|
|
1197
1231
|
try {
|
|
1198
1232
|
rmSync(pidPath);
|
|
@@ -1202,9 +1236,11 @@ async function startForegroundViewer(invocation) {
|
|
|
1202
1236
|
}, 5e3).unref();
|
|
1203
1237
|
};
|
|
1204
1238
|
process.on("SIGINT", () => {
|
|
1239
|
+
forceShutdown();
|
|
1205
1240
|
shutdown();
|
|
1206
1241
|
});
|
|
1207
1242
|
process.on("SIGTERM", () => {
|
|
1243
|
+
forceShutdown();
|
|
1208
1244
|
shutdown();
|
|
1209
1245
|
});
|
|
1210
1246
|
}
|
|
@@ -1222,6 +1258,7 @@ async function runServeInvocation(invocation) {
|
|
|
1222
1258
|
p.outro("done");
|
|
1223
1259
|
return;
|
|
1224
1260
|
}
|
|
1261
|
+
if (!await waitForPortRelease(invocation.host, invocation.port)) p.log.warn(`Port ${invocation.port} still in use after stop — restart may fail`);
|
|
1225
1262
|
} else if (invocation.mode === "stop") {
|
|
1226
1263
|
p.intro("codemem viewer");
|
|
1227
1264
|
p.outro("No background viewer found");
|
|
@@ -1285,9 +1322,9 @@ function writeJsonConfig(path, data) {
|
|
|
1285
1322
|
* Replaces Python's install_plugin_cmd + install_mcp_cmd.
|
|
1286
1323
|
*
|
|
1287
1324
|
* What it does:
|
|
1288
|
-
* 1.
|
|
1289
|
-
* 2. Adds/updates the MCP entry in ~/.config/opencode/opencode.
|
|
1290
|
-
* 3.
|
|
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
|
|
1291
1328
|
*
|
|
1292
1329
|
* Designed to be safe to run repeatedly (idempotent unless --force).
|
|
1293
1330
|
*/
|
|
@@ -1297,57 +1334,93 @@ function opencodeConfigDir() {
|
|
|
1297
1334
|
function claudeConfigDir() {
|
|
1298
1335
|
return join(homedir(), ".claude");
|
|
1299
1336
|
}
|
|
1300
|
-
/**
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
if
|
|
1311
|
-
const parent = dirname(dir);
|
|
1312
|
-
if (parent === dir) break;
|
|
1313
|
-
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");
|
|
1314
1348
|
}
|
|
1315
|
-
|
|
1349
|
+
if (existsSync(legacyCompat)) try {
|
|
1350
|
+
rmSync(legacyCompat);
|
|
1351
|
+
p.log.step("Removed legacy compat lib: ~/.config/opencode/lib/compat.js");
|
|
1352
|
+
} catch {}
|
|
1316
1353
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
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;
|
|
1329
1373
|
}
|
|
1330
|
-
return
|
|
1374
|
+
return false;
|
|
1375
|
+
}
|
|
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;
|
|
1395
|
+
}
|
|
1396
|
+
return false;
|
|
1331
1397
|
}
|
|
1332
1398
|
function installPlugin(force) {
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
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)}`);
|
|
1336
1406
|
return false;
|
|
1337
1407
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
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;
|
|
1345
1414
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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;
|
|
1351
1424
|
}
|
|
1352
1425
|
return true;
|
|
1353
1426
|
}
|
|
@@ -1362,20 +1435,23 @@ function installMcp(force) {
|
|
|
1362
1435
|
}
|
|
1363
1436
|
let mcpConfig = config.mcp;
|
|
1364
1437
|
if (mcpConfig == null || typeof mcpConfig !== "object" || Array.isArray(mcpConfig)) mcpConfig = {};
|
|
1365
|
-
|
|
1438
|
+
const migrated = migrateLegacyOpencodeMcp(config);
|
|
1439
|
+
if ("codemem" in mcpConfig && !force && !migrated) {
|
|
1366
1440
|
p.log.info(`MCP entry already exists in ${configPath}`);
|
|
1367
1441
|
return true;
|
|
1368
1442
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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
|
+
}
|
|
1379
1455
|
try {
|
|
1380
1456
|
writeJsonConfig(configPath, config);
|
|
1381
1457
|
p.log.success(`MCP entry installed: ${configPath}`);
|
|
@@ -1385,6 +1461,12 @@ function installMcp(force) {
|
|
|
1385
1461
|
}
|
|
1386
1462
|
return true;
|
|
1387
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
|
+
}
|
|
1388
1470
|
function installClaudeMcp(force) {
|
|
1389
1471
|
const settingsPath = join(claudeConfigDir(), "settings.json");
|
|
1390
1472
|
let settings;
|
|
@@ -1395,22 +1477,36 @@ function installClaudeMcp(force) {
|
|
|
1395
1477
|
}
|
|
1396
1478
|
let mcpServers = settings.mcpServers;
|
|
1397
1479
|
if (mcpServers == null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) mcpServers = {};
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
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
|
+
}
|
|
1413
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");
|
|
1414
1510
|
return true;
|
|
1415
1511
|
}
|
|
1416
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) => {
|
|
@@ -1572,22 +1668,11 @@ function parseStoredAddressEndpoint(value) {
|
|
|
1572
1668
|
async function runServeLifecycle(action, opts) {
|
|
1573
1669
|
if (opts.user === false || opts.system === true) p.log.warn("TS sync lifecycle currently manages the local viewer process, not separate user/system services.");
|
|
1574
1670
|
if (action === "start") {
|
|
1575
|
-
|
|
1576
|
-
if (config.sync_enabled !== true) {
|
|
1671
|
+
if (readCodememConfigFile().sync_enabled !== true) {
|
|
1577
1672
|
p.log.error("Sync is disabled. Run `codemem sync enable` first.");
|
|
1578
1673
|
process.exitCode = 1;
|
|
1579
1674
|
return;
|
|
1580
1675
|
}
|
|
1581
|
-
const configuredHost = typeof config.sync_host === "string" ? config.sync_host : "0.0.0.0";
|
|
1582
|
-
const configuredPort = typeof config.sync_port === "number" ? String(config.sync_port) : "7337";
|
|
1583
|
-
opts.host ??= configuredHost;
|
|
1584
|
-
opts.port ??= configuredPort;
|
|
1585
|
-
} else if (action === "restart") {
|
|
1586
|
-
const config = readCodememConfigFile();
|
|
1587
|
-
const configuredHost = typeof config.sync_host === "string" ? config.sync_host : "0.0.0.0";
|
|
1588
|
-
const configuredPort = typeof config.sync_port === "number" ? String(config.sync_port) : "7337";
|
|
1589
|
-
opts.host ??= configuredHost;
|
|
1590
|
-
opts.port ??= configuredPort;
|
|
1591
1676
|
}
|
|
1592
1677
|
const args = buildServeLifecycleArgs(action, opts, process.argv[1] ?? "", process.execArgv);
|
|
1593
1678
|
await new Promise((resolve, reject) => {
|