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.
@@ -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":"AAgBA,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,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;AA6SD,eAAO,MAAM,YAAY,SAuBtB,CAAC"}
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;AAqCH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0JpC,eAAO,MAAM,WAAW,SAE+B,CAAC"}
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("Verify the SQLite database is present and schema-ready").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) => {
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 dbPath = resolveDbPath(invocation.dbPath ?? void 0);
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: ${dbPath}`);
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();