@remnic/cli 1.0.9 → 1.0.11
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/index.js +157 -10
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -90,6 +90,7 @@ import {
|
|
|
90
90
|
renderXray,
|
|
91
91
|
applyOfflineSyncSnapshot,
|
|
92
92
|
buildOfflineSyncChangeset,
|
|
93
|
+
buildOfflineSyncSnapshot,
|
|
93
94
|
defaultOfflineSyncStatePath,
|
|
94
95
|
offlineSyncStateFromSnapshot,
|
|
95
96
|
readOfflineSyncState,
|
|
@@ -6647,11 +6648,25 @@ async function fetchOfflineSnapshot(args) {
|
|
|
6647
6648
|
offlineEndpoint(args.remoteUrl, "/remnic/v1/offline-sync/snapshot", {
|
|
6648
6649
|
namespace: args.namespace,
|
|
6649
6650
|
include_transcripts: args.includeTranscripts ? "true" : "false",
|
|
6650
|
-
content: "true"
|
|
6651
|
+
content: args.includeContent === false ? "false" : "true"
|
|
6651
6652
|
}),
|
|
6652
6653
|
args.token
|
|
6653
6654
|
);
|
|
6654
6655
|
}
|
|
6656
|
+
async function fetchOfflineFiles(args) {
|
|
6657
|
+
return fetchOfflineJson(
|
|
6658
|
+
offlineEndpoint(args.remoteUrl, "/remnic/v1/offline-sync/files"),
|
|
6659
|
+
args.token,
|
|
6660
|
+
{
|
|
6661
|
+
method: "POST",
|
|
6662
|
+
body: JSON.stringify({
|
|
6663
|
+
namespace: args.namespace,
|
|
6664
|
+
includeTranscripts: args.includeTranscripts,
|
|
6665
|
+
paths: args.paths
|
|
6666
|
+
})
|
|
6667
|
+
}
|
|
6668
|
+
);
|
|
6669
|
+
}
|
|
6655
6670
|
function resolvedOfflineSnapshotNamespace(snapshot, requestedNamespace) {
|
|
6656
6671
|
const resolved = typeof snapshot.namespace === "string" && snapshot.namespace.trim().length > 0 ? snapshot.namespace.trim() : void 0;
|
|
6657
6672
|
return resolved ?? requestedNamespace;
|
|
@@ -6684,6 +6699,99 @@ async function readFirstOfflineSyncState(paths) {
|
|
|
6684
6699
|
}
|
|
6685
6700
|
return null;
|
|
6686
6701
|
}
|
|
6702
|
+
function offlineFileStateMap(files) {
|
|
6703
|
+
return new Map(files.map((file) => [file.path, file]));
|
|
6704
|
+
}
|
|
6705
|
+
function offlineSnapshotContentPathsForApply(options) {
|
|
6706
|
+
const base = offlineFileStateMap(options.baseFiles);
|
|
6707
|
+
const current = options.currentFiles ? offlineFileStateMap(options.currentFiles) : null;
|
|
6708
|
+
const paths = [];
|
|
6709
|
+
for (const incoming of options.snapshot.files) {
|
|
6710
|
+
if (current?.get(incoming.path)?.sha256 === incoming.sha256) continue;
|
|
6711
|
+
if (base.get(incoming.path)?.sha256 === incoming.sha256) continue;
|
|
6712
|
+
paths.push(incoming.path);
|
|
6713
|
+
}
|
|
6714
|
+
return paths.sort();
|
|
6715
|
+
}
|
|
6716
|
+
function chunkOfflineFilePaths(paths) {
|
|
6717
|
+
const chunks = [];
|
|
6718
|
+
let current = [];
|
|
6719
|
+
let currentBytes = 256;
|
|
6720
|
+
for (const relPath of paths) {
|
|
6721
|
+
const cost = Buffer.byteLength(JSON.stringify(relPath), "utf-8") + 1;
|
|
6722
|
+
if (current.length > 0 && (current.length >= 1e3 || currentBytes + cost > 96e3)) {
|
|
6723
|
+
chunks.push(current);
|
|
6724
|
+
current = [];
|
|
6725
|
+
currentBytes = 256;
|
|
6726
|
+
}
|
|
6727
|
+
current.push(relPath);
|
|
6728
|
+
currentBytes += cost;
|
|
6729
|
+
}
|
|
6730
|
+
if (current.length > 0) chunks.push(current);
|
|
6731
|
+
return chunks;
|
|
6732
|
+
}
|
|
6733
|
+
function isOfflineFilesUnsupportedError(error) {
|
|
6734
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6735
|
+
return /offline sync request failed: 404\b/.test(message);
|
|
6736
|
+
}
|
|
6737
|
+
function isMissingOfflineContentError(error) {
|
|
6738
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6739
|
+
return /^missing decoded content for /.test(message);
|
|
6740
|
+
}
|
|
6741
|
+
async function hydrateOfflineSnapshotContent(args) {
|
|
6742
|
+
const neededPaths = offlineSnapshotContentPathsForApply({
|
|
6743
|
+
snapshot: args.snapshot,
|
|
6744
|
+
baseFiles: args.baseFiles,
|
|
6745
|
+
currentFiles: args.currentFiles
|
|
6746
|
+
});
|
|
6747
|
+
if (neededPaths.length === 0) return args.snapshot;
|
|
6748
|
+
const expectedByPath = new Map(args.snapshot.files.map((file) => [file.path, file]));
|
|
6749
|
+
const contentByPath = /* @__PURE__ */ new Map();
|
|
6750
|
+
try {
|
|
6751
|
+
for (const batch of chunkOfflineFilePaths(neededPaths)) {
|
|
6752
|
+
const partial = await fetchOfflineFiles({
|
|
6753
|
+
remoteUrl: args.remoteUrl,
|
|
6754
|
+
token: args.token,
|
|
6755
|
+
namespace: args.namespace,
|
|
6756
|
+
includeTranscripts: args.includeTranscripts,
|
|
6757
|
+
paths: batch
|
|
6758
|
+
});
|
|
6759
|
+
for (const file of partial.files) {
|
|
6760
|
+
const expected = expectedByPath.get(file.path);
|
|
6761
|
+
if (!expected) continue;
|
|
6762
|
+
if (file.sha256 !== expected.sha256 || file.bytes !== expected.bytes) {
|
|
6763
|
+
throw new Error(`remote file changed while fetching offline content: ${file.path}`);
|
|
6764
|
+
}
|
|
6765
|
+
if (typeof file.contentBase64 !== "string") {
|
|
6766
|
+
throw new Error(`remote offline content response omitted contentBase64 for ${file.path}`);
|
|
6767
|
+
}
|
|
6768
|
+
contentByPath.set(file.path, file.contentBase64);
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
} catch (error) {
|
|
6772
|
+
if (!isOfflineFilesUnsupportedError(error)) throw error;
|
|
6773
|
+
return fetchOfflineSnapshot({
|
|
6774
|
+
remoteUrl: args.remoteUrl,
|
|
6775
|
+
token: args.token,
|
|
6776
|
+
namespace: args.namespace,
|
|
6777
|
+
includeTranscripts: args.includeTranscripts,
|
|
6778
|
+
includeContent: true
|
|
6779
|
+
});
|
|
6780
|
+
}
|
|
6781
|
+
const missing = neededPaths.filter((relPath) => !contentByPath.has(relPath));
|
|
6782
|
+
if (missing.length > 0) {
|
|
6783
|
+
throw new Error(
|
|
6784
|
+
`remote offline content response omitted ${missing.length} changed file${missing.length === 1 ? "" : "s"}; retry sync`
|
|
6785
|
+
);
|
|
6786
|
+
}
|
|
6787
|
+
return {
|
|
6788
|
+
...args.snapshot,
|
|
6789
|
+
files: args.snapshot.files.map((file) => {
|
|
6790
|
+
const contentBase64 = contentByPath.get(file.path);
|
|
6791
|
+
return contentBase64 === void 0 ? file : { ...file, contentBase64 };
|
|
6792
|
+
})
|
|
6793
|
+
};
|
|
6794
|
+
}
|
|
6687
6795
|
async function pushOfflineChanges(args) {
|
|
6688
6796
|
return fetchOfflineJson(
|
|
6689
6797
|
offlineEndpoint(args.remoteUrl, "/remnic/v1/offline-sync/apply"),
|
|
@@ -6746,7 +6854,8 @@ async function runOfflineSyncOnce(options) {
|
|
|
6746
6854
|
remoteUrl: options.remoteUrl,
|
|
6747
6855
|
token: options.token,
|
|
6748
6856
|
namespace: options.namespace,
|
|
6749
|
-
includeTranscripts: options.includeTranscripts
|
|
6857
|
+
includeTranscripts: options.includeTranscripts,
|
|
6858
|
+
includeContent: false
|
|
6750
6859
|
});
|
|
6751
6860
|
syncNamespace = resolvedOfflineSnapshotNamespace(namespaceProbe, options.namespace);
|
|
6752
6861
|
}
|
|
@@ -6787,21 +6896,59 @@ async function runOfflineSyncOnce(options) {
|
|
|
6787
6896
|
namespace: syncNamespace,
|
|
6788
6897
|
changeset
|
|
6789
6898
|
}) : null;
|
|
6790
|
-
const
|
|
6899
|
+
const remoteSnapshotMetadata = await fetchOfflineSnapshot({
|
|
6791
6900
|
remoteUrl: options.remoteUrl,
|
|
6792
6901
|
token: options.token,
|
|
6793
6902
|
namespace: syncNamespace,
|
|
6794
|
-
includeTranscripts: options.includeTranscripts
|
|
6903
|
+
includeTranscripts: options.includeTranscripts,
|
|
6904
|
+
includeContent: false
|
|
6795
6905
|
});
|
|
6796
|
-
const
|
|
6797
|
-
const pull = await applyOfflineSyncSnapshot({
|
|
6906
|
+
const currentSnapshot = await buildOfflineSyncSnapshot({
|
|
6798
6907
|
root: options.memoryDir,
|
|
6799
|
-
|
|
6908
|
+
sourceId: localOfflineSourceId(options.memoryDir),
|
|
6909
|
+
includeContent: false,
|
|
6910
|
+
includeTranscripts: options.includeTranscripts,
|
|
6911
|
+
readFile: storageIo.readFile
|
|
6912
|
+
});
|
|
6913
|
+
const remoteSnapshot = await hydrateOfflineSnapshotContent({
|
|
6914
|
+
remoteUrl: options.remoteUrl,
|
|
6915
|
+
token: options.token,
|
|
6916
|
+
namespace: syncNamespace,
|
|
6917
|
+
includeTranscripts: options.includeTranscripts,
|
|
6918
|
+
snapshot: remoteSnapshotMetadata,
|
|
6800
6919
|
baseFiles,
|
|
6801
|
-
|
|
6802
|
-
writeFile: storageIo.writeFile,
|
|
6803
|
-
deleteFile: storageIo.deleteFile
|
|
6920
|
+
currentFiles: currentSnapshot.files
|
|
6804
6921
|
});
|
|
6922
|
+
const resolvedNamespace = resolvedOfflineSnapshotNamespace(remoteSnapshot, syncNamespace);
|
|
6923
|
+
let pull;
|
|
6924
|
+
try {
|
|
6925
|
+
pull = await applyOfflineSyncSnapshot({
|
|
6926
|
+
root: options.memoryDir,
|
|
6927
|
+
snapshot: remoteSnapshot,
|
|
6928
|
+
baseFiles,
|
|
6929
|
+
readFile: storageIo.readFile,
|
|
6930
|
+
writeFile: storageIo.writeFile,
|
|
6931
|
+
deleteFile: storageIo.deleteFile
|
|
6932
|
+
});
|
|
6933
|
+
} catch (error) {
|
|
6934
|
+
if (!isMissingOfflineContentError(error)) throw error;
|
|
6935
|
+
const retrySnapshot = await hydrateOfflineSnapshotContent({
|
|
6936
|
+
remoteUrl: options.remoteUrl,
|
|
6937
|
+
token: options.token,
|
|
6938
|
+
namespace: syncNamespace,
|
|
6939
|
+
includeTranscripts: options.includeTranscripts,
|
|
6940
|
+
snapshot: remoteSnapshotMetadata,
|
|
6941
|
+
baseFiles
|
|
6942
|
+
});
|
|
6943
|
+
pull = await applyOfflineSyncSnapshot({
|
|
6944
|
+
root: options.memoryDir,
|
|
6945
|
+
snapshot: retrySnapshot,
|
|
6946
|
+
baseFiles,
|
|
6947
|
+
readFile: storageIo.readFile,
|
|
6948
|
+
writeFile: storageIo.writeFile,
|
|
6949
|
+
deleteFile: storageIo.deleteFile
|
|
6950
|
+
});
|
|
6951
|
+
}
|
|
6805
6952
|
const state = offlineSyncStateFromSnapshot({
|
|
6806
6953
|
remoteId: options.remoteUrl,
|
|
6807
6954
|
namespace: resolvedNamespace,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "CLI for Remnic memory — init, query, doctor, daemon management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"yaml": "^2.4.2",
|
|
29
|
-
"@remnic/
|
|
29
|
+
"@remnic/core": "^1.1.17",
|
|
30
30
|
"@remnic/server": "^1.0.5",
|
|
31
|
-
"@remnic/
|
|
31
|
+
"@remnic/plugin-pi": "^1.0.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@remnic/bench": "^1.0.0",
|
|
@@ -76,12 +76,12 @@
|
|
|
76
76
|
"@remnic/bench": "1.0.1",
|
|
77
77
|
"@remnic/export-weclone": "1.0.1",
|
|
78
78
|
"@remnic/import-weclone": "1.0.1",
|
|
79
|
-
"@remnic/import-mem0": "0.1.0",
|
|
80
|
-
"@remnic/import-lossless-claw": "0.1.1",
|
|
81
79
|
"@remnic/import-chatgpt": "0.1.0",
|
|
82
|
-
"@remnic/import-
|
|
80
|
+
"@remnic/import-claude": "0.1.0",
|
|
81
|
+
"@remnic/import-lossless-claw": "0.1.1",
|
|
82
|
+
"@remnic/import-mem0": "0.1.0",
|
|
83
83
|
"@remnic/import-gemini": "0.1.0",
|
|
84
|
-
"@remnic/import-
|
|
84
|
+
"@remnic/import-supermemory": "0.1.2"
|
|
85
85
|
},
|
|
86
86
|
"license": "MIT",
|
|
87
87
|
"repository": {
|