@remnic/cli 9.54.4 → 9.54.5

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 +56 -41
  2. package/package.json +31 -31
package/dist/index.js CHANGED
@@ -299,13 +299,14 @@ import {
299
299
  } from "@remnic/core/reconcile/manifest.js";
300
300
 
301
301
  // src/offline-storage-io.ts
302
- import { mkdtemp, readdir, lstat, rm } from "fs/promises";
302
+ import { createDecipheriv, createHash } from "crypto";
303
303
  import fs3 from "fs";
304
+ import { lstat, mkdtemp, readdir, rm } from "fs/promises";
304
305
  import path from "path";
305
- import { createHash, createDecipheriv } from "crypto";
306
306
  import {
307
307
  OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES,
308
- StorageManager
308
+ StorageManager,
309
+ createSupportPassportPrivateFileExclusion
309
310
  } from "@remnic/core";
310
311
  import { OFFLINE_DECRYPT_STAGING_DIR_PREFIX } from "@remnic/core/offline-sync-exclude-globs";
311
312
  import {
@@ -326,6 +327,28 @@ import {
326
327
  readHeader,
327
328
  secureStoreDir
328
329
  } from "@remnic/core/secure-store";
330
+ var OFFLINE_SYNC_EXCLUSION_CONCURRENCY = 16;
331
+ function resolveOfflineDirectHydrationPath(memoryDir, relPath) {
332
+ const base = path.resolve(memoryDir);
333
+ const target = path.resolve(base, relPath);
334
+ const relative = path.relative(base, target);
335
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
336
+ throw new Error(`offline sync direct hydration path escapes memory dir: ${relPath}`);
337
+ }
338
+ return target;
339
+ }
340
+ async function filterOfflineSyncBaseFiles(memoryDir, files, excludeFile) {
341
+ const excluded = new Array(files.length);
342
+ for (let offset = 0; offset < files.length; offset += OFFLINE_SYNC_EXCLUSION_CONCURRENCY) {
343
+ await Promise.all(
344
+ files.slice(offset, offset + OFFLINE_SYNC_EXCLUSION_CONCURRENCY).map(async (file, index) => {
345
+ const filePath = resolveOfflineDirectHydrationPath(memoryDir, file.path);
346
+ excluded[offset + index] = await excludeFile({ root: memoryDir, path: file.path, filePath });
347
+ })
348
+ );
349
+ }
350
+ return files.filter((_file, index) => excluded[index] === false);
351
+ }
329
352
  async function createConfiguredOfflineStorage(memoryDir, secureStoreEncryptOnWrite = true) {
330
353
  const storage = new StorageManager(memoryDir);
331
354
  const header = await readHeader(memoryDir);
@@ -365,6 +388,7 @@ async function createOfflineStorageIo(memoryDir, configuredStorage) {
365
388
  await cleanupOrphanedOfflineDecryptStaging(memoryDir);
366
389
  const { storage, secureStoreKey } = configuredStorage ?? await createConfiguredOfflineStorage(memoryDir);
367
390
  return {
391
+ excludeFile: createSupportPassportPrivateFileExclusion(storage),
368
392
  readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
369
393
  readDeletionRevisions: () => storage.readDeletionRevisions(),
370
394
  readFileDigest: async ({ filePath }) => {
@@ -471,15 +495,9 @@ async function* readEncryptedOfflineFileChunks(options) {
471
495
  if (envelopeVersion !== ENVELOPE_VERSION) {
472
496
  throw new Error(`secure-store envelope has unsupported version ${envelopeVersion}: ${options.filePath}`);
473
497
  }
474
- const salt = envelopeHeader.subarray(
475
- ENVELOPE_LAYOUT.salt,
476
- ENVELOPE_LAYOUT.salt + ENVELOPE_SALT_LENGTH
477
- );
498
+ const salt = envelopeHeader.subarray(ENVELOPE_LAYOUT.salt, ENVELOPE_LAYOUT.salt + ENVELOPE_SALT_LENGTH);
478
499
  const iv = envelopeHeader.subarray(ENVELOPE_LAYOUT.iv, ENVELOPE_LAYOUT.iv + IV_LENGTH);
479
- const authTag = envelopeHeader.subarray(
480
- ENVELOPE_LAYOUT.authTag,
481
- ENVELOPE_LAYOUT.authTag + AUTH_TAG_LENGTH
482
- );
500
+ const authTag = envelopeHeader.subarray(ENVELOPE_LAYOUT.authTag, ENVELOPE_LAYOUT.authTag + AUTH_TAG_LENGTH);
483
501
  const aadCandidates = offlineFileAadCandidates(options.filePath, options.memoryDir);
484
502
  let lastError;
485
503
  for (const aad of aadCandidates) {
@@ -498,9 +516,7 @@ async function* readEncryptedOfflineFileChunks(options) {
498
516
  highWaterMark: options.chunkSize
499
517
  });
500
518
  for await (const encryptedChunk of stream) {
501
- const plain = decipher.update(
502
- Buffer.isBuffer(encryptedChunk) ? encryptedChunk : Buffer.from(encryptedChunk)
503
- );
519
+ const plain = decipher.update(Buffer.isBuffer(encryptedChunk) ? encryptedChunk : Buffer.from(encryptedChunk));
504
520
  if (plain.length > 0 && !output.write(plain)) {
505
521
  await new Promise((resolve2, reject) => {
506
522
  output.once("drain", resolve2);
@@ -1194,10 +1210,14 @@ async function computeConvergePlan(options = {}) {
1194
1210
  for (const rootInfo of roots) {
1195
1211
  const ns = rootInfo.namespace;
1196
1212
  namespacesToPlan.add(ns);
1213
+ const io = await createOfflineStorageIo(rootInfo.rootDir);
1197
1214
  const snapshot = await buildOfflineSyncSnapshotFromBase({
1198
1215
  root: rootInfo.rootDir,
1199
1216
  sourceId: "local",
1200
- includeContent: false
1217
+ includeContent: false,
1218
+ readFile: io.readFile,
1219
+ readFileDigest: io.readFileDigest,
1220
+ excludeFile: io.excludeFile
1201
1221
  });
1202
1222
  const files = snapshot.files.filter((record2) => !isInternalRemnicStatePath3(record2.path)).map((record2) => ({
1203
1223
  path: record2.path,
@@ -1207,7 +1227,6 @@ async function computeConvergePlan(options = {}) {
1207
1227
  }));
1208
1228
  localMap.set(ns, files);
1209
1229
  const evidence = await readLocalTombstoneEvidence(rootInfo.rootDir);
1210
- const io = await createOfflineStorageIo(rootInfo.rootDir);
1211
1230
  let manifestReadFailed = false;
1212
1231
  const manifest = await buildReconcileManifest({
1213
1232
  files,
@@ -11747,15 +11766,6 @@ function offlineDirectPushFiles(options) {
11747
11766
  return current.sha256 !== base.get(current.path)?.sha256;
11748
11767
  }).sort((left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path));
11749
11768
  }
11750
- function resolveOfflineDirectHydrationPath(memoryDir, relPath) {
11751
- const base = path17.resolve(memoryDir);
11752
- const target = path17.resolve(base, relPath);
11753
- const relative = path17.relative(base, target);
11754
- if (relative === "" || relative === ".." || relative.startsWith(`..${path17.sep}`) || path17.isAbsolute(relative)) {
11755
- throw new Error(`offline sync direct hydration path escapes memory dir: ${relPath}`);
11756
- }
11757
- return target;
11758
- }
11759
11769
  var OFFLINE_SYNC_FILE_CONTENT_UPLOAD_CHUNK_BYTES = OFFLINE_SYNC_FILE_CONTENT_TRANSFER_CHUNK_BYTES4;
11760
11770
  async function pushOfflineFileContent(args) {
11761
11771
  if (args.readFileChunks) {
@@ -12355,6 +12365,7 @@ async function runOfflineSyncOnce(options) {
12355
12365
  options.secureStoreEncryptOnWrite
12356
12366
  );
12357
12367
  const storageIo = await createOfflineStorageIo(options.memoryDir, offlineStorage);
12368
+ const syncBaseFiles = await filterOfflineSyncBaseFiles(options.memoryDir, baseFiles, storageIo.excludeFile);
12358
12369
  const localSourceId = localOfflineSourceId(options.memoryDir);
12359
12370
  await drainOfflineSyncImpressions(options.memoryDir, options);
12360
12371
  await drainPendingLifecycleForOfflineSync(
@@ -12369,21 +12380,22 @@ async function runOfflineSyncOnce(options) {
12369
12380
  const currentSnapshotForPush = await buildOfflineSyncSnapshotFromBase2({
12370
12381
  root: options.memoryDir,
12371
12382
  sourceId: localSourceId,
12372
- baseFiles,
12383
+ baseFiles: syncBaseFiles,
12373
12384
  baseCapturedAt,
12374
12385
  includeContent: false,
12375
12386
  includeTranscripts: options.includeTranscripts,
12376
12387
  readFile: storageIo.readFile,
12377
12388
  readFileDigest: storageIo.readFileDigest,
12389
+ excludeFile: storageIo.excludeFile,
12378
12390
  userExcludeRegexps: options.userExcludeRegexps
12379
12391
  });
12380
12392
  const pendingSummary = summarizeOfflineSyncPendingFiles({
12381
- baseFiles,
12393
+ baseFiles: syncBaseFiles,
12382
12394
  currentFiles: currentSnapshotForPush.files,
12383
12395
  includeTranscripts: options.includeTranscripts,
12384
12396
  userExcludeRegexps: options.userExcludeRegexps
12385
12397
  });
12386
- const baseByPath = offlineFileStateMap(baseFiles);
12398
+ const baseByPath = offlineFileStateMap(syncBaseFiles);
12387
12399
  let directPushAppliedUpserts = 0;
12388
12400
  let directPushSkipped = 0;
12389
12401
  let directPushNamespace;
@@ -12393,7 +12405,7 @@ async function runOfflineSyncOnce(options) {
12393
12405
  const directPushFailures = [];
12394
12406
  for (const file of offlineDirectPushFiles({
12395
12407
  currentFiles: currentSnapshotForPush.files,
12396
- baseFiles
12408
+ baseFiles: syncBaseFiles
12397
12409
  })) {
12398
12410
  if (options.skipLargeFilePaths?.has(file.path)) {
12399
12411
  continue;
@@ -12471,7 +12483,7 @@ async function runOfflineSyncOnce(options) {
12471
12483
  const resolvedNamespace2 = partial?.resolvedNamespace ?? resolvedOfflineSnapshotNamespace({ namespace: pushed?.namespace ?? "" }, syncNamespace);
12472
12484
  const stateWritePaths2 = stateWritePathsFor(resolvedNamespace2);
12473
12485
  const nextBaseFiles = advanceOfflineBaseFilesForSuccessfulPush({
12474
- baseFiles,
12486
+ baseFiles: syncBaseFiles,
12475
12487
  currentFiles: currentSnapshotForPush.files,
12476
12488
  directPushedPaths: [...directPushedPaths],
12477
12489
  hydratedFiles: partial?.hydratedFiles,
@@ -12518,12 +12530,13 @@ async function runOfflineSyncOnce(options) {
12518
12530
  let currentSnapshotForChangeset = directPushedPaths.size > 0 ? await buildOfflineSyncSnapshotFromBase2({
12519
12531
  root: options.memoryDir,
12520
12532
  sourceId: localSourceId,
12521
- baseFiles,
12533
+ baseFiles: syncBaseFiles,
12522
12534
  baseCapturedAt,
12523
12535
  includeContent: false,
12524
12536
  includeTranscripts: options.includeTranscripts,
12525
12537
  readFile: storageIo.readFile,
12526
12538
  readFileDigest: storageIo.readFileDigest,
12539
+ excludeFile: storageIo.excludeFile,
12527
12540
  userExcludeRegexps: options.userExcludeRegexps
12528
12541
  }) : currentSnapshotForPush;
12529
12542
  let changesetRetryCount = 0;
@@ -12533,7 +12546,7 @@ async function runOfflineSyncOnce(options) {
12533
12546
  root: options.memoryDir,
12534
12547
  sourceId: localSourceId,
12535
12548
  currentFiles: currentSnapshotForChangeset.files,
12536
- baseFiles,
12549
+ baseFiles: syncBaseFiles,
12537
12550
  // 3-strikes skipped large files must be excluded here too — the
12538
12551
  // direct-push loop skips them, but without this line the changeset
12539
12552
  // path would still try to upsert them inline (Cursor review, PR
@@ -12571,12 +12584,13 @@ async function runOfflineSyncOnce(options) {
12571
12584
  currentSnapshotForChangeset = await buildOfflineSyncSnapshotFromBase2({
12572
12585
  root: options.memoryDir,
12573
12586
  sourceId: localSourceId,
12574
- baseFiles,
12587
+ baseFiles: syncBaseFiles,
12575
12588
  baseCapturedAt,
12576
12589
  includeContent: false,
12577
12590
  includeTranscripts: options.includeTranscripts,
12578
12591
  readFile: storageIo.readFile,
12579
12592
  readFileDigest: storageIo.readFileDigest,
12593
+ excludeFile: storageIo.excludeFile,
12580
12594
  userExcludeRegexps: options.userExcludeRegexps
12581
12595
  });
12582
12596
  }
@@ -12614,7 +12628,7 @@ async function runOfflineSyncOnce(options) {
12614
12628
  namespace: syncNamespace,
12615
12629
  includeTranscripts: options.includeTranscripts,
12616
12630
  includeContent: false,
12617
- baseFiles,
12631
+ baseFiles: syncBaseFiles,
12618
12632
  baseCapturedAt
12619
12633
  });
12620
12634
  } catch (error) {
@@ -12626,12 +12640,13 @@ async function runOfflineSyncOnce(options) {
12626
12640
  currentSnapshot = await buildOfflineSyncSnapshotFromBase2({
12627
12641
  root: options.memoryDir,
12628
12642
  sourceId: localSourceId,
12629
- baseFiles,
12643
+ baseFiles: syncBaseFiles,
12630
12644
  baseCapturedAt,
12631
12645
  includeContent: false,
12632
12646
  includeTranscripts: options.includeTranscripts,
12633
12647
  readFile: storageIo.readFile,
12634
- readFileDigest: storageIo.readFileDigest
12648
+ readFileDigest: storageIo.readFileDigest,
12649
+ excludeFile: storageIo.excludeFile
12635
12650
  });
12636
12651
  } catch (error) {
12637
12652
  if (pushed) return writePartialPushState(error);
@@ -12649,7 +12664,7 @@ async function runOfflineSyncOnce(options) {
12649
12664
  namespace: syncNamespace,
12650
12665
  includeTranscripts: options.includeTranscripts,
12651
12666
  snapshot: remoteSnapshotMetadata,
12652
- baseFiles,
12667
+ baseFiles: syncBaseFiles,
12653
12668
  currentFiles: currentSnapshot.files,
12654
12669
  memoryDir: options.memoryDir,
12655
12670
  readFile: storageIo.readFile,
@@ -12706,7 +12721,7 @@ async function runOfflineSyncOnce(options) {
12706
12721
  namespace: syncNamespace,
12707
12722
  includeTranscripts: options.includeTranscripts,
12708
12723
  snapshot: remoteSnapshotMetadata,
12709
- baseFiles,
12724
+ baseFiles: syncBaseFiles,
12710
12725
  currentFiles: applyCurrentSnapshot.files,
12711
12726
  deferredPaths: [...remoteDeferredPaths],
12712
12727
  missingContentDeferredPaths: remoteDeferredPaths
@@ -12724,7 +12739,7 @@ async function runOfflineSyncOnce(options) {
12724
12739
  pull = await applyOfflineSyncSnapshot({
12725
12740
  root: options.memoryDir,
12726
12741
  snapshot: remoteSnapshot,
12727
- baseFiles,
12742
+ baseFiles: syncBaseFiles,
12728
12743
  currentFiles: latestApplySnapshot.files,
12729
12744
  deferredPaths: [...remoteDeferredPaths],
12730
12745
  allowMissingConflictContent: true,
@@ -12752,7 +12767,7 @@ async function runOfflineSyncOnce(options) {
12752
12767
  namespace: syncNamespace,
12753
12768
  includeTranscripts: options.includeTranscripts,
12754
12769
  snapshot: remoteSnapshotMetadata,
12755
- baseFiles,
12770
+ baseFiles: syncBaseFiles,
12756
12771
  currentFiles: applyCurrentSnapshot.files,
12757
12772
  deferredPaths: [...remoteDeferredPaths],
12758
12773
  missingContentDeferredPaths: remoteDeferredPaths
@@ -12771,7 +12786,7 @@ async function runOfflineSyncOnce(options) {
12771
12786
  pull = await applyOfflineSyncSnapshot({
12772
12787
  root: options.memoryDir,
12773
12788
  snapshot: retrySnapshot,
12774
- baseFiles,
12789
+ baseFiles: syncBaseFiles,
12775
12790
  currentFiles: latestRetryApplySnapshot.files,
12776
12791
  deferredPaths: [...remoteDeferredPaths],
12777
12792
  allowMissingConflictContent: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/cli",
3
- "version": "9.54.4",
3
+ "version": "9.54.5",
4
4
  "description": "CLI for Remnic memory — init, query, doctor, daemon management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,25 +26,25 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "yaml": "^2.4.2",
29
- "@remnic/plugin-pi": "^9.54.4",
30
- "@remnic/core": "^9.54.4",
31
- "@remnic/server": "^9.54.4"
29
+ "@remnic/plugin-pi": "^9.54.5",
30
+ "@remnic/server": "^9.54.5",
31
+ "@remnic/core": "^9.54.5"
32
32
  },
33
33
  "peerDependencies": {
34
- "@remnic/bench": "^9.54.4",
35
- "@remnic/plugin-openclaw": "^9.54.4",
36
- "@remnic/export-weclone": "^9.54.4",
37
- "@remnic/import-weclone": "^9.54.4",
38
- "@remnic/import-chatgpt": "^9.54.4",
39
- "@remnic/import-claude": "^9.54.4",
40
- "@remnic/import-gemini": "^9.54.4",
41
- "@remnic/import-lossless-claw": "^9.54.4",
42
- "@remnic/import-mem0": "^9.54.4",
43
- "@remnic/import-supermemory": "^9.54.4",
44
- "@remnic/connector-limitless": "^9.54.4",
45
- "@remnic/connector-bee": "^9.54.4",
46
- "@remnic/connector-omi": "^9.54.4",
47
- "@remnic/capture-audio": "^9.54.4"
34
+ "@remnic/bench": "^9.54.5",
35
+ "@remnic/plugin-openclaw": "^9.54.5",
36
+ "@remnic/export-weclone": "^9.54.5",
37
+ "@remnic/import-weclone": "^9.54.5",
38
+ "@remnic/import-chatgpt": "^9.54.5",
39
+ "@remnic/import-claude": "^9.54.5",
40
+ "@remnic/import-gemini": "^9.54.5",
41
+ "@remnic/import-lossless-claw": "^9.54.5",
42
+ "@remnic/import-mem0": "^9.54.5",
43
+ "@remnic/import-supermemory": "^9.54.5",
44
+ "@remnic/connector-limitless": "^9.54.5",
45
+ "@remnic/connector-bee": "^9.54.5",
46
+ "@remnic/connector-omi": "^9.54.5",
47
+ "@remnic/capture-audio": "^9.54.5"
48
48
  },
49
49
  "peerDependenciesMeta": {
50
50
  "@remnic/bench": {
@@ -93,19 +93,19 @@
93
93
  "devDependencies": {
94
94
  "tsup": "^8.5.1",
95
95
  "typescript": "^5.9.3",
96
- "@remnic/bench": "9.54.4",
97
- "@remnic/plugin-openclaw": "9.54.4",
98
- "@remnic/export-weclone": "9.54.4",
99
- "@remnic/import-weclone": "9.54.4",
100
- "@remnic/import-chatgpt": "9.54.4",
101
- "@remnic/import-lossless-claw": "9.54.4",
102
- "@remnic/import-gemini": "9.54.4",
103
- "@remnic/import-mem0": "9.54.4",
104
- "@remnic/import-supermemory": "9.54.4",
105
- "@remnic/connector-limitless": "9.54.4",
106
- "@remnic/import-claude": "9.54.4",
107
- "@remnic/connector-bee": "9.54.4",
108
- "@remnic/connector-omi": "9.54.4"
96
+ "@remnic/bench": "9.54.5",
97
+ "@remnic/plugin-openclaw": "9.54.5",
98
+ "@remnic/export-weclone": "9.54.5",
99
+ "@remnic/import-weclone": "9.54.5",
100
+ "@remnic/import-chatgpt": "9.54.5",
101
+ "@remnic/import-gemini": "9.54.5",
102
+ "@remnic/import-lossless-claw": "9.54.5",
103
+ "@remnic/import-mem0": "9.54.5",
104
+ "@remnic/import-claude": "9.54.5",
105
+ "@remnic/import-supermemory": "9.54.5",
106
+ "@remnic/connector-limitless": "9.54.5",
107
+ "@remnic/connector-bee": "9.54.5",
108
+ "@remnic/connector-omi": "9.54.5"
109
109
  },
110
110
  "license": "MIT",
111
111
  "repository": {