@remnic/cli 1.0.25 → 9.3.517

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 +364 -136
  2. package/package.json +22 -22
package/dist/index.js CHANGED
@@ -851,7 +851,12 @@ function validateBenchFlags(action, args) {
851
851
  if (!arg.startsWith("-")) {
852
852
  continue;
853
853
  }
854
- if (allowed.legacyEqualsPrefixes?.some((prefix) => arg.startsWith(prefix))) {
854
+ const legacyEqualsPrefix = allowed.legacyEqualsPrefixes?.find((prefix) => arg.startsWith(prefix));
855
+ if (legacyEqualsPrefix) {
856
+ const value = arg.slice(legacyEqualsPrefix.length);
857
+ if (value.trim().length === 0) {
858
+ throw new Error(`ERROR: ${legacyEqualsPrefix.slice(0, -1)} requires a value.`);
859
+ }
855
860
  continue;
856
861
  }
857
862
  if (isBenchValueFlag(arg)) {
@@ -1974,6 +1979,66 @@ function isCandidateReady(candidate, existsSync3) {
1974
1979
  function resolveServerBin(options = {}) {
1975
1980
  return resolveServerBinDetails(options).path;
1976
1981
  }
1982
+ function readVerifiedDaemonPid(options) {
1983
+ const readFileSync3 = options.readFileSync ?? fs4.readFileSync;
1984
+ const unlinkSync = options.unlinkSync ?? fs4.unlinkSync;
1985
+ const processKill = options.processKill ?? process.kill;
1986
+ const execFileSync3 = options.execFileSync ?? ((command, args, execOptions) => childProcess.execFileSync(command, args, execOptions));
1987
+ for (const file of options.pidFiles) {
1988
+ let pid;
1989
+ try {
1990
+ pid = parseDaemonPid(readFileSync3(file, "utf8"));
1991
+ } catch {
1992
+ continue;
1993
+ }
1994
+ if (pid === void 0) {
1995
+ removePidFileBestEffort(file, unlinkSync);
1996
+ continue;
1997
+ }
1998
+ try {
1999
+ processKill(pid, 0);
2000
+ } catch {
2001
+ removePidFileBestEffort(file, unlinkSync);
2002
+ continue;
2003
+ }
2004
+ const command = readProcessCommand(pid, execFileSync3);
2005
+ if (command === void 0) {
2006
+ return pid;
2007
+ }
2008
+ if (doesProcessCommandLookLikeRemnicDaemon(command, options.expectedServerBin)) {
2009
+ return pid;
2010
+ }
2011
+ removePidFileBestEffort(file, unlinkSync);
2012
+ }
2013
+ return void 0;
2014
+ }
2015
+ function doesProcessCommandLookLikeRemnicDaemon(command, expectedServerBin) {
2016
+ const normalizedCommand = command.trim();
2017
+ const normalizedExpected = path8.resolve(expandTilde(expectedServerBin));
2018
+ return normalizedCommand.includes(normalizedExpected) || /(?:^|\s|[/\\])(?:remnic-server|engram-server)(?:\.js)?(?:\s|$)/.test(normalizedCommand) || /@remnic[/\\]server[/\\]/.test(normalizedCommand) || /packages[/\\]remnic-server[/\\](?:bin[/\\]remnic-server\.js|dist[/\\]index\.js|src[/\\]index\.ts)/.test(normalizedCommand);
2019
+ }
2020
+ function parseDaemonPid(raw) {
2021
+ const trimmed = raw.trim();
2022
+ if (!/^\d+$/.test(trimmed)) return void 0;
2023
+ const pid = Number(trimmed);
2024
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
2025
+ }
2026
+ function readProcessCommand(pid, execFileSync3) {
2027
+ try {
2028
+ return execFileSync3("ps", ["-p", String(pid), "-o", "command="], {
2029
+ encoding: "utf8",
2030
+ stdio: "pipe"
2031
+ });
2032
+ } catch {
2033
+ return void 0;
2034
+ }
2035
+ }
2036
+ function removePidFileBestEffort(file, unlinkSync) {
2037
+ try {
2038
+ unlinkSync(file);
2039
+ } catch {
2040
+ }
2041
+ }
1977
2042
  function inspectLaunchdPlist(plistPath, options = {}) {
1978
2043
  const existsSync3 = options.existsSync ?? fs4.existsSync;
1979
2044
  const readFileSync3 = options.readFileSync ?? fs4.readFileSync;
@@ -2432,7 +2497,8 @@ Usage:
2432
2497
 
2433
2498
  Required:
2434
2499
  --adapter <name> One of: ${SUPPORTED_IMPORTERS.join(" | ")}
2435
- --file <path> Path to the source export (JSON or ZIP). May be
2500
+ --file <path> Path to a text/JSON source export. ZIP archives
2501
+ are not accepted by this single-file path yet. May be
2436
2502
  omitted for API-only adapters (mem0).
2437
2503
 
2438
2504
  Options:
@@ -2523,6 +2589,11 @@ function rejectLeftoverImportArgs(args, command) {
2523
2589
  }
2524
2590
  }
2525
2591
  async function runImportCommand(args, io) {
2592
+ if (args.file && isZipFilePath(args.file)) {
2593
+ throw new Error(
2594
+ `ZIP imports are not supported by --file yet: '${args.file}'. Extract the archive first or use --all-from-bundle for supported bundle layouts.`
2595
+ );
2596
+ }
2526
2597
  const adapter = await io.loadAdapter(args.adapter);
2527
2598
  let input;
2528
2599
  if (args.file) {
@@ -2575,6 +2646,16 @@ async function runImportCommand(args, io) {
2575
2646
  }
2576
2647
  return result;
2577
2648
  }
2649
+ function isZipFilePath(filePath) {
2650
+ return pathExtension(filePath) === ".zip";
2651
+ }
2652
+ function pathExtension(filePath) {
2653
+ const normalized = filePath.trim().toLowerCase();
2654
+ const lastSlash = Math.max(normalized.lastIndexOf("/"), normalized.lastIndexOf("\\"));
2655
+ const base = normalized.slice(lastSlash + 1);
2656
+ const dot = base.lastIndexOf(".");
2657
+ return dot >= 0 ? base.slice(dot) : "";
2658
+ }
2578
2659
  function parseImportBundleArgs(rest) {
2579
2660
  const args = [...rest];
2580
2661
  if (!args.includes("--all-from-bundle")) return void 0;
@@ -3837,7 +3918,8 @@ var __benchDatasetTestHooks = {
3837
3918
  seed,
3838
3919
  benchmarkOptions
3839
3920
  );
3840
- }
3921
+ },
3922
+ printBenchStatusLineForTest: printBenchStatusLine
3841
3923
  };
3842
3924
  function printBenchPackageSummary(result, outputPath, outputLabel = "Results saved") {
3843
3925
  console.log(`Benchmark: ${result.meta.benchmark}`);
@@ -3852,6 +3934,13 @@ function printBenchPackageSummary(result, outputPath, outputLabel = "Results sav
3852
3934
  }
3853
3935
  console.log(`${outputLabel}: ${outputPath}`);
3854
3936
  }
3937
+ function printBenchStatusLine(jsonMode, message) {
3938
+ if (jsonMode) {
3939
+ console.error(message);
3940
+ } else {
3941
+ console.log(message);
3942
+ }
3943
+ }
3855
3944
  function printStoredBenchResultSummary(result, summary) {
3856
3945
  printBenchPackageSummary(result, summary.path, "Stored result");
3857
3946
  console.log(`Run id: ${summary.id}`);
@@ -4672,7 +4761,8 @@ async function runBenchViaPackage(parsed, benchmarkId, runtimeProfile, benchStat
4672
4761
  if (completed % 50 === 0 || completed === total) {
4673
4762
  const elapsed = Math.round((Date.now() - benchStartTime) / 1e3);
4674
4763
  const remaining = total && elapsed > 0 ? Math.round((total - completed) / (completed / elapsed)) : "?";
4675
- console.log(
4764
+ printBenchStatusLine(
4765
+ parsed.json,
4676
4766
  ` [${benchmarkId}] ${completed}/${total ?? "?"} tasks (${elapsed}s elapsed, ~${remaining}s remaining)`
4677
4767
  );
4678
4768
  }
@@ -6614,7 +6704,7 @@ function normalizeOfflineRemoteUrl(raw) {
6614
6704
  return parsed.toString().replace(/\/$/, "");
6615
6705
  }
6616
6706
  function resolveOptionalOfflineRemoteUrl(args) {
6617
- const raw = resolveRequiredValueFlag(args, "--remote-url") ?? process.env.REMNIC_OFFLINE_REMOTE_URL ?? process.env.ENGRAM_OFFLINE_REMOTE_URL;
6707
+ const raw = resolveRequiredValueFlag(args, "--remote-url") ?? resolveRequiredValueFlag(args, "--remote") ?? process.env.REMNIC_OFFLINE_REMOTE_URL ?? process.env.ENGRAM_OFFLINE_REMOTE_URL;
6618
6708
  if (!raw || raw.trim().length === 0) return void 0;
6619
6709
  return normalizeOfflineRemoteUrl(raw);
6620
6710
  }
@@ -6672,6 +6762,12 @@ function offlineRequestTimeoutMs() {
6672
6762
  process.env.REMNIC_OFFLINE_REQUEST_TIMEOUT_MS ?? process.env.ENGRAM_OFFLINE_REQUEST_TIMEOUT_MS
6673
6763
  );
6674
6764
  }
6765
+ function offlineSnapshotPostTimeoutMs() {
6766
+ return parseOfflineSyncRequestTimeoutMs(
6767
+ process.env.REMNIC_OFFLINE_SNAPSHOT_POST_TIMEOUT_MS ?? process.env.ENGRAM_OFFLINE_SNAPSHOT_POST_TIMEOUT_MS,
6768
+ Math.min(offlineRequestTimeoutMs(), 6e4)
6769
+ );
6770
+ }
6675
6771
  function offlineFetchHeaders(token, initHeaders, defaultContentType) {
6676
6772
  const headers = new Headers(initHeaders);
6677
6773
  headers.set("authorization", `Bearer ${token}`);
@@ -6842,18 +6938,23 @@ async function fetchOfflineSnapshot(args) {
6842
6938
  const postRequest = offlineSnapshotBasePostRequest(postBody);
6843
6939
  if (postRequest) {
6844
6940
  const postRequestUsesGzip = new Headers(postRequest.headers).get("content-encoding")?.toLowerCase() === "gzip";
6941
+ const postAbort = new AbortController();
6942
+ const postTimeout = setTimeout(() => postAbort.abort(), offlineSnapshotPostTimeoutMs());
6845
6943
  try {
6846
6944
  return await fetchOfflineJson(
6847
6945
  offlineEndpoint(args.remoteUrl, "/remnic/v1/offline-sync/snapshot"),
6848
6946
  args.token,
6849
6947
  {
6850
6948
  method: "POST",
6949
+ signal: postAbort.signal,
6851
6950
  ...postRequest
6852
6951
  }
6853
6952
  );
6854
6953
  } catch (error) {
6855
6954
  if (!isOfflineSnapshotPostFallbackError(error, { compressed: postRequestUsesGzip })) throw error;
6856
6955
  tryStreamSnapshot = true;
6956
+ } finally {
6957
+ clearTimeout(postTimeout);
6857
6958
  }
6858
6959
  } else {
6859
6960
  tryStreamSnapshot = true;
@@ -6915,6 +7016,9 @@ function offlineSnapshotBasePostRequest(body) {
6915
7016
  function isOfflineSnapshotPostFallbackError(error, options = {}) {
6916
7017
  const message = error instanceof Error ? error.message : String(error);
6917
7018
  if (/offline-sync\/snapshot\b.* returned (404|405|413)\b/.test(message)) return true;
7019
+ if (/^offline sync request timed out after \d+ms: POST .*\/offline-sync\/snapshot\b/.test(message) || /^offline sync request failed before response: POST .*\/offline-sync\/snapshot\b/.test(message) || /^(This operation was aborted|The operation was aborted|AbortError)/i.test(message)) {
7020
+ return true;
7021
+ }
6918
7022
  if (!options.compressed) return false;
6919
7023
  return /offline-sync\/snapshot\b.* returned (400|415)\b/.test(message) && /\b(unsupported_content_encoding|invalid_gzip_body|invalid_json)\b/.test(message);
6920
7024
  }
@@ -6950,6 +7054,8 @@ var OFFLINE_SYNC_DIRECT_PUSH_MIN_BYTES = Math.min(
6950
7054
  );
6951
7055
  var OFFLINE_SYNC_DIRECT_HYDRATE_MIN_BYTES = OFFLINE_SYNC_DIRECT_PUSH_MIN_BYTES;
6952
7056
  var OFFLINE_SYNC_CHANGESET_RETRY_MAX = 1024;
7057
+ var OFFLINE_SYNC_CONTENT_MISSING_RETRY_MAX = 3;
7058
+ var OFFLINE_SYNC_CONTENT_MISSING_RETRY_DELAY_MS = 250;
6953
7059
  var OfflineRemoteFileChangedError = class extends Error {
6954
7060
  path;
6955
7061
  constructor(path12) {
@@ -7431,10 +7537,53 @@ function isOfflineFilesUnsupportedError(error) {
7431
7537
  const message = error instanceof Error ? error.message : String(error);
7432
7538
  return /offline sync request failed: .* returned 404\b/.test(message);
7433
7539
  }
7540
+ var OfflineMissingContentError = class extends Error {
7541
+ missing;
7542
+ constructor(missing) {
7543
+ const preview = missing.slice(0, 8).map((file) => file.path).join(", ");
7544
+ const suffix = missing.length > 8 ? `, ... +${missing.length - 8} more` : "";
7545
+ super(
7546
+ `remote offline content response omitted ${missing.length} changed file${missing.length === 1 ? "" : "s"}: ${preview}${suffix}; retry sync`
7547
+ );
7548
+ this.name = "OfflineMissingContentError";
7549
+ this.missing = missing;
7550
+ }
7551
+ };
7434
7552
  function isMissingOfflineContentError(error) {
7553
+ if (error instanceof OfflineMissingContentError) return true;
7435
7554
  const message = error instanceof Error ? error.message : String(error);
7436
7555
  return /^missing decoded content for /.test(message);
7437
7556
  }
7557
+ function formatMissingOfflineContentError(missing) {
7558
+ return new OfflineMissingContentError(missing);
7559
+ }
7560
+ function isOfflineMissingContentDeferrablePath(relPath) {
7561
+ const parts = relPath.split("/");
7562
+ return parts[0] === "profiling" || parts[0] === "namespaces" && parts[2] === "profiling";
7563
+ }
7564
+ function deferMissingOfflineContent(missing, deferredPaths) {
7565
+ if (!deferredPaths) return [...missing];
7566
+ const stillMissing = [];
7567
+ for (const file of missing) {
7568
+ if (isOfflineMissingContentDeferrablePath(file.path)) {
7569
+ deferredPaths.add(file.path);
7570
+ } else {
7571
+ stillMissing.push(file);
7572
+ }
7573
+ }
7574
+ return stillMissing;
7575
+ }
7576
+ function formatMissingDecodedContentError(missing) {
7577
+ const preview = missing.slice(0, 8).map((file) => file.path).join(", ");
7578
+ const suffix = missing.length > 8 ? `, ... +${missing.length - 8} more` : "";
7579
+ return new Error(
7580
+ `remote offline content response omitted ${missing.length} changed file${missing.length === 1 ? "" : "s"}: ${preview}${suffix}; retry sync`
7581
+ );
7582
+ }
7583
+ async function waitForMissingOfflineContentRetry(delayMs) {
7584
+ if (delayMs <= 0) return;
7585
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
7586
+ }
7438
7587
  async function hydrateOfflineSnapshotContent(args) {
7439
7588
  const snapshot = normalizeOfflineSyncSnapshot(args.snapshot);
7440
7589
  const neededFiles = offlineSnapshotContentFilesForApply({
@@ -7448,26 +7597,47 @@ async function hydrateOfflineSnapshotContent(args) {
7448
7597
  const expectedByPath = new Map(snapshot.files.map((file) => [file.path, file]));
7449
7598
  const contentByPath = /* @__PURE__ */ new Map();
7450
7599
  const updatedByPath = /* @__PURE__ */ new Map();
7600
+ const fetchFiles = args.fetchFiles ?? fetchOfflineFiles;
7601
+ const retryMax = args.missingContentRetryMax ?? OFFLINE_SYNC_CONTENT_MISSING_RETRY_MAX;
7602
+ const retryDelayMs = args.missingContentRetryDelayMs ?? OFFLINE_SYNC_CONTENT_MISSING_RETRY_DELAY_MS;
7603
+ if (!Number.isInteger(retryMax) || retryMax < 0) {
7604
+ throw new Error("offline sync missing content retry max must be an integer >= 0");
7605
+ }
7606
+ if (!Number.isInteger(retryDelayMs) || retryDelayMs < 0) {
7607
+ throw new Error("offline sync missing content retry delay must be an integer >= 0");
7608
+ }
7451
7609
  try {
7452
- for (const batch of chunkOfflineFileContentBatches(neededFiles)) {
7453
- const partial = await fetchOfflineFiles({
7454
- remoteUrl: args.remoteUrl,
7455
- token: args.token,
7456
- namespace: args.namespace,
7457
- includeTranscripts: args.includeTranscripts,
7458
- paths: batch.map((file) => file.path)
7459
- });
7460
- for (const file of partial.files) {
7461
- const expected = expectedByPath.get(file.path);
7462
- if (!expected) continue;
7463
- if (typeof file.contentBase64 !== "string") {
7464
- throw new Error(`remote offline content response omitted contentBase64 for ${file.path}`);
7465
- }
7466
- if (file.sha256 !== expected.sha256 || file.bytes !== expected.bytes || file.mtimeMs !== expected.mtimeMs) {
7467
- updatedByPath.set(file.path, file);
7610
+ let pendingFiles = neededFiles;
7611
+ for (let attempt = 0; ; attempt += 1) {
7612
+ for (const batch of chunkOfflineFileContentBatches(pendingFiles)) {
7613
+ const partial = await fetchFiles({
7614
+ remoteUrl: args.remoteUrl,
7615
+ token: args.token,
7616
+ namespace: args.namespace,
7617
+ includeTranscripts: args.includeTranscripts,
7618
+ paths: batch.map((file) => file.path)
7619
+ });
7620
+ for (const file of partial.files) {
7621
+ const expected = expectedByPath.get(file.path);
7622
+ if (!expected) continue;
7623
+ if (typeof file.contentBase64 !== "string") {
7624
+ throw new Error(`remote offline content response omitted contentBase64 for ${file.path}`);
7625
+ }
7626
+ if (file.sha256 !== expected.sha256 || file.bytes !== expected.bytes || file.mtimeMs !== expected.mtimeMs) {
7627
+ updatedByPath.set(file.path, file);
7628
+ }
7629
+ contentByPath.set(file.path, file.contentBase64);
7468
7630
  }
7469
- contentByPath.set(file.path, file.contentBase64);
7470
7631
  }
7632
+ const missing2 = pendingFiles.filter((file) => !contentByPath.has(file.path));
7633
+ if (missing2.length === 0) break;
7634
+ if (attempt >= retryMax) {
7635
+ const stillMissing = deferMissingOfflineContent(missing2, args.missingContentDeferredPaths);
7636
+ if (stillMissing.length > 0) throw formatMissingOfflineContentError(stillMissing);
7637
+ break;
7638
+ }
7639
+ pendingFiles = missing2;
7640
+ await waitForMissingOfflineContentRetry(retryDelayMs);
7471
7641
  }
7472
7642
  } catch (error) {
7473
7643
  if (!isOfflineFilesUnsupportedError(error)) throw error;
@@ -7479,10 +7649,10 @@ async function hydrateOfflineSnapshotContent(args) {
7479
7649
  includeContent: true
7480
7650
  });
7481
7651
  }
7482
- const missing = neededFiles.map((file) => file.path).filter((relPath) => !contentByPath.has(relPath));
7652
+ const missing = neededFiles.map((file) => file.path).filter((relPath) => !contentByPath.has(relPath) && !args.missingContentDeferredPaths?.has(relPath));
7483
7653
  if (missing.length > 0) {
7484
- throw new Error(
7485
- `remote offline content response omitted ${missing.length} changed file${missing.length === 1 ? "" : "s"}; retry sync`
7654
+ throw formatMissingDecodedContentError(
7655
+ neededFiles.filter((file) => missing.includes(file.path))
7486
7656
  );
7487
7657
  }
7488
7658
  return {
@@ -8141,7 +8311,8 @@ async function runOfflineSyncOnce(options) {
8141
8311
  snapshot: remoteSnapshotMetadata,
8142
8312
  baseFiles,
8143
8313
  currentFiles: applyCurrentSnapshot.files,
8144
- deferredPaths: [...remoteDeferredPaths]
8314
+ deferredPaths: [...remoteDeferredPaths],
8315
+ missingContentDeferredPaths: remoteDeferredPaths
8145
8316
  });
8146
8317
  } catch (error) {
8147
8318
  if (pushed || partialHydration.hydratedFiles.length > 0) {
@@ -8185,7 +8356,8 @@ async function runOfflineSyncOnce(options) {
8185
8356
  snapshot: remoteSnapshotMetadata,
8186
8357
  baseFiles,
8187
8358
  currentFiles: applyCurrentSnapshot.files,
8188
- deferredPaths: [...remoteDeferredPaths]
8359
+ deferredPaths: [...remoteDeferredPaths],
8360
+ missingContentDeferredPaths: remoteDeferredPaths
8189
8361
  });
8190
8362
  } catch (retryError) {
8191
8363
  if (pushed || partialHydration.hydratedFiles.length > 0) {
@@ -8307,7 +8479,7 @@ async function cmdOffline(action, rest, json) {
8307
8479
  console.log(`Usage: remnic offline <prepare|sync|status|watch> [options]
8308
8480
 
8309
8481
  Options:
8310
- --remote-url <url> Remote Remnic server URL, e.g. http://home:4242
8482
+ --remote-url <url> Remote Remnic server URL, e.g. http://home:4242 (--remote alias accepted)
8311
8483
  --token <token> Bearer token for the remote server
8312
8484
  --namespace <name> Namespace to sync
8313
8485
  --memory-dir <dir> Local memory dir (defaults to resolved memoryDir)
@@ -8809,109 +8981,113 @@ async function cmdConnectors(action, rest, json) {
8809
8981
  const remnicCfg = raw.remnic ?? raw.engram ?? raw;
8810
8982
  const config = parseConfig(remnicCfg);
8811
8983
  const orchestrator = new Orchestrator(config);
8812
- await orchestrator.initialize();
8813
- await orchestrator.deferredReady;
8814
- const cfg = config.connectors;
8815
- const sharedIngestFn = async (docs) => {
8816
- const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
8817
- const turns = docs.map((doc) => ({
8818
- role: "assistant",
8819
- content: doc.title ? `# ${doc.title}
8984
+ try {
8985
+ await orchestrator.initialize();
8986
+ await orchestrator.deferredReady;
8987
+ const cfg = config.connectors;
8988
+ const sharedIngestFn = async (docs) => {
8989
+ const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
8990
+ const turns = docs.map((doc) => ({
8991
+ role: "assistant",
8992
+ content: doc.title ? `# ${doc.title}
8820
8993
 
8821
8994
  ${doc.content}` : doc.content,
8822
- timestamp: fetchedAt
8823
- }));
8824
- await orchestrator.ingestBulkImportBatch(turns);
8825
- };
8826
- const makeWriteCursorFn = (id) => async (state) => {
8827
- await writeLiveConnectorState(config.memoryDir, id, {
8828
- id,
8829
- cursor: state.cursor,
8830
- lastSyncAt: (/* @__PURE__ */ new Date()).toISOString(),
8831
- lastSyncStatus: state.lastSyncStatus,
8832
- ...state.lastSyncError !== void 0 ? { lastSyncError: state.lastSyncError } : {},
8833
- totalDocsImported: state.totalDocsImported
8834
- });
8835
- };
8836
- let runResult;
8837
- if (connectorName === GDRIVE_ID) {
8838
- if (!cfg?.googleDrive?.enabled) {
8839
- process.stderr.write(
8840
- `connectors run: connector "${connectorName}" is disabled. Set connectors.googleDrive.enabled=true in config.
8995
+ timestamp: fetchedAt
8996
+ }));
8997
+ await orchestrator.ingestBulkImportBatch(turns);
8998
+ };
8999
+ const makeWriteCursorFn = (id) => async (state) => {
9000
+ await writeLiveConnectorState(config.memoryDir, id, {
9001
+ id,
9002
+ cursor: state.cursor,
9003
+ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString(),
9004
+ lastSyncStatus: state.lastSyncStatus,
9005
+ ...state.lastSyncError !== void 0 ? { lastSyncError: state.lastSyncError } : {},
9006
+ totalDocsImported: state.totalDocsImported
9007
+ });
9008
+ };
9009
+ let runResult;
9010
+ if (connectorName === GDRIVE_ID) {
9011
+ if (!cfg?.googleDrive?.enabled) {
9012
+ process.stderr.write(
9013
+ `connectors run: connector "${connectorName}" is disabled. Set connectors.googleDrive.enabled=true in config.
8841
9014
  `
8842
- );
8843
- process.exitCode = 1;
8844
- return;
8845
- }
8846
- let validatedCfg;
8847
- try {
8848
- validatedCfg = validateGDriveCfg(cfg.googleDrive);
8849
- } catch (err) {
8850
- process.stderr.write(
8851
- `connectors run: invalid config for "${connectorName}": ${err instanceof Error ? err.message : String(err)}
9015
+ );
9016
+ process.exitCode = 1;
9017
+ return;
9018
+ }
9019
+ let validatedCfg;
9020
+ try {
9021
+ validatedCfg = validateGDriveCfg(cfg.googleDrive);
9022
+ } catch (err) {
9023
+ process.stderr.write(
9024
+ `connectors run: invalid config for "${connectorName}": ${err instanceof Error ? err.message : String(err)}
8852
9025
  `
8853
- );
8854
- process.exitCode = 1;
8855
- return;
8856
- }
8857
- const connector = makeGDriveConnector();
8858
- const state = await readLiveConnectorState(config.memoryDir, connectorName);
8859
- runResult = await pollOnce({
8860
- connectorId: connectorName,
8861
- priorState: state,
8862
- syncFn: (cursor) => connector.syncIncremental({
8863
- cursor,
8864
- config: validatedCfg
8865
- }),
8866
- ingestFn: sharedIngestFn,
8867
- writeCursorFn: makeWriteCursorFn(connectorName)
8868
- });
8869
- } else if (connectorName === NOTION_ID) {
8870
- if (!cfg?.notion?.enabled) {
9026
+ );
9027
+ process.exitCode = 1;
9028
+ return;
9029
+ }
9030
+ const connector = makeGDriveConnector();
9031
+ const state = await readLiveConnectorState(config.memoryDir, connectorName);
9032
+ runResult = await pollOnce({
9033
+ connectorId: connectorName,
9034
+ priorState: state,
9035
+ syncFn: (cursor) => connector.syncIncremental({
9036
+ cursor,
9037
+ config: validatedCfg
9038
+ }),
9039
+ ingestFn: sharedIngestFn,
9040
+ writeCursorFn: makeWriteCursorFn(connectorName)
9041
+ });
9042
+ } else if (connectorName === NOTION_ID) {
9043
+ if (!cfg?.notion?.enabled) {
9044
+ process.stderr.write(
9045
+ `connectors run: connector "${connectorName}" is disabled. Set connectors.notion.enabled=true in config.
9046
+ `
9047
+ );
9048
+ process.exitCode = 1;
9049
+ return;
9050
+ }
9051
+ let validatedCfg;
9052
+ try {
9053
+ validatedCfg = validateNotionCfg(cfg.notion);
9054
+ } catch (err) {
9055
+ process.stderr.write(
9056
+ `connectors run: invalid config for "${connectorName}": ${err instanceof Error ? err.message : String(err)}
9057
+ `
9058
+ );
9059
+ process.exitCode = 1;
9060
+ return;
9061
+ }
9062
+ const connector = makeNotionConnector();
9063
+ const state = await readLiveConnectorState(config.memoryDir, connectorName);
9064
+ runResult = await pollOnce({
9065
+ connectorId: connectorName,
9066
+ priorState: state,
9067
+ syncFn: (cursor) => connector.syncIncremental({
9068
+ cursor,
9069
+ config: validatedCfg
9070
+ }),
9071
+ ingestFn: sharedIngestFn,
9072
+ writeCursorFn: makeWriteCursorFn(connectorName)
9073
+ });
9074
+ } else {
8871
9075
  process.stderr.write(
8872
- `connectors run: connector "${connectorName}" is disabled. Set connectors.notion.enabled=true in config.
9076
+ `connectors run: unknown connector "${connectorName}". Known connectors: ${GDRIVE_ID}, ${NOTION_ID}.
8873
9077
  `
8874
9078
  );
8875
9079
  process.exitCode = 1;
8876
9080
  return;
8877
9081
  }
8878
- let validatedCfg;
8879
- try {
8880
- validatedCfg = validateNotionCfg(cfg.notion);
8881
- } catch (err) {
8882
- process.stderr.write(
8883
- `connectors run: invalid config for "${connectorName}": ${err instanceof Error ? err.message : String(err)}
8884
- `
8885
- );
9082
+ const output = renderRunResult(connectorName, runResult, format);
9083
+ if (runResult.error !== void 0 || runResult.stateWriteError !== void 0) {
9084
+ process.stderr.write(output + "\n");
8886
9085
  process.exitCode = 1;
8887
- return;
9086
+ } else {
9087
+ console.log(output);
8888
9088
  }
8889
- const connector = makeNotionConnector();
8890
- const state = await readLiveConnectorState(config.memoryDir, connectorName);
8891
- runResult = await pollOnce({
8892
- connectorId: connectorName,
8893
- priorState: state,
8894
- syncFn: (cursor) => connector.syncIncremental({
8895
- cursor,
8896
- config: validatedCfg
8897
- }),
8898
- ingestFn: sharedIngestFn,
8899
- writeCursorFn: makeWriteCursorFn(connectorName)
8900
- });
8901
- } else {
8902
- process.stderr.write(
8903
- `connectors run: unknown connector "${connectorName}". Known connectors: ${GDRIVE_ID}, ${NOTION_ID}.
8904
- `
8905
- );
8906
- process.exitCode = 1;
8907
- return;
8908
- }
8909
- const output = renderRunResult(connectorName, runResult, format);
8910
- if (runResult.error !== void 0 || runResult.stateWriteError !== void 0) {
8911
- process.stderr.write(output + "\n");
8912
- process.exitCode = 1;
8913
- } else {
8914
- console.log(output);
9089
+ } finally {
9090
+ await orchestrator.destroy();
8915
9091
  }
8916
9092
  } else {
8917
9093
  console.log("Usage: remnic connectors <list|install|remove|doctor|marketplace|status|run> [id]");
@@ -9219,7 +9395,7 @@ async function cmdLegacyBenchmark(action, rest, json) {
9219
9395
  } else if (action === "report") {
9220
9396
  const reportPath = benchConfig.reportPath;
9221
9397
  const suite = await runBenchSuite(service, { ...benchConfig, reportPath });
9222
- console.log(`Report saved to ${reportPath ?? "benchmarks/report.json"}`);
9398
+ printBenchStatusLine(json, `Report saved to ${reportPath ?? "benchmarks/report.json"}`);
9223
9399
  if (json) {
9224
9400
  console.log(JSON.stringify(suite.report, null, 2));
9225
9401
  }
@@ -9346,9 +9522,9 @@ async function cmdBench(rest) {
9346
9522
  }
9347
9523
  const completeCount = prevStatus.benchmarks.filter((b) => b.status === "complete").length;
9348
9524
  const failedCount = prevStatus.benchmarks.filter((b) => b.status === "failed").length;
9349
- console.log(`Resuming from: ${path11.basename(latestStatusPath)}`);
9350
- console.log(` Previous run: ${prevStatus.startedAt}`);
9351
- console.log(` Benchmarks: ${prevStatus.benchmarks.length} total, ${completeCount} complete, ${failedCount} failed`);
9525
+ printBenchStatusLine(parsed.json, `Resuming from: ${path11.basename(latestStatusPath)}`);
9526
+ printBenchStatusLine(parsed.json, ` Previous run: ${prevStatus.startedAt}`);
9527
+ printBenchStatusLine(parsed.json, ` Benchmarks: ${prevStatus.benchmarks.length} total, ${completeCount} complete, ${failedCount} failed`);
9352
9528
  const before = selectedBenchmarks.length;
9353
9529
  if (parsed.resume) {
9354
9530
  selectedWorkItems = filterBenchWorkItemsForPreviousStatus(
@@ -9357,7 +9533,7 @@ async function cmdBench(rest) {
9357
9533
  "resume"
9358
9534
  );
9359
9535
  selectedBenchmarks = [...new Set(selectedWorkItems.map((item) => item.benchmarkId))];
9360
- console.log(` Resuming: ${selectedBenchmarks.length} of ${before} benchmarks to re-run`);
9536
+ printBenchStatusLine(parsed.json, ` Resuming: ${selectedBenchmarks.length} of ${before} benchmarks to re-run`);
9361
9537
  } else {
9362
9538
  selectedWorkItems = filterBenchWorkItemsForPreviousStatus(
9363
9539
  selectedWorkItems,
@@ -9365,13 +9541,14 @@ async function cmdBench(rest) {
9365
9541
  "retry-failed"
9366
9542
  );
9367
9543
  selectedBenchmarks = [...new Set(selectedWorkItems.map((item) => item.benchmarkId))];
9368
- console.log(` Retrying: ${selectedBenchmarks.length} of ${before} selected benchmarks had failures`);
9544
+ printBenchStatusLine(parsed.json, ` Retrying: ${selectedBenchmarks.length} of ${before} selected benchmarks had failures`);
9369
9545
  }
9370
9546
  if (selectedWorkItems.length === 0) {
9371
9547
  if (parsed.retryFailed) {
9372
- console.log("Nothing to re-run \u2014 no selected benchmarks had failures.");
9548
+ printBenchStatusLine(parsed.json, "Nothing to re-run \u2014 no selected benchmarks had failures.");
9373
9549
  } else {
9374
- console.log(
9550
+ printBenchStatusLine(
9551
+ parsed.json,
9375
9552
  "Nothing to re-run \u2014 all selected benchmarks completed successfully in the previous run."
9376
9553
  );
9377
9554
  }
@@ -9417,6 +9594,7 @@ async function cmdBench(rest) {
9417
9594
  }
9418
9595
  } else {
9419
9596
  const fallbackResultPath = await runBenchViaFallback(parsed, benchmarkId, runtimeProfile);
9597
+ writtenPaths.push(fallbackResultPath);
9420
9598
  try {
9421
9599
  await updateBenchmarkCompleted(benchStatusPath, statusId, fallbackResultPath);
9422
9600
  } catch {
@@ -9532,13 +9710,10 @@ var [LAUNCHD_PLIST_PATH] = LAUNCHD_PLIST_PATHS;
9532
9710
  var SYSTEMD_UNIT_PATHS = systemdUnitPaths(resolveHomeDir());
9533
9711
  var [SYSTEMD_UNIT_PATH] = SYSTEMD_UNIT_PATHS;
9534
9712
  function readPid() {
9535
- for (const file of [PID_FILE, LEGACY_PID_FILE]) {
9536
- try {
9537
- return parseInt(fs7.readFileSync(file, "utf8").trim(), 10);
9538
- } catch {
9539
- }
9540
- }
9541
- return void 0;
9713
+ return readVerifiedDaemonPid({
9714
+ pidFiles: [PID_FILE, LEGACY_PID_FILE],
9715
+ expectedServerBin: resolveServerBin()
9716
+ });
9542
9717
  }
9543
9718
  function inferPort() {
9544
9719
  try {
@@ -9570,6 +9745,36 @@ function isStandaloneServiceInstalled() {
9570
9745
  if (isLinux()) return anyFileExists(SYSTEMD_UNIT_PATHS);
9571
9746
  return false;
9572
9747
  }
9748
+ function commandFailureDetail(error) {
9749
+ if (error && typeof error === "object") {
9750
+ const maybe = error;
9751
+ const stderr = maybe.stderr ? String(maybe.stderr).trim() : "";
9752
+ if (stderr) return stderr;
9753
+ const stdout = maybe.stdout ? String(maybe.stdout).trim() : "";
9754
+ if (stdout) return stdout;
9755
+ if (maybe.message) return maybe.message;
9756
+ }
9757
+ return String(error);
9758
+ }
9759
+ function failDaemonInstall(command, error) {
9760
+ console.error(`Error: daemon install failed while running ${command}.`);
9761
+ console.error(` ${commandFailureDetail(error)}`);
9762
+ process.exit(1);
9763
+ }
9764
+ function isLaunchdLabelLoaded(label) {
9765
+ return firstSuccessfulResult([label], (candidate) => {
9766
+ childProcess2.execSync(`launchctl list ${candidate} 2>/dev/null`, { stdio: "pipe" });
9767
+ return true;
9768
+ }) === true;
9769
+ }
9770
+ function isSystemdServiceActive() {
9771
+ return firstSuccessfulResult(SYSTEMD_SERVICE_CANDIDATES, (serviceName) => {
9772
+ const out = childProcess2.execSync(`systemctl --user is-active ${serviceName} 2>/dev/null`, {
9773
+ encoding: "utf8"
9774
+ }).trim();
9775
+ return out === "active" ? true : void 0;
9776
+ }) === true;
9777
+ }
9573
9778
  function selectLaunchdInspection(openclawPluginModeConfigured) {
9574
9779
  const canonical = inspectLaunchdPlist(LAUNCHD_PLIST_PATH);
9575
9780
  if (canonical.installed) return canonical;
@@ -9617,7 +9822,13 @@ function daemonInstall() {
9617
9822
  fs7.writeFileSync(LAUNCHD_PLIST_PATH, plist);
9618
9823
  try {
9619
9824
  launchdLoadPlist(LAUNCHD_PLIST_PATH);
9620
- } catch {
9825
+ } catch (err) {
9826
+ if (!isLaunchdLabelLoaded(LAUNCHD_LABEL)) {
9827
+ failDaemonInstall(`launchctl load -w ${LAUNCHD_PLIST_PATH}`, err);
9828
+ }
9829
+ }
9830
+ if (!isLaunchdLabelLoaded(LAUNCHD_LABEL)) {
9831
+ failDaemonInstall(`launchctl list ${LAUNCHD_LABEL}`, new Error("service was not loaded after install"));
9621
9832
  }
9622
9833
  console.log(`Installed launchd service: ${LAUNCHD_PLIST_PATH}`);
9623
9834
  console.log(` Label: ${LAUNCHD_LABEL}`);
@@ -9631,9 +9842,21 @@ function daemonInstall() {
9631
9842
  fs7.writeFileSync(SYSTEMD_UNIT_PATH, unit);
9632
9843
  try {
9633
9844
  childProcess2.execSync("systemctl --user daemon-reload", { stdio: "pipe" });
9845
+ } catch (err) {
9846
+ failDaemonInstall("systemctl --user daemon-reload", err);
9847
+ }
9848
+ try {
9634
9849
  childProcess2.execSync(`systemctl --user enable ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
9850
+ } catch (err) {
9851
+ failDaemonInstall(`systemctl --user enable ${SYSTEMD_SERVICE}`, err);
9852
+ }
9853
+ try {
9635
9854
  childProcess2.execSync(`systemctl --user start ${SYSTEMD_SERVICE}`, { stdio: "pipe" });
9636
- } catch {
9855
+ } catch (err) {
9856
+ failDaemonInstall(`systemctl --user start ${SYSTEMD_SERVICE}`, err);
9857
+ }
9858
+ if (!isSystemdServiceActive()) {
9859
+ failDaemonInstall(`systemctl --user is-active ${SYSTEMD_SERVICE}`, new Error("service is not active after install"));
9637
9860
  }
9638
9861
  console.log(`Installed systemd user service: ${SYSTEMD_UNIT_PATH}`);
9639
9862
  console.log(` Restart: on-failure, WantedBy: default.target`);
@@ -11397,6 +11620,8 @@ export {
11397
11620
  BENCHMARK_CATALOG,
11398
11621
  OFFLINE_SYNC_APPLY_MAX_REQUEST_BYTES,
11399
11622
  OFFLINE_SYNC_CHANGESET_RETRY_MAX,
11623
+ OFFLINE_SYNC_CONTENT_MISSING_RETRY_DELAY_MS,
11624
+ OFFLINE_SYNC_CONTENT_MISSING_RETRY_MAX,
11400
11625
  OFFLINE_SYNC_DIRECT_HYDRATE_MIN_BYTES,
11401
11626
  OFFLINE_SYNC_DIRECT_PUSH_MIN_BYTES,
11402
11627
  OFFLINE_SYNC_FILE_CONTENT_UPLOAD_CHUNK_BYTES,
@@ -11419,6 +11644,8 @@ export {
11419
11644
  formatOfflineRequestForError,
11420
11645
  getBenchUsageText,
11421
11646
  hasFlag,
11647
+ hydrateOfflineSnapshotContent,
11648
+ isOfflineMissingContentDeferrablePath,
11422
11649
  isOfflineSnapshotPostFallbackError,
11423
11650
  main,
11424
11651
  offlinePartialHydrationForPaths,
@@ -11438,6 +11665,7 @@ export {
11438
11665
  resolveConfigPath,
11439
11666
  resolveFlag,
11440
11667
  resolveMemoryDir,
11668
+ resolveOptionalOfflineRemoteUrl,
11441
11669
  resolveSyncSourceDir,
11442
11670
  runOfflineSyncOnce,
11443
11671
  runTrainingExport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/cli",
3
- "version": "1.0.25",
3
+ "version": "9.3.517",
4
4
  "description": "CLI for Remnic memory — init, query, doctor, daemon management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,20 +26,20 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "yaml": "^2.4.2",
29
- "@remnic/plugin-pi": "^1.0.9",
30
- "@remnic/server": "^1.0.5",
31
- "@remnic/core": "^1.1.31"
29
+ "@remnic/plugin-pi": "^9.3.517",
30
+ "@remnic/server": "^9.3.517",
31
+ "@remnic/core": "^9.3.517"
32
32
  },
33
33
  "peerDependencies": {
34
- "@remnic/bench": "^1.0.0",
35
- "@remnic/export-weclone": "^1.0.0",
36
- "@remnic/import-weclone": "^1.0.0",
37
- "@remnic/import-chatgpt": "^0.1.0",
38
- "@remnic/import-claude": "^0.1.0",
39
- "@remnic/import-gemini": "^0.1.0",
40
- "@remnic/import-lossless-claw": "^0.1.1",
41
- "@remnic/import-mem0": "^0.1.0",
42
- "@remnic/import-supermemory": "^0.1.1"
34
+ "@remnic/bench": "^9.3.517",
35
+ "@remnic/export-weclone": "^9.3.517",
36
+ "@remnic/import-weclone": "^9.3.517",
37
+ "@remnic/import-chatgpt": "^9.3.517",
38
+ "@remnic/import-claude": "^9.3.517",
39
+ "@remnic/import-gemini": "^9.3.517",
40
+ "@remnic/import-lossless-claw": "^9.3.517",
41
+ "@remnic/import-mem0": "^9.3.517",
42
+ "@remnic/import-supermemory": "^9.3.517"
43
43
  },
44
44
  "peerDependenciesMeta": {
45
45
  "@remnic/bench": {
@@ -73,15 +73,15 @@
73
73
  "devDependencies": {
74
74
  "tsup": "^8.5.1",
75
75
  "typescript": "^5.9.3",
76
- "@remnic/export-weclone": "1.0.1",
77
- "@remnic/bench": "1.0.1",
78
- "@remnic/import-weclone": "1.0.1",
79
- "@remnic/import-chatgpt": "0.1.0",
80
- "@remnic/import-claude": "0.1.0",
81
- "@remnic/import-gemini": "0.1.0",
82
- "@remnic/import-lossless-claw": "0.1.1",
83
- "@remnic/import-supermemory": "0.1.2",
84
- "@remnic/import-mem0": "0.1.0"
76
+ "@remnic/export-weclone": "9.3.517",
77
+ "@remnic/bench": "9.3.517",
78
+ "@remnic/import-weclone": "9.3.517",
79
+ "@remnic/import-chatgpt": "9.3.517",
80
+ "@remnic/import-claude": "9.3.517",
81
+ "@remnic/import-gemini": "9.3.517",
82
+ "@remnic/import-lossless-claw": "9.3.517",
83
+ "@remnic/import-supermemory": "9.3.517",
84
+ "@remnic/import-mem0": "9.3.517"
85
85
  },
86
86
  "license": "MIT",
87
87
  "repository": {