codemem 0.22.2 → 0.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/index.js +139 -5
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/commands/serve.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare function extractViewerPid(payload: unknown): number | null;
|
|
|
4
4
|
export declare function isLocalHost(host: string): boolean;
|
|
5
5
|
export declare function isLoopbackOnlyHost(host: string): boolean;
|
|
6
6
|
export declare function isLikelyViewerCommand(command: string): boolean;
|
|
7
|
+
export declare function prepareViewerDatabase(dbPath?: string | null): string;
|
|
7
8
|
export declare function pickViewerPidCandidate(statsPid: number | null, listenerPid: number | null): number | null;
|
|
8
9
|
export declare function buildForegroundRunnerArgs(scriptPath: string, invocation: ResolvedServeInvocation, execArgv?: string[]): string[];
|
|
9
10
|
export declare function isSqliteVecLoadFailure(error: unknown): boolean;
|
|
@@ -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":"AAiBA,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,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQxD;AASD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAEpE;AAED,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,GAAG,IAAI,CAGf;AAqLD,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;AA8SD,eAAO,MAAM,YAAY,SAuBtB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AA2CH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0JpC,eAAO,MAAM,WAAW,SAE+B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_COORDINATOR_DB_PATH, MemoryStore, ObserverClient, RawEventSweeper, SyncRetentionRunner, VERSION, backfillTagsText, backfillVectors, buildRawEventEnvelopeFromHook, connect, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorImportInviteAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, ensureDeviceIdentity, exportMemories, fingerprintPublicKey, getRawEventStatus, importMemories, initDatabase, isEmbeddingDisabled, loadPublicKey, loadSqliteVec, planReplicationOpsAgePrune, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCoordinatorSyncConfig, 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, SyncRetentionRunner, VERSION, applyBootstrapSnapshot, backfillTagsText, backfillVectors, buildAuthHeaders, buildBaseUrl, buildRawEventEnvelopeFromHook, connect, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorImportInviteAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, ensureDeviceIdentity, exportMemories, fetchAllSnapshotPages, fingerprintPublicKey, getRawEventStatus, hasUnsyncedSharedMemoryChanges, importMemories, initDatabase, isEmbeddingDisabled, loadPublicKey, loadSqliteVec, planReplicationOpsAgePrune, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCoordinatorSyncConfig, readImportPayload, requestJson, 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
5
|
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -210,7 +210,7 @@ function estimateReplicationOpsBytes(db) {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
var dbCommand = new Command("db").configureHelp(helpStyle).description("Database maintenance");
|
|
213
|
-
dbCommand.addCommand(new Command("init").configureHelp(helpStyle).description("
|
|
213
|
+
dbCommand.addCommand(new Command("init").configureHelp(helpStyle).description("Create or verify the SQLite database and schema").option("--db <path>", "database path (default: $CODEMEM_DB or ~/.codemem/mem.sqlite)").option("--db-path <path>", "database path (default: $CODEMEM_DB or ~/.codemem/mem.sqlite)").action((opts) => {
|
|
214
214
|
const result = initDatabase(opts.db ?? opts.dbPath);
|
|
215
215
|
p.intro("codemem db init");
|
|
216
216
|
p.log.success(`Database ready: ${result.path}`);
|
|
@@ -966,6 +966,9 @@ function isLikelyViewerCommand(command) {
|
|
|
966
966
|
if (!/\bserve\s+start\b/.test(lowered)) return false;
|
|
967
967
|
return lowered.includes("codemem") || lowered.includes("packages/cli/dist/index.js") || lowered.includes("/cli/dist/index.js");
|
|
968
968
|
}
|
|
969
|
+
function prepareViewerDatabase(dbPath) {
|
|
970
|
+
return initDatabase(dbPath ?? void 0).path;
|
|
971
|
+
}
|
|
969
972
|
function pickViewerPidCandidate(statsPid, listenerPid) {
|
|
970
973
|
if (statsPid && listenerPid && statsPid !== listenerPid) return null;
|
|
971
974
|
return statsPid ?? listenerPid ?? null;
|
|
@@ -1208,6 +1211,7 @@ async function startForegroundViewer(invocation) {
|
|
|
1208
1211
|
process.exitCode = 1;
|
|
1209
1212
|
return;
|
|
1210
1213
|
}
|
|
1214
|
+
const preparedDb = prepareViewerDatabase(invocation.dbPath);
|
|
1211
1215
|
const observer = new ObserverClient();
|
|
1212
1216
|
let store;
|
|
1213
1217
|
try {
|
|
@@ -1242,8 +1246,7 @@ async function startForegroundViewer(invocation) {
|
|
|
1242
1246
|
getSyncRuntimeStatus: () => syncRuntimeStatus
|
|
1243
1247
|
};
|
|
1244
1248
|
const app = createApp(appOpts);
|
|
1245
|
-
const
|
|
1246
|
-
const pidPath = pidFilePath(dbPath);
|
|
1249
|
+
const pidPath = pidFilePath(resolveDbPath(invocation.dbPath ?? void 0));
|
|
1247
1250
|
let syncServer = null;
|
|
1248
1251
|
let syncListenerReady = false;
|
|
1249
1252
|
if (syncEnabled) {
|
|
@@ -1272,7 +1275,7 @@ async function startForegroundViewer(invocation) {
|
|
|
1272
1275
|
}), "utf-8");
|
|
1273
1276
|
p.intro("codemem viewer");
|
|
1274
1277
|
p.log.success(`Listening on http://${info.address}:${info.port}`);
|
|
1275
|
-
p.log.info(`Database: ${
|
|
1278
|
+
p.log.info(`Database: ${preparedDb}`);
|
|
1276
1279
|
p.log.step("Raw event sweeper started");
|
|
1277
1280
|
if (syncConfig.syncRetentionEnabled) {
|
|
1278
1281
|
retentionRunner.start();
|
|
@@ -2242,6 +2245,137 @@ peersCommand.addCommand(new Command("remove").configureHelp(helpStyle).descripti
|
|
|
2242
2245
|
}
|
|
2243
2246
|
}));
|
|
2244
2247
|
syncCommand.addCommand(peersCommand);
|
|
2248
|
+
syncCommand.addCommand(new Command("bootstrap").configureHelp(helpStyle).description("Fast-bootstrap memories from a peer (full snapshot transfer)").requiredOption("--peer <device-id>", "peer device ID to bootstrap from").option("--page-size <n>", "items per snapshot page (default: 2000)", "2000").option("--db <path>", "database path").option("--db-path <path>", "database path").option("--keys-dir <path>", "keys directory").option("--force", "skip dirty-local-state safety check").option("--json", "output as JSON").action(async (opts) => {
|
|
2249
|
+
const store = new MemoryStore(resolveDbPath(opts.db ?? opts.dbPath));
|
|
2250
|
+
try {
|
|
2251
|
+
const peerDeviceId = opts.peer.trim();
|
|
2252
|
+
const pageSize = Math.max(1, Number.parseInt(opts.pageSize, 10) || 2e3);
|
|
2253
|
+
const keysDir = opts.keysDir ?? void 0;
|
|
2254
|
+
const peer = drizzle(store.db, { schema }).select().from(schema.syncPeers).where(eq(schema.syncPeers.peer_device_id, peerDeviceId)).get();
|
|
2255
|
+
if (!peer) {
|
|
2256
|
+
if (opts.json) console.log(JSON.stringify({
|
|
2257
|
+
ok: false,
|
|
2258
|
+
error: "peer not found"
|
|
2259
|
+
}));
|
|
2260
|
+
else p.log.error(`Peer ${peerDeviceId} not found in sync_peers.`);
|
|
2261
|
+
process.exitCode = 1;
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
if (!peer.pinned_fingerprint) {
|
|
2265
|
+
if (opts.json) console.log(JSON.stringify({
|
|
2266
|
+
ok: false,
|
|
2267
|
+
error: "peer not pinned"
|
|
2268
|
+
}));
|
|
2269
|
+
else p.log.error(`Peer ${peerDeviceId} has no pinned fingerprint. Accept it first.`);
|
|
2270
|
+
process.exitCode = 1;
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
if (!opts.force) {
|
|
2274
|
+
const dirty = hasUnsyncedSharedMemoryChanges(store.db);
|
|
2275
|
+
if (dirty.dirty) {
|
|
2276
|
+
if (opts.json) console.log(JSON.stringify({
|
|
2277
|
+
ok: false,
|
|
2278
|
+
error: "local_unsynced_changes",
|
|
2279
|
+
count: dirty.count
|
|
2280
|
+
}));
|
|
2281
|
+
else p.log.error(`${dirty.count} unsynced shared memory change(s) would be lost. Use --force to override.`);
|
|
2282
|
+
process.exitCode = 1;
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
const [deviceId] = ensureDeviceIdentity(store.db, { keysDir });
|
|
2287
|
+
const addresses = JSON.parse(String(peer.addresses_json ?? "[]"));
|
|
2288
|
+
if (!addresses.length) {
|
|
2289
|
+
if (opts.json) console.log(JSON.stringify({
|
|
2290
|
+
ok: false,
|
|
2291
|
+
error: "no peer addresses"
|
|
2292
|
+
}));
|
|
2293
|
+
else p.log.error("Peer has no known addresses. Run a sync first or add addresses.");
|
|
2294
|
+
process.exitCode = 1;
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
let boundary = null;
|
|
2298
|
+
let baseUrl = "";
|
|
2299
|
+
let lastAddressError = "";
|
|
2300
|
+
for (const address of addresses) {
|
|
2301
|
+
const candidate = buildBaseUrl(address);
|
|
2302
|
+
if (!candidate) continue;
|
|
2303
|
+
const statusUrl = `${candidate}/v1/status`;
|
|
2304
|
+
const headers = buildAuthHeaders({
|
|
2305
|
+
deviceId,
|
|
2306
|
+
method: "GET",
|
|
2307
|
+
url: statusUrl,
|
|
2308
|
+
bodyBytes: Buffer.alloc(0),
|
|
2309
|
+
keysDir
|
|
2310
|
+
});
|
|
2311
|
+
try {
|
|
2312
|
+
const [code, payload] = await requestJson("GET", statusUrl, { headers });
|
|
2313
|
+
if (code !== 200 || !payload) {
|
|
2314
|
+
lastAddressError = `${candidate}: status ${code}`;
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
if (payload.fingerprint !== peer.pinned_fingerprint) {
|
|
2318
|
+
lastAddressError = `${candidate}: fingerprint mismatch`;
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
const reset = payload.sync_reset;
|
|
2322
|
+
if (reset && typeof reset.generation === "number" && typeof reset.snapshot_id === "string") {
|
|
2323
|
+
boundary = {
|
|
2324
|
+
generation: reset.generation,
|
|
2325
|
+
snapshot_id: reset.snapshot_id,
|
|
2326
|
+
baseline_cursor: typeof reset.baseline_cursor === "string" ? reset.baseline_cursor : null
|
|
2327
|
+
};
|
|
2328
|
+
baseUrl = candidate;
|
|
2329
|
+
break;
|
|
2330
|
+
}
|
|
2331
|
+
lastAddressError = `${candidate}: missing sync_reset boundary`;
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
lastAddressError = `${candidate}: ${err instanceof Error ? err.message : String(err)}`;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (!boundary || !baseUrl) {
|
|
2337
|
+
const detail = lastAddressError ? `peer unreachable or missing reset boundary (${lastAddressError})` : "peer unreachable or missing reset boundary";
|
|
2338
|
+
if (opts.json) console.log(JSON.stringify({
|
|
2339
|
+
ok: false,
|
|
2340
|
+
error: detail
|
|
2341
|
+
}));
|
|
2342
|
+
else p.log.error(detail);
|
|
2343
|
+
process.exitCode = 1;
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
if (!opts.json) {
|
|
2347
|
+
p.intro("codemem sync bootstrap");
|
|
2348
|
+
p.log.step(`Bootstrapping from ${peer.name || peerDeviceId}...`);
|
|
2349
|
+
}
|
|
2350
|
+
const resetInfo = {
|
|
2351
|
+
generation: boundary.generation,
|
|
2352
|
+
snapshot_id: boundary.snapshot_id,
|
|
2353
|
+
baseline_cursor: boundary.baseline_cursor,
|
|
2354
|
+
retained_floor_cursor: null,
|
|
2355
|
+
reset_required: true,
|
|
2356
|
+
reason: "initial_bootstrap"
|
|
2357
|
+
};
|
|
2358
|
+
const { items } = await fetchAllSnapshotPages(baseUrl, resetInfo, deviceId, {
|
|
2359
|
+
keysDir,
|
|
2360
|
+
pageSize
|
|
2361
|
+
});
|
|
2362
|
+
const result = applyBootstrapSnapshot(store.db, peerDeviceId, items, resetInfo);
|
|
2363
|
+
if (opts.json) console.log(JSON.stringify({
|
|
2364
|
+
ok: result.ok,
|
|
2365
|
+
applied: result.applied,
|
|
2366
|
+
deleted: result.deleted,
|
|
2367
|
+
error: result.error ?? null
|
|
2368
|
+
}));
|
|
2369
|
+
else {
|
|
2370
|
+
if (result.ok) p.log.success(`Applied ${result.applied} memories (removed ${result.deleted} stale).`);
|
|
2371
|
+
else p.log.error(result.error || "Bootstrap apply failed.");
|
|
2372
|
+
p.outro(result.ok ? "Bootstrap complete" : "Bootstrap failed");
|
|
2373
|
+
}
|
|
2374
|
+
if (!result.ok) process.exitCode = 1;
|
|
2375
|
+
} finally {
|
|
2376
|
+
store.close();
|
|
2377
|
+
}
|
|
2378
|
+
}));
|
|
2245
2379
|
syncCommand.addCommand(new Command("connect").configureHelp(helpStyle).description("Configure coordinator URL for cloud sync").argument("<url>", "coordinator URL (e.g. https://coordinator.example.com)").option("--group <group>", "sync group ID").action((url, opts) => {
|
|
2246
2380
|
const config = readCodememConfigFile();
|
|
2247
2381
|
config.sync_coordinator_url = url.trim();
|