@remnic/cli 9.69.15 → 9.69.16
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 +138 -35
- package/package.json +32 -32
package/dist/index.js
CHANGED
|
@@ -1701,6 +1701,65 @@ function secureStoreEnvelopeHeaderAad(salt) {
|
|
|
1701
1701
|
return out;
|
|
1702
1702
|
}
|
|
1703
1703
|
|
|
1704
|
+
// src/converge-watch.ts
|
|
1705
|
+
var CONVERGE_WATCH_MIN_INTERVAL_MS = 1e3;
|
|
1706
|
+
var CONVERGE_WATCH_DEFAULT_INTERVAL_MS = 3e5;
|
|
1707
|
+
var CONVERGE_WATCH_MAX_INTERVAL_MS = 2147483647;
|
|
1708
|
+
function sleepAborted(ms, signal) {
|
|
1709
|
+
return new Promise((resolve2) => {
|
|
1710
|
+
const timer = setTimeout(() => {
|
|
1711
|
+
signal.removeEventListener("abort", onAbort);
|
|
1712
|
+
resolve2(true);
|
|
1713
|
+
}, ms);
|
|
1714
|
+
const onAbort = () => {
|
|
1715
|
+
clearTimeout(timer);
|
|
1716
|
+
resolve2(false);
|
|
1717
|
+
};
|
|
1718
|
+
if (signal.aborted) {
|
|
1719
|
+
clearTimeout(timer);
|
|
1720
|
+
resolve2(false);
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
async function convergeWatch(options) {
|
|
1727
|
+
const intervalMs = Math.min(
|
|
1728
|
+
CONVERGE_WATCH_MAX_INTERVAL_MS,
|
|
1729
|
+
Math.max(CONVERGE_WATCH_MIN_INTERVAL_MS, options.intervalMs ?? CONVERGE_WATCH_DEFAULT_INTERVAL_MS)
|
|
1730
|
+
);
|
|
1731
|
+
const { apply, intervalMs: _intervalMs, maxCycles, onCycle, signal, ...applyOptions } = options;
|
|
1732
|
+
const outcome = {
|
|
1733
|
+
cycles: 0,
|
|
1734
|
+
convergedCycles: 0,
|
|
1735
|
+
appliedCycles: 0,
|
|
1736
|
+
failedCycles: 0,
|
|
1737
|
+
lastStatus: "aborted"
|
|
1738
|
+
};
|
|
1739
|
+
while (maxCycles === void 0 || outcome.cycles < maxCycles) {
|
|
1740
|
+
if (signal?.aborted) break;
|
|
1741
|
+
try {
|
|
1742
|
+
const result = await apply(applyOptions);
|
|
1743
|
+
outcome.cycles += 1;
|
|
1744
|
+
if (result.status === "converged") outcome.convergedCycles += 1;
|
|
1745
|
+
else if (result.status === "stopped_unresolved_conflicts" || result.status === "applied" && result.transfers.failed > 0) {
|
|
1746
|
+
outcome.failedCycles += 1;
|
|
1747
|
+
} else outcome.appliedCycles += 1;
|
|
1748
|
+
outcome.lastStatus = result.status;
|
|
1749
|
+
onCycle?.(outcome.cycles, { result });
|
|
1750
|
+
} catch (err) {
|
|
1751
|
+
outcome.cycles += 1;
|
|
1752
|
+
outcome.failedCycles += 1;
|
|
1753
|
+
outcome.lastStatus = "error";
|
|
1754
|
+
onCycle?.(outcome.cycles, { error: err });
|
|
1755
|
+
}
|
|
1756
|
+
if (maxCycles !== void 0 && outcome.cycles >= maxCycles) break;
|
|
1757
|
+
const slept = await sleepAborted(intervalMs, signal ?? new AbortController().signal);
|
|
1758
|
+
if (!slept) break;
|
|
1759
|
+
}
|
|
1760
|
+
return outcome;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1704
1763
|
// src/converge.ts
|
|
1705
1764
|
import { resolveAgentAccessAuthToken } from "@remnic/core/resolve-auth-token.js";
|
|
1706
1765
|
|
|
@@ -2292,7 +2351,10 @@ async function computeConvergePlan(options = {}) {
|
|
|
2292
2351
|
if (options.baseFilesByNamespace) {
|
|
2293
2352
|
for (const [ns, files] of options.baseFilesByNamespace) {
|
|
2294
2353
|
namespacesToPlan.add(ns);
|
|
2295
|
-
baseMap.set(
|
|
2354
|
+
baseMap.set(
|
|
2355
|
+
ns,
|
|
2356
|
+
files.filter((file) => !isInternalRemnicStatePath3(file.path))
|
|
2357
|
+
);
|
|
2296
2358
|
}
|
|
2297
2359
|
}
|
|
2298
2360
|
if (options.semanticAgreementsByNamespace) {
|
|
@@ -2304,7 +2366,10 @@ async function computeConvergePlan(options = {}) {
|
|
|
2304
2366
|
if (options.localFilesByNamespace) {
|
|
2305
2367
|
for (const [ns, files] of options.localFilesByNamespace) {
|
|
2306
2368
|
namespacesToPlan.add(ns);
|
|
2307
|
-
localMap.set(
|
|
2369
|
+
localMap.set(
|
|
2370
|
+
ns,
|
|
2371
|
+
files.filter((file) => !isInternalRemnicStatePath3(file.path))
|
|
2372
|
+
);
|
|
2308
2373
|
}
|
|
2309
2374
|
}
|
|
2310
2375
|
if (options.localTombstonesByNamespace) {
|
|
@@ -2321,7 +2386,10 @@ async function computeConvergePlan(options = {}) {
|
|
|
2321
2386
|
if (options.peerFilesByNamespace) {
|
|
2322
2387
|
for (const [ns, files] of options.peerFilesByNamespace) {
|
|
2323
2388
|
namespacesToPlan.add(ns);
|
|
2324
|
-
peerMap.set(
|
|
2389
|
+
peerMap.set(
|
|
2390
|
+
ns,
|
|
2391
|
+
files.filter((file) => !isInternalRemnicStatePath3(file.path))
|
|
2392
|
+
);
|
|
2325
2393
|
}
|
|
2326
2394
|
}
|
|
2327
2395
|
if (options.peerTombstonesByNamespace) {
|
|
@@ -2418,12 +2486,7 @@ async function computeConvergePlan(options = {}) {
|
|
|
2418
2486
|
}
|
|
2419
2487
|
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
2420
2488
|
const timeoutMs = options.peerRequestTimeoutMs ?? DEFAULT_PEER_REQUEST_TIMEOUT_MS;
|
|
2421
|
-
const capabilities = await fetchPeerSyncCapabilities(
|
|
2422
|
-
peerUrl,
|
|
2423
|
-
resolvedToken,
|
|
2424
|
-
fetchFn,
|
|
2425
|
-
timeoutMs
|
|
2426
|
-
);
|
|
2489
|
+
const capabilities = await fetchPeerSyncCapabilities(peerUrl, resolvedToken, fetchFn, timeoutMs);
|
|
2427
2490
|
for (const ns of namespacesToPlan) {
|
|
2428
2491
|
const peerData = await fetchPeerSnapshot(peerUrl, ns, resolvedToken, fetchFn, timeoutMs);
|
|
2429
2492
|
const streamedManifest = capabilities?.manifestStream ? await fetchPeerManifestStream(peerUrl, ns, resolvedToken, fetchFn, timeoutMs) : null;
|
|
@@ -2459,14 +2522,7 @@ async function computeConvergePlan(options = {}) {
|
|
|
2459
2522
|
for (const tombstonePath of TOMBSTONE_PATHS) {
|
|
2460
2523
|
const state = peerFiles.find((file) => file.path === tombstonePath);
|
|
2461
2524
|
if (!state) continue;
|
|
2462
|
-
const remote = await fetchPeerFileContent(
|
|
2463
|
-
peerUrl,
|
|
2464
|
-
ns,
|
|
2465
|
-
tombstonePath,
|
|
2466
|
-
resolvedToken,
|
|
2467
|
-
fetchFn,
|
|
2468
|
-
timeoutMs
|
|
2469
|
-
);
|
|
2525
|
+
const remote = await fetchPeerFileContent(peerUrl, ns, tombstonePath, resolvedToken, fetchFn, timeoutMs);
|
|
2470
2526
|
if (!remote || remote.sha256.toLowerCase() !== state.sha256.toLowerCase()) {
|
|
2471
2527
|
throw new Error(`failed to read peer tombstone evidence: ${tombstonePath}`);
|
|
2472
2528
|
}
|
|
@@ -2939,13 +2995,7 @@ async function executeConvergeApply(options = {}) {
|
|
|
2939
2995
|
}
|
|
2940
2996
|
if (options.peerUrl && peerMutatedNamespaces.size > 0) {
|
|
2941
2997
|
const namespaces = [...peerMutatedNamespaces].sort();
|
|
2942
|
-
if (!await postPeerConvergenceComplete(
|
|
2943
|
-
options.peerUrl,
|
|
2944
|
-
namespaces,
|
|
2945
|
-
resolvedToken,
|
|
2946
|
-
fetchFn,
|
|
2947
|
-
timeoutMs
|
|
2948
|
-
)) {
|
|
2998
|
+
if (!await postPeerConvergenceComplete(options.peerUrl, namespaces, resolvedToken, fetchFn, timeoutMs)) {
|
|
2949
2999
|
actualTransfers.failed += 1;
|
|
2950
3000
|
}
|
|
2951
3001
|
}
|
|
@@ -2975,11 +3025,7 @@ async function updateCursorsForPlan(plan, options) {
|
|
|
2975
3025
|
for (const ns of namespaces) {
|
|
2976
3026
|
const cursorPath = defaultConvergeCursorPath(memoryDir, peerUrl, ns);
|
|
2977
3027
|
const priorSemanticAgreements = options.semanticAgreementsByNamespace?.get(ns) ?? (await readConvergeCursor(cursorPath))?.semanticAgreements ?? [];
|
|
2978
|
-
const { baseFiles, semanticAgreements } = deriveConvergeCursorBase(
|
|
2979
|
-
plan.entries,
|
|
2980
|
-
ns,
|
|
2981
|
-
priorSemanticAgreements
|
|
2982
|
-
);
|
|
3028
|
+
const { baseFiles, semanticAgreements } = deriveConvergeCursorBase(plan.entries, ns, priorSemanticAgreements);
|
|
2983
3029
|
const cursorState = {
|
|
2984
3030
|
version: 1,
|
|
2985
3031
|
peerUrl,
|
|
@@ -3031,11 +3077,12 @@ function formatConvergeApplyReport(result) {
|
|
|
3031
3077
|
}
|
|
3032
3078
|
async function cmdConverge(action, rest, json, config = parseConfig13({})) {
|
|
3033
3079
|
if (action === "help" || action === "--help" || action === "-h" || rest.includes("--help") || rest.includes("-h")) {
|
|
3034
|
-
console.log(`Usage: remnic converge <plan|apply> [options]
|
|
3080
|
+
console.log(`Usage: remnic converge <plan|apply|watch> [options]
|
|
3035
3081
|
|
|
3036
3082
|
Subcommands:
|
|
3037
3083
|
plan Compute and display reconciliation plan
|
|
3038
3084
|
apply Execute bidirectional converge transport (alias: transport, sync)
|
|
3085
|
+
watch Run apply on a cadence until stopped (scheduled replication)
|
|
3039
3086
|
|
|
3040
3087
|
Options:
|
|
3041
3088
|
--peer <url> Peer server URL (or --remote-url / --remote)
|
|
@@ -3043,13 +3090,15 @@ Options:
|
|
|
3043
3090
|
--conflict-policy <policy>
|
|
3044
3091
|
Policy override (newest-wins|manual)
|
|
3045
3092
|
Default: converge.conflictPolicy (newest-wins)
|
|
3093
|
+
--interval <seconds>
|
|
3094
|
+
Watch cadence in seconds (watch only; default 300, min 1)
|
|
3046
3095
|
--dry-run Simulate transfers without mutating disk or remote peer
|
|
3047
3096
|
--json Output detailed JSON plan report
|
|
3048
3097
|
`);
|
|
3049
3098
|
return;
|
|
3050
3099
|
}
|
|
3051
|
-
if (action !== "plan" && action !== "apply" && action !== "transport" && action !== "sync") {
|
|
3052
|
-
process.stderr.write(`converge: unknown action "${action}". Use: plan or
|
|
3100
|
+
if (action !== "plan" && action !== "apply" && action !== "transport" && action !== "sync" && action !== "watch") {
|
|
3101
|
+
process.stderr.write(`converge: unknown action "${action}". Use: plan, apply, or watch [options].
|
|
3053
3102
|
`);
|
|
3054
3103
|
process.exitCode = 2;
|
|
3055
3104
|
return;
|
|
@@ -3058,6 +3107,7 @@ Options:
|
|
|
3058
3107
|
let peerToken;
|
|
3059
3108
|
let dryRun = false;
|
|
3060
3109
|
let conflictPolicy;
|
|
3110
|
+
let intervalSeconds;
|
|
3061
3111
|
for (let i = 0; i < rest.length; i += 1) {
|
|
3062
3112
|
const arg = rest[i];
|
|
3063
3113
|
if ((arg === "--peer" || arg === "--remote-url" || arg === "--remote") && rest[i + 1]) {
|
|
@@ -3068,17 +3118,70 @@ Options:
|
|
|
3068
3118
|
i += 1;
|
|
3069
3119
|
} else if (arg === "--dry-run") {
|
|
3070
3120
|
dryRun = true;
|
|
3121
|
+
} else if (arg === "--interval") {
|
|
3122
|
+
const raw = rest[i + 1];
|
|
3123
|
+
const parsed = raw === void 0 ? Number.NaN : Number(raw);
|
|
3124
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3125
|
+
process.stderr.write("converge: --interval must be a positive number of seconds.\n");
|
|
3126
|
+
process.exitCode = 2;
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
intervalSeconds = parsed;
|
|
3130
|
+
i += 1;
|
|
3071
3131
|
} else if (arg === "--conflict-policy") {
|
|
3072
3132
|
const policy = rest[i + 1];
|
|
3073
3133
|
if (typeof policy !== "string" || !CONVERGE_CONFLICT_POLICIES.includes(policy)) {
|
|
3074
|
-
throw new Error(
|
|
3075
|
-
`converge: --conflict-policy must be one of ${CONVERGE_CONFLICT_POLICIES.join(", ")}`
|
|
3076
|
-
);
|
|
3134
|
+
throw new Error(`converge: --conflict-policy must be one of ${CONVERGE_CONFLICT_POLICIES.join(", ")}`);
|
|
3077
3135
|
}
|
|
3078
3136
|
conflictPolicy = policy;
|
|
3079
3137
|
i += 1;
|
|
3080
3138
|
}
|
|
3081
3139
|
}
|
|
3140
|
+
if (action === "watch") {
|
|
3141
|
+
const controller = new AbortController();
|
|
3142
|
+
const onSignal = () => controller.abort();
|
|
3143
|
+
process.once("SIGINT", onSignal);
|
|
3144
|
+
process.once("SIGTERM", onSignal);
|
|
3145
|
+
let outcome;
|
|
3146
|
+
try {
|
|
3147
|
+
outcome = await convergeWatch({
|
|
3148
|
+
apply: (applyOptions) => executeConvergeApply(applyOptions),
|
|
3149
|
+
config,
|
|
3150
|
+
peerUrl,
|
|
3151
|
+
dryRun,
|
|
3152
|
+
peerToken,
|
|
3153
|
+
conflictPolicy,
|
|
3154
|
+
intervalMs: intervalSeconds !== void 0 ? intervalSeconds * 1e3 : void 0,
|
|
3155
|
+
signal: controller.signal,
|
|
3156
|
+
onCycle: json ? void 0 : (cycle, event) => {
|
|
3157
|
+
if (event.error !== void 0) {
|
|
3158
|
+
console.error(`converge watch: cycle ${cycle} failed: ${String(event.error)}`);
|
|
3159
|
+
return;
|
|
3160
|
+
}
|
|
3161
|
+
const result2 = event.result;
|
|
3162
|
+
if (!result2) return;
|
|
3163
|
+
const transfers = result2.transfers;
|
|
3164
|
+
console.log(
|
|
3165
|
+
`converge watch: cycle ${cycle} status=${result2.status} pulled=${transfers.pulled} pushed=${transfers.pushed} conflicts=${transfers.conflictsResolved} failed=${transfers.failed}`
|
|
3166
|
+
);
|
|
3167
|
+
}
|
|
3168
|
+
});
|
|
3169
|
+
if (json) {
|
|
3170
|
+
console.log(JSON.stringify(outcome, null, 2));
|
|
3171
|
+
} else {
|
|
3172
|
+
console.log(
|
|
3173
|
+
`converge watch stopped after ${outcome.cycles} cycle(s): ${outcome.convergedCycles} converged, ${outcome.appliedCycles} applied, ${outcome.failedCycles} failed (last: ${outcome.lastStatus}).`
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
} finally {
|
|
3177
|
+
process.removeListener("SIGINT", onSignal);
|
|
3178
|
+
process.removeListener("SIGTERM", onSignal);
|
|
3179
|
+
if (outcome && outcome.cycles > 0 && outcome.failedCycles === outcome.cycles) {
|
|
3180
|
+
process.exitCode = 1;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3082
3185
|
if (action === "plan") {
|
|
3083
3186
|
const plan = await computeConvergePlan({ config, peerUrl, peerToken, conflictPolicy });
|
|
3084
3187
|
if (json) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/cli",
|
|
3
|
-
"version": "9.69.
|
|
3
|
+
"version": "9.69.16",
|
|
4
4
|
"description": "CLI for Remnic memory — init, query, doctor, daemon management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,26 +26,26 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"yaml": "^2.4.2",
|
|
29
|
-
"@remnic/plugin-pi": "^9.69.
|
|
30
|
-
"@remnic/
|
|
31
|
-
"@remnic/
|
|
29
|
+
"@remnic/plugin-pi": "^9.69.16",
|
|
30
|
+
"@remnic/core": "^9.69.16",
|
|
31
|
+
"@remnic/server": "^9.69.16"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@remnic/bench": "^9.69.
|
|
35
|
-
"@remnic/plugin-openclaw": "^9.69.
|
|
36
|
-
"@remnic/export-weclone": "^9.69.
|
|
37
|
-
"@remnic/import-weclone": "^9.69.
|
|
38
|
-
"@remnic/import-chatgpt": "^9.69.
|
|
39
|
-
"@remnic/import-claude": "^9.69.
|
|
40
|
-
"@remnic/import-gemini": "^9.69.
|
|
41
|
-
"@remnic/import-lossless-claw": "^9.69.
|
|
42
|
-
"@remnic/import-mem0": "^9.69.
|
|
43
|
-
"@remnic/import-supermemory": "^9.69.
|
|
44
|
-
"@remnic/import-okf": "^9.69.
|
|
45
|
-
"@remnic/connector-limitless": "^9.69.
|
|
46
|
-
"@remnic/connector-bee": "^9.69.
|
|
47
|
-
"@remnic/connector-omi": "^9.69.
|
|
48
|
-
"@remnic/capture-audio": "^9.69.
|
|
34
|
+
"@remnic/bench": "^9.69.16",
|
|
35
|
+
"@remnic/plugin-openclaw": "^9.69.16",
|
|
36
|
+
"@remnic/export-weclone": "^9.69.16",
|
|
37
|
+
"@remnic/import-weclone": "^9.69.16",
|
|
38
|
+
"@remnic/import-chatgpt": "^9.69.16",
|
|
39
|
+
"@remnic/import-claude": "^9.69.16",
|
|
40
|
+
"@remnic/import-gemini": "^9.69.16",
|
|
41
|
+
"@remnic/import-lossless-claw": "^9.69.16",
|
|
42
|
+
"@remnic/import-mem0": "^9.69.16",
|
|
43
|
+
"@remnic/import-supermemory": "^9.69.16",
|
|
44
|
+
"@remnic/import-okf": "^9.69.16",
|
|
45
|
+
"@remnic/connector-limitless": "^9.69.16",
|
|
46
|
+
"@remnic/connector-bee": "^9.69.16",
|
|
47
|
+
"@remnic/connector-omi": "^9.69.16",
|
|
48
|
+
"@remnic/capture-audio": "^9.69.16"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"@remnic/bench": {
|
|
@@ -97,19 +97,19 @@
|
|
|
97
97
|
"devDependencies": {
|
|
98
98
|
"tsup": "^8.5.1",
|
|
99
99
|
"typescript": "^5.9.3",
|
|
100
|
-
"@remnic/bench": "9.69.
|
|
101
|
-
"@remnic/plugin-openclaw": "9.69.
|
|
102
|
-
"@remnic/
|
|
103
|
-
"@remnic/
|
|
104
|
-
"@remnic/import-
|
|
105
|
-
"@remnic/import-
|
|
106
|
-
"@remnic/import-
|
|
107
|
-
"@remnic/import-
|
|
108
|
-
"@remnic/
|
|
109
|
-
"@remnic/
|
|
110
|
-
"@remnic/connector-bee": "9.69.
|
|
111
|
-
"@remnic/import-
|
|
112
|
-
"@remnic/connector-omi": "9.69.
|
|
100
|
+
"@remnic/bench": "9.69.16",
|
|
101
|
+
"@remnic/plugin-openclaw": "9.69.16",
|
|
102
|
+
"@remnic/import-weclone": "9.69.16",
|
|
103
|
+
"@remnic/export-weclone": "9.69.16",
|
|
104
|
+
"@remnic/import-chatgpt": "9.69.16",
|
|
105
|
+
"@remnic/import-lossless-claw": "9.69.16",
|
|
106
|
+
"@remnic/import-gemini": "9.69.16",
|
|
107
|
+
"@remnic/import-claude": "9.69.16",
|
|
108
|
+
"@remnic/import-mem0": "9.69.16",
|
|
109
|
+
"@remnic/connector-limitless": "9.69.16",
|
|
110
|
+
"@remnic/connector-bee": "9.69.16",
|
|
111
|
+
"@remnic/import-supermemory": "9.69.16",
|
|
112
|
+
"@remnic/connector-omi": "9.69.16"
|
|
113
113
|
},
|
|
114
114
|
"license": "MIT",
|
|
115
115
|
"repository": {
|