@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 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` only, default: latest) |
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 `info` work without a key for public (unencrypted) vectors. A key is required for Seal-encrypted vectors and for any `push`.
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. **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.
175
- 5. **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.
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.8
4
- description: Claude 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.
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: restore specific version instead of latest |
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 |
@@ -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 only, default: latest)
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') {
@@ -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
- objectId: '0x686098f1439237fff9f36b99c7329683c22979d2005c2465cb891acb012a7595',
163
+ {
164
+ objectId: '0x686098f1439237fff9f36b99c7329683c22979d2005c2465cb891acb012a7595',
161
165
  weight: 1,
162
- aggregatorUrl: "https://seal-aggregator-mainnet.mystenlabs.com",
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
+ }
@@ -1,5 +1,6 @@
1
1
  export { push } from './push.js';
2
2
  export { pull } from './pull.js';
3
3
  export { info } from './info.js';
4
+ export { diff } from './diff.js';
4
5
  export { watch } from './watch.js';
5
6
  export { rebate } from './rebate.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fizzyflow/wdoublesync_cli",
3
- "version": "1.0.7",
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
- "pull:test": "mkdir -p test_pull && cd test_pull && node ../bin/wdoublesync.js pull 0x69d7d77c55d66c4c6d0a87d120caf0535fdbb3c8c414658f81f66a4d50c80120 --chain testnet",
15
- "push:test": "cd test_pull && node ../bin/wdoublesync.js push 0x69d7d77c55d66c4c6d0a87d120caf0535fdbb3c8c414658f81f66a4d50c80120 --chain testnet",
16
- "pushfull:test": "cd test_pull && node ../bin/wdoublesync.js push 0x69d7d77c55d66c4c6d0a87d120caf0535fdbb3c8c414658f81f66a4d50c80120 --chain testnet --force-snapshot",
17
- "info:test": "cd test_pull && node ../bin/wdoublesync.js info 0x69d7d77c55d66c4c6d0a87d120caf0535fdbb3c8c414658f81f66a4d50c80120 --chain testnet",
18
- "watch:test": "cd test_pull && node ../bin/wdoublesync.js watch 0x69d7d77c55d66c4c6d0a87d120caf0535fdbb3c8c414658f81f66a4d50c80120 --chain testnet"
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)",