@fizzyflow/wdoublesync_cli 1.0.7 → 1.0.8
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/README.md +29 -4
- package/SKILL.md +17 -3
- package/bin/wdoublesync.js +5 -2
- package/lib/WalrusSealClient.js +9 -3
- package/lib/commands/diff.js +114 -0
- package/lib/commands/index.js +1 -0
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ pnpm link --global
|
|
|
24
24
|
wdoublesync push [vector-id] [path] [options] Sync folder to a vector (creates one if no id given)
|
|
25
25
|
wdoublesync pull <vector-id> [path] [options] Restore vector contents to a folder
|
|
26
26
|
wdoublesync info [vector-id] [path] [options] Show chain/wallet info, or full vector metadata
|
|
27
|
+
wdoublesync diff <vector-id> [path] [options] List files added/modified/deleted locally vs the on-chain version
|
|
27
28
|
wdoublesync watch <vector-id> [path] [options] Watch folder: auto-push on changes, auto-pull on remote updates
|
|
28
29
|
wdoublesync rebate <vector-id> [path] [options] Burn archive patches and push folder as a fresh snapshot
|
|
29
30
|
```
|
|
@@ -37,7 +38,7 @@ The `[path]` argument is optional — if omitted, the current working directory
|
|
|
37
38
|
| `--chain <name>` | Chain: `mainnet`, `testnet`, `devnet`, `localnet` (default: `testnet`) |
|
|
38
39
|
| `--key <suiprivkey>` | Sui private key (or set `WDOUBLESYNC_KEY` env var) |
|
|
39
40
|
| `--phrase <mnemonic>` | Mnemonic phrase instead of a raw key |
|
|
40
|
-
| `--version <n>` | Version to restore (`pull`
|
|
41
|
+
| `--version <n>` | Version to restore or compare against (`pull`, `diff`; default: latest) |
|
|
41
42
|
| `--exclude <p1,p2>` | Extra exclude patterns (comma-separated) |
|
|
42
43
|
| `--no-compress` | Disable gzip compression |
|
|
43
44
|
| `--no-seal` | Create a new vector public (no Seal encryption). Only applies when creating; ignored for existing vectors |
|
|
@@ -57,7 +58,7 @@ Supply a signing key in one of three ways (checked in order):
|
|
|
57
58
|
2. `--phrase "word1 word2 ..."`
|
|
58
59
|
3. `WDOUBLESYNC_KEY` environment variable
|
|
59
60
|
|
|
60
|
-
`pull` and `
|
|
61
|
+
`pull`, `info` and `diff` work without a key for public (unencrypted) vectors. A key is required for Seal-encrypted vectors and for any `push`.
|
|
61
62
|
|
|
62
63
|
## Examples
|
|
63
64
|
|
|
@@ -113,6 +114,29 @@ wdoublesync info 0xabc123... --chain testnet
|
|
|
113
114
|
wdoublesync info 0xabc123... ~/my-project --chain testnet
|
|
114
115
|
```
|
|
115
116
|
|
|
117
|
+
### See what changed locally (diff)
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# list files added/modified/deleted locally compared to the latest on-chain version
|
|
121
|
+
wdoublesync diff 0xabc123... --chain testnet
|
|
122
|
+
wdoublesync diff 0xabc123... ~/my-project --chain testnet
|
|
123
|
+
|
|
124
|
+
# compare against a specific version
|
|
125
|
+
wdoublesync diff 0xabc123... --chain testnet --version 2
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Output shows the changes from the local folder's perspective — what a `push` would apply:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
diff: ~/my-project vs 0xabc123... (version 3 of 3)
|
|
132
|
+
local changes vs chain (what push would apply):
|
|
133
|
+
added: docs/new-page.md
|
|
134
|
+
modified: src/index.js
|
|
135
|
+
deleted: old-config.json
|
|
136
|
+
|
|
137
|
+
3 change(s): 1 added, 1 modified, 1 deleted
|
|
138
|
+
```
|
|
139
|
+
|
|
116
140
|
### Fast incremental pushes with a manifest
|
|
117
141
|
|
|
118
142
|
```bash
|
|
@@ -171,8 +195,9 @@ wdoublesync push 0xabc123... --chain testnet --key suiprivkey1... --force-snapsh
|
|
|
171
195
|
1. **push** — scans the current directory, computes a tree hash, and compares it against the last stored version. If changes are detected, a compressed diff (or full snapshot on the first push) is uploaded to Walrus and appended to the EndlessVector on-chain.
|
|
172
196
|
2. **pull** — reads the requested version from the EndlessVector, decrypts it if Seal-encrypted, and writes only changed files to disk. Files absent from the stored version are deleted.
|
|
173
197
|
3. **info** — reads EndlessVector metadata from the chain (version count, binary size, history) and checks whether the local folder matches any stored version.
|
|
174
|
-
4. **
|
|
175
|
-
5. **
|
|
198
|
+
4. **diff** — restores the requested version in memory (nothing is written to disk), hashes every file on both sides, and lists files added, modified, or deleted locally compared to the on-chain version.
|
|
199
|
+
5. **watch** — combines push and pull in a loop. A filesystem watcher triggers a debounced push on local changes. A poll interval checks the remote vector for new versions and pulls them if found. Push and pull never run concurrently.
|
|
200
|
+
6. **rebate** — burns all existing archive patches on-chain and pushes the current folder as a single fresh full snapshot, reducing Walrus storage costs after many incremental syncs.
|
|
176
201
|
|
|
177
202
|
### Default excludes
|
|
178
203
|
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wdoublesync
|
|
3
|
-
version: 0.1.
|
|
4
|
-
description:
|
|
3
|
+
version: 0.1.9
|
|
4
|
+
description: Agent skill for decentralized file storage, versioning, and sync on Sui + Walrus + Seal. Enables agents to push local folders as on-chain vectors, pull versions, manage Walrus storage, and watch for changes.
|
|
5
5
|
keywords: [walrus, sui, storage, versioning, sync, seal-encryption, blockchain, defi]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -97,6 +97,7 @@ wdoublesync watch 0x1234... ~/my-data --poll-interval 5
|
|
|
97
97
|
| **push** | `[vectorId]` `[path]` `[options]` | Sync folder to vector; creates new vector if no ID given |
|
|
98
98
|
| **pull** | `vectorId` `[path]` `[options]` | Restore vector (default: latest version) into folder |
|
|
99
99
|
| **info** | `[vectorId]` `[path]` | No ID: show chain + wallet. With ID: full vector details |
|
|
100
|
+
| **diff** | `vectorId` `[path]` `[options]` | List files added/modified/deleted locally vs the on-chain version (read-only, writes nothing) |
|
|
100
101
|
| **watch** | `vectorId` `[path]` `[options]` | Bi-directional sync: auto-push local changes, auto-pull remote updates |
|
|
101
102
|
| **rebate** | `vectorId` `[path]` | Burn archive patches, push folder as fresh single snapshot |
|
|
102
103
|
|
|
@@ -150,6 +151,19 @@ wdoublesync pull 0x1234... ~/restored
|
|
|
150
151
|
wdoublesync pull 0x1234... ~/restored --version 10
|
|
151
152
|
```
|
|
152
153
|
|
|
154
|
+
#### diff (preview local changes before pushing)
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
wdoublesync diff 0x1234... ~/my-data
|
|
158
|
+
# Output: local changes vs chain (what push would apply):
|
|
159
|
+
# added: docs/new-page.md
|
|
160
|
+
# modified: src/index.js
|
|
161
|
+
# deleted: old-config.json
|
|
162
|
+
# 3 change(s): 1 added, 1 modified, 1 deleted
|
|
163
|
+
# Compare against an older version with --version N
|
|
164
|
+
# Prints "no changes" when local matches the chain. Writes nothing to disk.
|
|
165
|
+
```
|
|
166
|
+
|
|
153
167
|
#### watch (bi-directional)
|
|
154
168
|
|
|
155
169
|
```bash
|
|
@@ -187,7 +201,7 @@ wdoublesync rebate 0x1234... ~/my-data
|
|
|
187
201
|
| `--no-seal` | — | — | Push: create a new vector public (no Seal encryption). Only applies when creating a vector; ignored for existing ones |
|
|
188
202
|
| `--manifest` | — | — | Push: write/use `.wdoublesync` file for faster change detection |
|
|
189
203
|
| `--force-snapshot` | — | — | Push: full snapshot (repairs corrupt vectors) |
|
|
190
|
-
| `--version` | Number | `latest` | Pull:
|
|
204
|
+
| `--version` | Number | `latest` | Pull/diff: use a specific version instead of latest |
|
|
191
205
|
| `--exclude` | Patterns (comma-separated) | — | Extra file patterns to exclude from sync |
|
|
192
206
|
| `--poll-interval` | Seconds | `2` | Watch: check for remote updates every N seconds |
|
|
193
207
|
| `--debounce` | Milliseconds | `1000` | Watch: quiet period before pushing after local change |
|
package/bin/wdoublesync.js
CHANGED
|
@@ -11,7 +11,7 @@ process.exit = (code) => {
|
|
|
11
11
|
throw err;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
import { push, pull, info, watch, rebate } from '../lib/commands/index.js';
|
|
14
|
+
import { push, pull, info, diff, watch, rebate } from '../lib/commands/index.js';
|
|
15
15
|
|
|
16
16
|
const USAGE = `wdoublesync — sync local folders to EndlessVector on Sui+Walrus
|
|
17
17
|
|
|
@@ -19,6 +19,7 @@ Usage:
|
|
|
19
19
|
wdoublesync push [vector-id] [options] Sync current folder to vector (creates if no id)
|
|
20
20
|
wdoublesync pull <vector-id> [options] Restore vector contents to current folder
|
|
21
21
|
wdoublesync info <vector-id> [options] Show vector metadata
|
|
22
|
+
wdoublesync diff <vector-id> [options] List local files added/modified/deleted vs the on-chain version
|
|
22
23
|
wdoublesync watch <vector-id> [options] Watch folder and auto-push/pull
|
|
23
24
|
wdoublesync rebate <vector-id> [options] Archive history, burn archives, push fresh snapshot
|
|
24
25
|
|
|
@@ -26,7 +27,7 @@ Options:
|
|
|
26
27
|
--chain <name> Chain: mainnet, testnet, devnet, localnet (default: testnet)
|
|
27
28
|
--key <suiprivkey> Sui private key (or set WDOUBLESYNC_KEY env var)
|
|
28
29
|
--phrase <mnemonic> Mnemonic phrase
|
|
29
|
-
--version <n> Version to restore (pull
|
|
30
|
+
--version <n> Version to restore/compare (pull, diff; default: latest)
|
|
30
31
|
--exclude <p1,p2> Extra exclude patterns (comma-separated)
|
|
31
32
|
--no-compress Disable gzip compression
|
|
32
33
|
--no-seal Create a new vector public (no Seal encryption); only applies on creation
|
|
@@ -138,6 +139,8 @@ try {
|
|
|
138
139
|
await pull(args);
|
|
139
140
|
} else if (args.command === 'info') {
|
|
140
141
|
await info(args);
|
|
142
|
+
} else if (args.command === 'diff') {
|
|
143
|
+
await diff(args);
|
|
141
144
|
} else if (args.command === 'watch') {
|
|
142
145
|
await watch(args);
|
|
143
146
|
} else if (args.command === 'rebate') {
|
package/lib/WalrusSealClient.js
CHANGED
|
@@ -4,6 +4,10 @@ import { SuiGrpcClient, GrpcWebFetchTransport } from '@mysten/sui/grpc';
|
|
|
4
4
|
import { Transaction } from '@mysten/sui/transactions';
|
|
5
5
|
import { fromHex } from '@mysten/sui/utils';
|
|
6
6
|
|
|
7
|
+
// Mysten's mainnet Seal committee key server requires an API key.
|
|
8
|
+
const MAINNET_SEAL_API_KEY_NAME = 'x-api-key';
|
|
9
|
+
const MAINNET_SEAL_API_KEY = 'c2f9b915-a4f3-41a2-bf91-99a630894db3';
|
|
10
|
+
|
|
7
11
|
class WalrusSealClient {
|
|
8
12
|
/** @type {SuiGrpcClient} */
|
|
9
13
|
suiClient;
|
|
@@ -156,10 +160,12 @@ class WalrusSealClient {
|
|
|
156
160
|
{ objectId: '0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8', weight: 1 },
|
|
157
161
|
],
|
|
158
162
|
mainnet: [
|
|
159
|
-
{
|
|
160
|
-
|
|
163
|
+
{
|
|
164
|
+
objectId: '0x686098f1439237fff9f36b99c7329683c22979d2005c2465cb891acb012a7595',
|
|
161
165
|
weight: 1,
|
|
162
|
-
aggregatorUrl:
|
|
166
|
+
aggregatorUrl: 'https://seal-aggregator-mainnet.mystenlabs.com',
|
|
167
|
+
apiKeyName: MAINNET_SEAL_API_KEY_NAME,
|
|
168
|
+
apiKey: MAINNET_SEAL_API_KEY,
|
|
163
169
|
},
|
|
164
170
|
],
|
|
165
171
|
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import EndlessVector from '@fizzyflow/endless-vector';
|
|
2
|
+
import { WDoubleSync } from '@fizzyflow/wdoublesync';
|
|
3
|
+
import { DoubleSyncFSFolder } from '../DoubleSyncFSFolder.js';
|
|
4
|
+
import {
|
|
5
|
+
makeClientForRead,
|
|
6
|
+
resolvePath,
|
|
7
|
+
makeExcludes,
|
|
8
|
+
formatPath,
|
|
9
|
+
hashLocalTree,
|
|
10
|
+
} from './shared.js';
|
|
11
|
+
|
|
12
|
+
export function compareFileMaps(localFiles, remoteFiles) {
|
|
13
|
+
const added = [];
|
|
14
|
+
const modified = [];
|
|
15
|
+
const deleted = [];
|
|
16
|
+
|
|
17
|
+
for (const path of Object.keys(localFiles)) {
|
|
18
|
+
if (!(path in remoteFiles)) {
|
|
19
|
+
added.push(path);
|
|
20
|
+
} else if (localFiles[path] !== remoteFiles[path]) {
|
|
21
|
+
modified.push(path);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
for (const path of Object.keys(remoteFiles)) {
|
|
25
|
+
if (!(path in localFiles)) {
|
|
26
|
+
deleted.push(path);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
added.sort();
|
|
31
|
+
modified.sort();
|
|
32
|
+
deleted.sort();
|
|
33
|
+
|
|
34
|
+
return { added, modified, deleted };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function diff(args) {
|
|
38
|
+
if (!args.vectorId) {
|
|
39
|
+
throw new Error('vector-id is required for diff');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const wsc = await makeClientForRead(args);
|
|
43
|
+
|
|
44
|
+
const evParams = {
|
|
45
|
+
suiClient: wsc.suiClient,
|
|
46
|
+
id: args.vectorId,
|
|
47
|
+
packageId: args.chain,
|
|
48
|
+
walrusClient: wsc.walrusClient,
|
|
49
|
+
aggregatorUrl: wsc.aggregatorUrl,
|
|
50
|
+
};
|
|
51
|
+
if (wsc.sealClient && wsc.suiMaster) {
|
|
52
|
+
evParams.sealClient = wsc.sealClient;
|
|
53
|
+
evParams.signAndExecuteTransaction = (tx) => wsc.signAndExecuteTransaction(tx);
|
|
54
|
+
evParams.senderAddress = wsc.suiMaster.address;
|
|
55
|
+
evParams.signer = wsc.suiMaster._keypair;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ev = new EndlessVector(evParams);
|
|
59
|
+
const wdsync = new WDoubleSync({ endlessVector: ev });
|
|
60
|
+
|
|
61
|
+
await ev.initialize();
|
|
62
|
+
const totalVersions = ev.length;
|
|
63
|
+
|
|
64
|
+
if (totalVersions === 0) {
|
|
65
|
+
console.log('vector', args.vectorId, 'has no versions yet — nothing to diff');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const version = args.version != null ? Number(args.version) : totalVersions;
|
|
70
|
+
if (!Number.isInteger(version) || version < 1 || version > totalVersions) {
|
|
71
|
+
throw new Error('version must be between 1 and ' + totalVersions);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const destPath = resolvePath(args.path);
|
|
75
|
+
console.log('diff:', formatPath(destPath), 'vs', args.vectorId, '(version ' + version + ' of ' + totalVersions + ')');
|
|
76
|
+
console.log(' restoring chain version...');
|
|
77
|
+
|
|
78
|
+
let remoteFolder;
|
|
79
|
+
try {
|
|
80
|
+
remoteFolder = await wdsync.restore(version === totalVersions ? undefined : version);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (err.message?.includes('sealClient not configured') || err.message?.includes('signer or sessionKey is required')) {
|
|
83
|
+
throw new Error('This vector is Seal-encrypted. Provide --key or --phrase to decrypt.');
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const remoteFiles = await hashLocalTree(remoteFolder);
|
|
89
|
+
const fsFolder = new DoubleSyncFSFolder(destPath, makeExcludes(args));
|
|
90
|
+
let localFiles = {};
|
|
91
|
+
try {
|
|
92
|
+
localFiles = await hashLocalTree(fsFolder);
|
|
93
|
+
} catch {
|
|
94
|
+
// local folder missing or unreadable — treat as empty
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { added, modified, deleted } = compareFileMaps(localFiles, remoteFiles);
|
|
98
|
+
const total = added.length + modified.length + deleted.length;
|
|
99
|
+
|
|
100
|
+
if (total === 0) {
|
|
101
|
+
console.log(' no changes — local folder matches version', version);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('\nlocal changes vs chain (what push would apply):');
|
|
106
|
+
for (const path of added) console.log(' added: ', path);
|
|
107
|
+
for (const path of modified) console.log(' modified: ', path);
|
|
108
|
+
for (const path of deleted) console.log(' deleted: ', path);
|
|
109
|
+
|
|
110
|
+
console.log('\n ' + total + ' change(s): ' +
|
|
111
|
+
added.length + ' added, ' +
|
|
112
|
+
modified.length + ' modified, ' +
|
|
113
|
+
deleted.length + ' deleted');
|
|
114
|
+
}
|
package/lib/commands/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fizzyflow/wdoublesync_cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tool built on the wdoublesync library to sync local folders to Walrus decentralised storage on the Sui network. Stores versioned gzip-compressed snapshots and diffs inside an EndlessVector on-chain object. Supports Seal encryption, manifest-based fast change detection, and full version history with point-in-time restore.",
|
|
6
6
|
"bin": {
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
14
|
+
"pushnew:test": "cd test_pull && node ../bin/wdoublesync.js push --chain mainnet",
|
|
15
|
+
"pull:test": "mkdir -p test_pull && cd test_pull && node ../bin/wdoublesync.js pull 0x77f779ed40404203406f1854bb7dd183fbc4743d28beb056057823c6b2b565c0 --chain mainnet",
|
|
16
|
+
"push:test": "cd test_pull && node ../bin/wdoublesync.js push 0x77f779ed40404203406f1854bb7dd183fbc4743d28beb056057823c6b2b565c0 --chain mainnet",
|
|
17
|
+
"pushfull:test": "cd test_pull && node ../bin/wdoublesync.js push 0x77f779ed40404203406f1854bb7dd183fbc4743d28beb056057823c6b2b565c0 --chain mainnet --force-snapshot",
|
|
18
|
+
"info:test": "cd test_pull && node ../bin/wdoublesync.js info 0x77f779ed40404203406f1854bb7dd183fbc4743d28beb056057823c6b2b565c0 --chain mainnet",
|
|
19
|
+
"diff:test": "cd test_pull && node ../bin/wdoublesync.js diff 0x77f779ed40404203406f1854bb7dd183fbc4743d28beb056057823c6b2b565c0 --chain mainnet",
|
|
20
|
+
"watch:test": "cd test_pull && node ../bin/wdoublesync.js watch 0x77f779ed40404203406f1854bb7dd183fbc4743d28beb056057823c6b2b565c0 --chain mainnet"
|
|
19
21
|
},
|
|
20
22
|
"keywords": [],
|
|
21
23
|
"author": "suidouble (https://github.com/suidouble)",
|