@storacha/clawracha 0.0.1 → 0.0.2
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/handlers/apply.d.ts +18 -0
- package/dist/handlers/apply.d.ts.map +1 -0
- package/dist/handlers/apply.js +50 -0
- package/dist/handlers/process.d.ts +15 -0
- package/dist/handlers/process.d.ts.map +1 -0
- package/dist/handlers/process.js +82 -0
- package/dist/handlers/remote.d.ts +15 -0
- package/dist/handlers/remote.d.ts.map +1 -0
- package/dist/handlers/remote.js +48 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/mdsync/index.d.ts +45 -0
- package/dist/mdsync/index.d.ts.map +1 -0
- package/dist/mdsync/index.js +365 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +224 -26
- package/dist/sync.d.ts +12 -5
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +82 -157
- package/dist/types/index.d.ts +58 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/client.d.ts +14 -0
- package/dist/utils/client.d.ts.map +1 -0
- package/dist/utils/client.js +38 -0
- package/dist/utils/differ.d.ts +29 -0
- package/dist/utils/differ.d.ts.map +1 -0
- package/dist/utils/differ.js +47 -0
- package/dist/utils/encoder.d.ts +16 -0
- package/dist/utils/encoder.d.ts.map +1 -0
- package/dist/utils/encoder.js +51 -0
- package/dist/utils/tempcar.d.ts +22 -0
- package/dist/utils/tempcar.d.ts.map +1 -0
- package/dist/utils/tempcar.js +44 -0
- package/dist/watcher.d.ts +1 -1
- package/dist/watcher.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from "node:fs/promises";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
+
import { extract as extractDelegation } from "@storacha/client/delegation";
|
|
11
12
|
import { SyncEngine } from "./sync.js";
|
|
12
13
|
import { FileWatcher } from "./watcher.js";
|
|
14
|
+
import { createStorachaClient } from "./utils/client.js";
|
|
13
15
|
// Global state
|
|
14
16
|
let syncEngine = null;
|
|
15
17
|
let fileWatcher = null;
|
|
@@ -53,11 +55,12 @@ export default function plugin(api) {
|
|
|
53
55
|
return;
|
|
54
56
|
}
|
|
55
57
|
const deviceConfig = await loadDeviceConfig(workspace);
|
|
56
|
-
if (!deviceConfig) {
|
|
57
|
-
ctx.logger.info("
|
|
58
|
+
if (!deviceConfig || !deviceConfig.setupComplete) {
|
|
59
|
+
ctx.logger.info("Setup not complete. Run /storacha-init or /storacha-join first.");
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
60
|
-
|
|
62
|
+
const storachaClient = await createStorachaClient(deviceConfig);
|
|
63
|
+
syncEngine = new SyncEngine(storachaClient, workspace);
|
|
61
64
|
await syncEngine.init(deviceConfig);
|
|
62
65
|
fileWatcher = new FileWatcher({
|
|
63
66
|
workspace,
|
|
@@ -105,7 +108,7 @@ export default function plugin(api) {
|
|
|
105
108
|
content: [
|
|
106
109
|
{
|
|
107
110
|
type: "text",
|
|
108
|
-
text: "Sync not initialized. Run /storacha-init first.",
|
|
111
|
+
text: "Sync not initialized. Run /storacha-init or /storacha-join first.",
|
|
109
112
|
},
|
|
110
113
|
],
|
|
111
114
|
details: null,
|
|
@@ -131,7 +134,7 @@ export default function plugin(api) {
|
|
|
131
134
|
content: [
|
|
132
135
|
{
|
|
133
136
|
type: "text",
|
|
134
|
-
text: "Sync not initialized. Run /storacha-init first.",
|
|
137
|
+
text: "Sync not initialized. Run /storacha-init or /storacha-join first.",
|
|
135
138
|
},
|
|
136
139
|
],
|
|
137
140
|
details: null,
|
|
@@ -150,65 +153,260 @@ export default function plugin(api) {
|
|
|
150
153
|
};
|
|
151
154
|
},
|
|
152
155
|
});
|
|
153
|
-
//
|
|
156
|
+
// --- Slash Commands ---
|
|
154
157
|
api.registerCommand({
|
|
155
158
|
name: "storacha-init",
|
|
156
|
-
description: "Initialize Storacha
|
|
157
|
-
|
|
159
|
+
description: "Initialize a NEW Storacha workspace (first device). Usage: /storacha-init <upload-delegation-b64>",
|
|
160
|
+
acceptsArgs: true,
|
|
161
|
+
handler: async (_ctx) => {
|
|
158
162
|
const workspace = workspaceDir;
|
|
159
163
|
if (!workspace)
|
|
160
164
|
return { text: "No workspace configured." };
|
|
165
|
+
const b64 = _ctx.args?.trim();
|
|
166
|
+
if (!b64) {
|
|
167
|
+
return {
|
|
168
|
+
text: [
|
|
169
|
+
"Usage: `/storacha-init <upload-delegation-b64>`",
|
|
170
|
+
"",
|
|
171
|
+
"This creates a **new** workspace. If you're syncing from an existing device, use `/storacha-join` instead.",
|
|
172
|
+
].join("\n"),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Validate delegation
|
|
176
|
+
const bytes = Buffer.from(b64, "base64");
|
|
177
|
+
const { ok: delegation, error } = await extractDelegation(bytes);
|
|
178
|
+
if (!delegation) {
|
|
179
|
+
return { text: `Invalid delegation: ${error}` };
|
|
180
|
+
}
|
|
161
181
|
const { Agent } = await import("@storacha/ucn/pail");
|
|
162
182
|
const agent = await Agent.generate();
|
|
163
183
|
const agentKey = Agent.format(agent);
|
|
164
|
-
const
|
|
184
|
+
const spaceDID = delegation.capabilities[0]?.with;
|
|
185
|
+
const config = {
|
|
186
|
+
agentKey,
|
|
187
|
+
uploadDelegation: b64,
|
|
188
|
+
spaceDID: spaceDID ?? undefined,
|
|
189
|
+
setupComplete: true,
|
|
190
|
+
};
|
|
165
191
|
await saveDeviceConfig(workspace, config);
|
|
166
192
|
return {
|
|
167
193
|
text: [
|
|
168
|
-
"
|
|
194
|
+
"\u{1f525} Storacha workspace initialized!",
|
|
169
195
|
`Agent DID: \`${agent.did()}\``,
|
|
196
|
+
`Space: \`${spaceDID ?? "unknown"}\``,
|
|
170
197
|
"",
|
|
171
|
-
"
|
|
198
|
+
"Restart the gateway to start syncing.",
|
|
199
|
+
"",
|
|
200
|
+
"To add another device, run `/storacha-grant <their-agent-DID>` here,",
|
|
201
|
+
"then `/storacha-join <upload-b64> <name-b64>` on the other device.",
|
|
172
202
|
].join("\n"),
|
|
173
203
|
};
|
|
174
204
|
},
|
|
175
205
|
});
|
|
176
206
|
api.registerCommand({
|
|
177
|
-
name: "storacha-
|
|
178
|
-
description: "
|
|
207
|
+
name: "storacha-join",
|
|
208
|
+
description: "Join an existing Storacha workspace from another device. Usage: /storacha-join <upload-delegation-b64> <name-delegation-b64>",
|
|
179
209
|
acceptsArgs: true,
|
|
180
|
-
handler: async (
|
|
210
|
+
handler: async (_ctx) => {
|
|
181
211
|
const workspace = workspaceDir;
|
|
182
212
|
if (!workspace)
|
|
183
213
|
return { text: "No workspace configured." };
|
|
184
|
-
const
|
|
185
|
-
if (!
|
|
186
|
-
return {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
214
|
+
const args = _ctx.args?.trim();
|
|
215
|
+
if (!args) {
|
|
216
|
+
return {
|
|
217
|
+
text: [
|
|
218
|
+
"Usage: `/storacha-join <upload-delegation-b64> <name-delegation-b64>`",
|
|
219
|
+
"",
|
|
220
|
+
"Get both delegations by running `/storacha-grant` on the existing device.",
|
|
221
|
+
"If you're setting up a **new** workspace, use `/storacha-init` instead.",
|
|
222
|
+
].join("\n"),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const spaceIdx = args.indexOf(" ");
|
|
226
|
+
if (spaceIdx === -1) {
|
|
227
|
+
return {
|
|
228
|
+
text: "Two arguments required: `/storacha-join <upload-b64> <name-b64>`",
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const uploadB64 = args.slice(0, spaceIdx).trim();
|
|
232
|
+
const nameB64 = args.slice(spaceIdx + 1).trim();
|
|
233
|
+
if (!uploadB64 || !nameB64) {
|
|
234
|
+
return {
|
|
235
|
+
text: "Two arguments required: `/storacha-join <upload-b64> <name-b64>`",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
// Validate upload delegation
|
|
239
|
+
const uploadBytes = Buffer.from(uploadB64, "base64");
|
|
240
|
+
const { ok: uploadDelegation, error: uploadErr } = await extractDelegation(uploadBytes);
|
|
241
|
+
if (!uploadDelegation) {
|
|
242
|
+
return { text: `Invalid upload delegation: ${uploadErr}` };
|
|
243
|
+
}
|
|
244
|
+
// Validate name delegation
|
|
245
|
+
const nameBytes = Buffer.from(nameB64, "base64");
|
|
246
|
+
const { ok: nameDelegation, error: nameErr } = await extractDelegation(nameBytes);
|
|
247
|
+
if (!nameDelegation) {
|
|
248
|
+
return { text: `Invalid name delegation: ${nameErr}` };
|
|
249
|
+
}
|
|
250
|
+
const { Agent } = await import("@storacha/ucn/pail");
|
|
251
|
+
const agent = await Agent.generate();
|
|
252
|
+
const agentKey = Agent.format(agent);
|
|
253
|
+
const spaceDID = uploadDelegation.capabilities[0]?.with;
|
|
254
|
+
const config = {
|
|
255
|
+
agentKey,
|
|
256
|
+
uploadDelegation: uploadB64,
|
|
257
|
+
nameDelegation: nameB64,
|
|
258
|
+
spaceDID: spaceDID ?? undefined,
|
|
259
|
+
setupComplete: true,
|
|
260
|
+
};
|
|
191
261
|
await saveDeviceConfig(workspace, config);
|
|
262
|
+
// Pull remote state immediately before watcher starts
|
|
263
|
+
let pullCount = 0;
|
|
264
|
+
try {
|
|
265
|
+
const storachaClient = await createStorachaClient(config);
|
|
266
|
+
const engine = new SyncEngine(storachaClient, workspace);
|
|
267
|
+
await engine.init(config);
|
|
268
|
+
pullCount = await engine.pullRemote();
|
|
269
|
+
// Save name archive after pull
|
|
270
|
+
const nameArchive = await engine.exportNameArchive();
|
|
271
|
+
config.nameArchive = nameArchive;
|
|
272
|
+
await saveDeviceConfig(workspace, config);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
return {
|
|
276
|
+
text: [
|
|
277
|
+
"\u26a0\ufe0f Delegations saved but initial pull failed:",
|
|
278
|
+
`\`${err.message}\``,
|
|
279
|
+
"",
|
|
280
|
+
"Restart the gateway to retry.",
|
|
281
|
+
].join("\n"),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
192
284
|
return {
|
|
193
|
-
text:
|
|
285
|
+
text: [
|
|
286
|
+
"\u{1f525} Joined existing Storacha workspace!",
|
|
287
|
+
`Agent DID: \`${agent.did()}\``,
|
|
288
|
+
`Space: \`${spaceDID ?? "unknown"}\``,
|
|
289
|
+
`Pulled ${pullCount} files from remote.`,
|
|
290
|
+
"",
|
|
291
|
+
"Restart the gateway to start syncing.",
|
|
292
|
+
].join("\n"),
|
|
293
|
+
};
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
api.registerCommand({
|
|
297
|
+
name: "storacha-grant",
|
|
298
|
+
description: "Grant another device access. Usage: /storacha-grant <target-DID>",
|
|
299
|
+
acceptsArgs: true,
|
|
300
|
+
handler: async (_ctx) => {
|
|
301
|
+
const workspace = workspaceDir;
|
|
302
|
+
if (!workspace)
|
|
303
|
+
return { text: "No workspace configured." };
|
|
304
|
+
const targetDID = _ctx.args?.trim();
|
|
305
|
+
if (!targetDID || !targetDID.startsWith("did:")) {
|
|
306
|
+
return { text: "Usage: `/storacha-grant <did:key:z...>`" };
|
|
307
|
+
}
|
|
308
|
+
const config = await loadDeviceConfig(workspace);
|
|
309
|
+
if (!config) {
|
|
310
|
+
return {
|
|
311
|
+
text: "Not initialized. Run `/storacha-init` or `/storacha-join` first.",
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const results = [];
|
|
315
|
+
// Re-delegate upload capability
|
|
316
|
+
if (config.uploadDelegation) {
|
|
317
|
+
try {
|
|
318
|
+
const storachaClient = await createStorachaClient(config);
|
|
319
|
+
const audience = { did: () => targetDID };
|
|
320
|
+
const uploadDelegation = await storachaClient.createDelegation(audience, [
|
|
321
|
+
"space/blob/add",
|
|
322
|
+
"space/index/add",
|
|
323
|
+
"upload/add",
|
|
324
|
+
"filecoin/offer",
|
|
325
|
+
]);
|
|
326
|
+
const { ok: archiveBytes } = await uploadDelegation.archive();
|
|
327
|
+
if (archiveBytes) {
|
|
328
|
+
const b64 = Buffer.from(archiveBytes).toString("base64");
|
|
329
|
+
results.push("**Upload delegation:**\n```\n" + b64 + "\n```");
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
results.push(`\u274c Failed to create upload delegation: ${err.message}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
results.push("\u26a0\ufe0f No upload delegation to re-delegate.");
|
|
338
|
+
}
|
|
339
|
+
// Re-delegate name (pail sync) capability
|
|
340
|
+
if (config.nameDelegation) {
|
|
341
|
+
try {
|
|
342
|
+
const { Agent, Name } = await import("@storacha/ucn/pail");
|
|
343
|
+
const agent = Agent.parse(config.agentKey);
|
|
344
|
+
let name;
|
|
345
|
+
if (config.nameArchive) {
|
|
346
|
+
const archiveBytes = Buffer.from(config.nameArchive, "base64");
|
|
347
|
+
name = await Name.extract(agent, archiveBytes);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
const nameBytes = Buffer.from(config.nameDelegation, "base64");
|
|
351
|
+
const { ok: nameDel } = await extractDelegation(nameBytes);
|
|
352
|
+
if (!nameDel) {
|
|
353
|
+
results.push("\u274c Failed to extract name delegation.");
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
name = Name.from(agent, [nameDel]);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (name) {
|
|
360
|
+
const nameDel = await name.grant(targetDID);
|
|
361
|
+
const { ok: archiveBytes } = await nameDel.archive();
|
|
362
|
+
if (archiveBytes) {
|
|
363
|
+
const b64 = Buffer.from(archiveBytes).toString("base64");
|
|
364
|
+
results.push("**Name delegation:**\n```\n" + b64 + "\n```");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
results.push(`\u274c Failed to create name delegation: ${err.message}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
results.push("\u26a0\ufe0f No name delegation to re-delegate.");
|
|
374
|
+
}
|
|
375
|
+
if (results.length === 0) {
|
|
376
|
+
return { text: "Nothing to grant. Set up this device first." };
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
text: [
|
|
380
|
+
`\u{1f525} Delegations for \`${targetDID}\`:`,
|
|
381
|
+
"",
|
|
382
|
+
...results,
|
|
383
|
+
"",
|
|
384
|
+
"The target device should run:",
|
|
385
|
+
"`/storacha-join <upload-b64> <name-b64>`",
|
|
386
|
+
].join("\n"),
|
|
194
387
|
};
|
|
195
388
|
},
|
|
196
389
|
});
|
|
197
390
|
api.registerCommand({
|
|
198
391
|
name: "storacha-status",
|
|
199
392
|
description: "Show Storacha sync status",
|
|
200
|
-
handler: async (
|
|
393
|
+
handler: async (_ctx) => {
|
|
201
394
|
const workspace = workspaceDir;
|
|
202
395
|
if (!workspace)
|
|
203
396
|
return { text: "No workspace configured." };
|
|
204
397
|
const config = await loadDeviceConfig(workspace);
|
|
205
398
|
if (!config)
|
|
206
|
-
return {
|
|
399
|
+
return {
|
|
400
|
+
text: "Not initialized. Run /storacha-init or /storacha-join first.",
|
|
401
|
+
};
|
|
207
402
|
const lines = [
|
|
208
|
-
"
|
|
403
|
+
"\u{1f525} Storacha Sync Status",
|
|
209
404
|
`Agent: configured`,
|
|
210
|
-
`
|
|
405
|
+
`Upload delegation: ${config.uploadDelegation ? "\u2705" : "\u274c not set"}`,
|
|
406
|
+
`Name delegation: ${config.nameDelegation ? "\u2705" : "\u274c not set"}`,
|
|
407
|
+
`Space DID: ${config.spaceDID ?? "unknown"}`,
|
|
211
408
|
`Name Archive: ${config.nameArchive ? "saved" : "not created"}`,
|
|
409
|
+
`Setup complete: ${config.setupComplete ? "\u2705" : "\u274c"}`,
|
|
212
410
|
];
|
|
213
411
|
if (syncEngine) {
|
|
214
412
|
const status = await syncEngine.status();
|
package/dist/sync.d.ts
CHANGED
|
@@ -8,18 +8,20 @@
|
|
|
8
8
|
* 5. Upload all blocks as CAR
|
|
9
9
|
* 6. Apply remote changes to local filesystem
|
|
10
10
|
*/
|
|
11
|
-
import type { SyncState, FileChange, DeviceConfig } from "./types.js";
|
|
12
|
-
import { type PailEntries } from "./differ.js";
|
|
11
|
+
import type { SyncState, FileChange, DeviceConfig } from "./types/index.js";
|
|
12
|
+
import { type PailEntries } from "./utils/differ.js";
|
|
13
|
+
import { Client } from "@storacha/client";
|
|
13
14
|
export declare class SyncEngine {
|
|
14
15
|
private workspace;
|
|
15
16
|
private blocks;
|
|
16
17
|
private name;
|
|
17
18
|
private current;
|
|
18
19
|
private pendingOps;
|
|
19
|
-
private
|
|
20
|
+
private carFile;
|
|
20
21
|
private running;
|
|
21
22
|
private lastSync;
|
|
22
|
-
|
|
23
|
+
private storachaClient;
|
|
24
|
+
constructor(storachaClient: Client, workspace: string);
|
|
23
25
|
/**
|
|
24
26
|
* Initialize sync engine with device config
|
|
25
27
|
*/
|
|
@@ -35,7 +37,7 @@ export declare class SyncEngine {
|
|
|
35
37
|
/**
|
|
36
38
|
* Create CAR and upload to Storacha
|
|
37
39
|
*/
|
|
38
|
-
private
|
|
40
|
+
private possiblyUploadCAR;
|
|
39
41
|
/**
|
|
40
42
|
* Get current pail entries as map
|
|
41
43
|
*/
|
|
@@ -44,6 +46,11 @@ export declare class SyncEngine {
|
|
|
44
46
|
* Apply remote changes to local filesystem
|
|
45
47
|
*/
|
|
46
48
|
private applyRemoteChanges;
|
|
49
|
+
/**
|
|
50
|
+
* Pull all remote state and write to local filesystem.
|
|
51
|
+
* Used by /storacha-join to overwrite local with remote before watcher starts.
|
|
52
|
+
*/
|
|
53
|
+
pullRemote(): Promise<number>;
|
|
47
54
|
status(): Promise<SyncState>;
|
|
48
55
|
exportNameArchive(): Promise<string>;
|
|
49
56
|
private storeBlocks;
|
package/dist/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EAEV,YAAY,EACb,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,cAAc,CAAS;gBAEnB,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAMrD;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAgC/C;;OAEG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAe1D;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD3B;;OAEG;YACW,iBAAiB;IAc/B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAc5C;;OAEG;YACW,kBAAkB;IAWhC;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IA0B7B,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAW5B,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;YAM5B,WAAW;CAK1B"}
|