@storacha/clawracha 0.2.2 → 0.3.0

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/plugin.js CHANGED
@@ -7,15 +7,11 @@
7
7
  * - Agent tools for manual sync control
8
8
  */
9
9
  import { json as consumeJson } from "stream/consumers";
10
+ import * as fs from "node:fs/promises";
10
11
  import { SyncEngine } from "./sync.js";
11
- import { createStorachaClient } from "./utils/client.js";
12
- import { decodeDelegation, encodeDelegation, } from "./utils/delegation.js";
13
12
  import { resolveAgentWorkspace, getAgentIds } from "./utils/workspace.js";
14
- import { Agent, Name } from "@storacha/ucn/pail";
15
- import { extract } from "@storacha/client/delegation";
16
13
  import * as z from "zod";
17
- import { spaceAccess } from "@storacha/client/capability/access";
18
- import { loadDeviceConfig, startWorkspaceSync, doInit, doSetup, doJoin, } from "./commands.js";
14
+ import { loadDeviceConfig, startWorkspaceSync, doInit, doSetup, doJoin, doGrant, } from "./commands.js";
19
15
  const activeSyncers = new Map();
20
16
  // --- Config helpers ---
21
17
  const UpdateParams = z.object({
@@ -49,8 +45,6 @@ export default function plugin(api) {
49
45
  const updateParams = paramsResult.data;
50
46
  const sync = activeSyncers.get(updateParams.workspace);
51
47
  if (sync) {
52
- // stop active sync engine if present
53
- // waiting for any active syncs to flush.
54
48
  await sync.watcher.stop();
55
49
  await sync.watcher.forceFlush();
56
50
  await sync.engine.stop();
@@ -74,8 +68,7 @@ export default function plugin(api) {
74
68
  for (const agentId of agentIds) {
75
69
  const workspace = resolveAgentWorkspace(ctx.config, agentId);
76
70
  try {
77
- const sync = await startWorkspaceSync(workspace, agentId, pluginConfig, false, // Don't emit "add" events for existing files on startup (handled by initial scan)
78
- ctx.logger);
71
+ const sync = await startWorkspaceSync(workspace, agentId, pluginConfig, false, ctx.logger);
79
72
  if (sync) {
80
73
  activeSyncers.set(workspace, sync);
81
74
  }
@@ -101,7 +94,7 @@ export default function plugin(api) {
101
94
  activeSyncers.clear();
102
95
  },
103
96
  });
104
- // --- Agent tools (keyed by workspace dir) ---
97
+ // --- Agent tools ---
105
98
  api.registerTool({
106
99
  name: "storacha_sync_status",
107
100
  label: "Storacha Sync Status",
@@ -167,7 +160,6 @@ export default function plugin(api) {
167
160
  const clawracha = program
168
161
  .command("clawracha")
169
162
  .description("Storacha workspace sync commands");
170
- // Helper to resolve workspace from --agent
171
163
  function requireAgent(agentId) {
172
164
  if (!agentId) {
173
165
  console.error("Error: --agent <id> is required. Specify which agent workspace to configure.");
@@ -195,16 +187,16 @@ export default function plugin(api) {
195
187
  }
196
188
  else {
197
189
  console.log("\nNext step — choose one:");
198
- console.log(` New workspace: openclaw clawracha setup <delegation> --agent ${agentId}`);
199
- console.log(` Join existing: openclaw clawracha join <upload> <name> --agent ${agentId}`);
190
+ console.log(` New workspace: openclaw clawracha setup --agent ${agentId}`);
191
+ console.log(` Join existing: openclaw clawracha join <bundle> --agent ${agentId}`);
200
192
  }
201
193
  return;
202
194
  }
203
195
  console.log(`🔥 Agent initialized for ${agentId}!`);
204
196
  console.log(`Agent DID: ${result.agentDID}`);
205
197
  console.log("\nNext step — choose one:");
206
- console.log(` New workspace: openclaw clawracha setup <delegation> --agent ${agentId}`);
207
- console.log(` Join existing: openclaw clawracha join <upload> <name> --agent ${agentId}`);
198
+ console.log(` New workspace: openclaw clawracha setup --agent ${agentId}`);
199
+ console.log(` Join existing: openclaw clawracha join <bundle> --agent ${agentId}`);
208
200
  }
209
201
  catch (err) {
210
202
  console.error(`Error: ${err.message}`);
@@ -213,12 +205,12 @@ export default function plugin(api) {
213
205
  process.exit(1);
214
206
  }
215
207
  });
216
- // --- setup ---
208
+ // --- setup (login-based only) ---
217
209
  clawracha
218
- .command("setup <delegation>")
219
- .description("Set up a NEW workspace (first device). <delegation> is a file path or base64 CID string.")
210
+ .command("setup")
211
+ .description("Set up a NEW workspace via Storacha login (creates space, generates delegations)")
220
212
  .requiredOption("--agent <id>", "Agent ID")
221
- .action(async (delegationArg, opts) => {
213
+ .action(async (opts) => {
222
214
  try {
223
215
  const { agentId, workspace } = requireAgent(opts.agent);
224
216
  const deviceConfig = await loadDeviceConfig(workspace);
@@ -226,12 +218,16 @@ export default function plugin(api) {
226
218
  console.error(`Run \`openclaw clawracha init --agent ${agentId}\` first.`);
227
219
  process.exit(1);
228
220
  }
229
- const result = await doSetup(workspace, agentId, delegationArg, pluginConfig, config.gateway);
230
- console.log(`🔥 Storacha workspace ready for ${agentId}!`);
221
+ const { prompt } = await import("./prompts.js");
222
+ const email = await prompt("Storacha email: ");
223
+ const spaceName = await prompt("Space name: ");
224
+ console.log("\n⏳ Setting up workspace...");
225
+ const result = await doSetup(workspace, agentId, email, spaceName, pluginConfig, config.gateway);
226
+ console.log(`\n🔥 Storacha workspace ready for ${agentId}!`);
231
227
  console.log(`Agent DID: ${result.agentDID}`);
232
228
  console.log(`Space: ${result.spaceDID ?? "unknown"}`);
233
229
  console.log(`\nTo add another device, run \`openclaw clawracha grant <their-DID> --agent ${agentId}\` here,`);
234
- console.log(`then \`openclaw clawracha join <upload> <name> --agent <id>\` on the other device.`);
230
+ console.log(`then \`openclaw clawracha join <bundle> --agent <id>\` on the other device.`);
235
231
  console.log("\nSync is now active (no gateway restart needed).");
236
232
  }
237
233
  catch (err) {
@@ -243,12 +239,12 @@ export default function plugin(api) {
243
239
  process.exit(1);
244
240
  }
245
241
  });
246
- // --- join ---
242
+ // --- join (single bundle arg) ---
247
243
  clawracha
248
- .command("join <upload-delegation> <name-delegation>")
249
- .description("Join an existing workspace from another device. Arguments are file paths or base64 CID strings.")
244
+ .command("join <bundle>")
245
+ .description("Join an existing workspace from another device. Bundle is a file path or base64 string from `grant`.")
250
246
  .requiredOption("--agent <id>", "Agent ID")
251
- .action(async (uploadArg, nameArg, opts) => {
247
+ .action(async (bundleArg, opts) => {
252
248
  try {
253
249
  const { agentId, workspace } = requireAgent(opts.agent);
254
250
  const deviceConfig = await loadDeviceConfig(workspace);
@@ -256,8 +252,9 @@ export default function plugin(api) {
256
252
  console.error(`Run \`openclaw clawracha init --agent ${agentId}\` first.`);
257
253
  process.exit(1);
258
254
  }
259
- const result = await doJoin(workspace, agentId, uploadArg, nameArg, pluginConfig, config.gateway);
260
- console.log(`🔥 Joined existing Storacha workspace for ${agentId}!`);
255
+ console.log("⏳ Joining workspace...");
256
+ const result = await doJoin(workspace, agentId, bundleArg, pluginConfig, config.gateway);
257
+ console.log(`\n🔥 Joined Storacha workspace for ${agentId}!`);
261
258
  console.log(`Agent DID: ${result.agentDID}`);
262
259
  console.log(`Space: ${result.spaceDID ?? "unknown"}`);
263
260
  console.log(`Pulled ${result.pullCount} files from remote.`);
@@ -272,11 +269,12 @@ export default function plugin(api) {
272
269
  process.exit(1);
273
270
  }
274
271
  });
275
- // --- grant ---
272
+ // --- grant (produces bundle) ---
276
273
  clawracha
277
274
  .command("grant <target-DID>")
278
- .description("Grant another device access to this workspace")
275
+ .description("Grant another device access to this workspace (outputs a delegation bundle)")
279
276
  .requiredOption("--agent <id>", "Agent ID")
277
+ .option("-o, --output <file>", "Write bundle to file instead of base64 stdout")
280
278
  .action(async (targetDID, opts) => {
281
279
  try {
282
280
  const { agentId, workspace } = requireAgent(opts.agent);
@@ -284,60 +282,19 @@ export default function plugin(api) {
284
282
  console.error("Error: target must be a DID (did:key:z...)");
285
283
  process.exit(1);
286
284
  }
287
- const deviceConfig = await loadDeviceConfig(workspace);
288
- if (!deviceConfig) {
289
- console.error(`Not initialized. Run \`openclaw clawracha init --agent ${agentId}\` first.`);
290
- process.exit(1);
291
- }
292
- const results = [];
293
- // Re-delegate upload capability
294
- if (deviceConfig.uploadDelegation) {
295
- const storachaClient = await createStorachaClient(deviceConfig);
296
- const audience = {
297
- did: () => targetDID,
298
- };
299
- const uploadDel = await storachaClient.createDelegation(audience,
300
- // @ts-expect-error createDelegation should validate abilities
301
- Object.keys(spaceAccess), { expiration: Infinity });
302
- const { ok: archiveBytes } = await uploadDel.archive();
303
- if (archiveBytes) {
304
- results.push(`Upload delegation:\n${encodeDelegation(archiveBytes)}`);
305
- }
306
- }
307
- else {
308
- results.push("⚠️ No upload delegation to re-delegate.");
309
- }
310
- // Re-delegate name capability
311
- const agent = Agent.parse(deviceConfig.agentKey);
312
- let name;
313
- if (deviceConfig.nameArchive) {
314
- const archiveBytes = decodeDelegation(deviceConfig.nameArchive);
315
- name = await Name.extract(agent, archiveBytes);
316
- }
317
- else if (deviceConfig.nameDelegation) {
318
- const nameBytes = decodeDelegation(deviceConfig.nameDelegation);
319
- const { ok: nameDel } = await extract(nameBytes);
320
- if (nameDel) {
321
- name = Name.from(agent, [nameDel]);
322
- }
323
- }
324
- if (name) {
325
- const nameDel = await name.grant(targetDID);
326
- const { ok: archiveBytes } = await nameDel.archive();
327
- if (archiveBytes) {
328
- results.push(`Name delegation:\n${encodeDelegation(archiveBytes)}`);
329
- }
285
+ console.log("⏳ Creating delegation bundle...");
286
+ const bundleBytes = await doGrant(workspace, targetDID);
287
+ if (opts.output) {
288
+ await fs.writeFile(opts.output, bundleBytes);
289
+ console.log(`\n🔥 Delegation bundle written to ${opts.output}`);
330
290
  }
331
291
  else {
332
- results.push("⚠️ No name state available to grant from.");
333
- }
334
- console.log(`🔥 Delegations for ${targetDID}:\n`);
335
- for (const r of results) {
336
- console.log(r);
337
- console.log();
292
+ const base64 = Buffer.from(bundleBytes).toString("base64");
293
+ console.log(`\n🔥 Delegation bundle for ${targetDID}:\n`);
294
+ console.log(base64);
338
295
  }
339
- console.log("The target device should run:");
340
- console.log(` openclaw clawracha join <upload-delegation> <name-delegation> --agent <id>`);
296
+ console.log("\nThe target device should run:");
297
+ console.log(` openclaw clawracha join <bundle> --agent <id>`);
341
298
  }
342
299
  catch (err) {
343
300
  console.error(`Error: ${err.message}`);
@@ -363,7 +320,9 @@ export default function plugin(api) {
363
320
  console.log(`Workspace: ${workspace}`);
364
321
  console.log(`Upload delegation: ${deviceConfig.uploadDelegation ? "✅" : "❌ not set"}`);
365
322
  console.log(`Name delegation: ${deviceConfig.nameDelegation ? "✅" : "❌ not set"}`);
323
+ console.log(`Plan delegation: ${deviceConfig.planDelegation ? "✅" : "❌ not set"}`);
366
324
  console.log(`Space DID: ${deviceConfig.spaceDID ?? "unknown"}`);
325
+ console.log(`Access: ${deviceConfig.access?.type ?? "unknown"}`);
367
326
  console.log(`Name Archive: ${deviceConfig.nameArchive ? "saved" : "not created"}`);
368
327
  console.log(`Setup complete: ${deviceConfig.setupComplete ? "✅" : "❌"}`);
369
328
  const sync = activeSyncers.get(workspace);
@@ -398,7 +357,6 @@ export default function plugin(api) {
398
357
  console.log(`Not set up. Run \`openclaw clawracha init --agent ${agentId}\` first.`);
399
358
  return;
400
359
  }
401
- // Use active syncer if available, otherwise spin up temporary engine
402
360
  let engine;
403
361
  const activeSync = activeSyncers.get(workspace);
404
362
  if (activeSync) {
@@ -439,7 +397,7 @@ export default function plugin(api) {
439
397
  .description("Interactive guided setup for Storacha workspace sync")
440
398
  .requiredOption("--agent <id>", "Agent ID")
441
399
  .action(async (opts) => {
442
- const { promptMultiline, choose } = await import("./prompts.js");
400
+ const { prompt, promptMultiline, choose } = await import("./prompts.js");
443
401
  try {
444
402
  const { agentId, workspace } = requireAgent(opts.agent);
445
403
  console.log("🔥 Welcome to Clawracha — Storacha workspace sync!\n");
@@ -460,18 +418,12 @@ export default function plugin(api) {
460
418
  // Step 2: Path choice
461
419
  const choice = await choose("Are you setting up a NEW workspace or JOINING an existing one?", ["NEW workspace", "JOIN existing"]);
462
420
  if (choice === "NEW workspace") {
463
- // --- Setup path ---
421
+ // --- Setup path (login only) ---
464
422
  console.log("\n📦 New Workspace Setup\n");
465
- console.log("You need a Storacha upload delegation for this agent.");
466
- console.log("On a machine with the Storacha CLI, run:\n");
467
- console.log(` storacha delegation create ${initResult.agentDID} --base64\n`);
468
- const delegationInput = await promptMultiline("Paste your upload delegation here:");
469
- if (!delegationInput) {
470
- console.error("No delegation provided. Aborting.");
471
- process.exit(1);
472
- }
423
+ const email = await prompt("Storacha email: ");
424
+ const spaceName = await prompt("Space name: ");
473
425
  console.log("\n⏳ Setting up workspace...");
474
- const result = await doSetup(workspace, agentId, delegationInput, pluginConfig, config.gateway);
426
+ const result = await doSetup(workspace, agentId, email, spaceName, pluginConfig, config.gateway);
475
427
  console.log(`\n🔥 Storacha workspace ready for ${agentId}!`);
476
428
  console.log(` Agent DID: ${result.agentDID}`);
477
429
  console.log(` Space: ${result.spaceDID ?? "unknown"}`);
@@ -480,24 +432,18 @@ export default function plugin(api) {
480
432
  console.log("\nSync is now active! 🎉");
481
433
  }
482
434
  else {
483
- // --- Join path ---
435
+ // --- Join path (single bundle) ---
484
436
  console.log("\n🤝 Join Existing Workspace\n");
485
- console.log("You need someone with access to grant you delegations.");
437
+ console.log("You need a delegation bundle from someone with access.");
486
438
  console.log("Ask them to run:\n");
487
439
  console.log(` openclaw clawracha grant ${initResult.agentDID} --agent <their-agent-id>\n`);
488
- console.log("They'll get two delegations to share with you.\n");
489
- const uploadInput = await promptMultiline("Paste the upload delegation here:");
490
- if (!uploadInput) {
491
- console.error("No upload delegation provided. Aborting.");
492
- process.exit(1);
493
- }
494
- const nameInput = await promptMultiline("Paste the name delegation here:");
495
- if (!nameInput) {
496
- console.error("No name delegation provided. Aborting.");
440
+ const bundleInput = await promptMultiline("Paste the delegation bundle here:");
441
+ if (!bundleInput) {
442
+ console.error("No bundle provided. Aborting.");
497
443
  process.exit(1);
498
444
  }
499
445
  console.log("\n⏳ Joining workspace...");
500
- const result = await doJoin(workspace, agentId, uploadInput, nameInput, pluginConfig, config.gateway);
446
+ const result = await doJoin(workspace, agentId, bundleInput, pluginConfig, config.gateway);
501
447
  console.log(`\n🔥 Joined Storacha workspace for ${agentId}!`);
502
448
  console.log(` Agent DID: ${result.agentDID}`);
503
449
  console.log(` Space: ${result.spaceDID ?? "unknown"}`);
package/dist/sync.d.ts CHANGED
@@ -19,6 +19,9 @@ export declare class SyncEngine {
19
19
  private carFile;
20
20
  private lastSync;
21
21
  private syncLock;
22
+ private encryptionConfig?;
23
+ private decryptionConfig?;
24
+ private encryptedClient?;
22
25
  constructor(workspace: string);
23
26
  /**
24
27
  * Initialize sync engine with device config.
@@ -1 +1 @@
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;AAgBxE,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAAiD;IAC9D,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAoC;gBAExC,SAAS,EAAE,MAAM;IAK7B;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC/C;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAe1D;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAMb,UAAU;IAwDxB;;OAEG;YACW,iBAAiB;IAe/B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAc5C;;OAEG;YACW,kBAAkB;IAUhC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3B;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;YAMrB,gBAAgB;IA0B9B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC;QACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,UAAU,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC5D,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IAkBI,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAW5B,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;YAM5B,WAAW;CAK1B"}
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;AAmBxE,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAAiD;IAC9D,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,eAAe,CAAC,CAAkB;gBAE9B,SAAS,EAAE,MAAM;IAK7B;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA6D/C;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB1D;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAMb,UAAU;IAwDxB;;OAEG;YACW,iBAAiB;IAe/B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAc5C;;OAEG;YACW,kBAAkB;IAYhC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3B;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;YAMrB,gBAAgB;IA4B9B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC;QACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,UAAU,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC5D,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IAkBI,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAW5B,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;YAM5B,WAAW;CAK1B"}
package/dist/sync.js CHANGED
@@ -18,6 +18,8 @@ import { diffRemoteChanges } from "./utils/differ.js";
18
18
  import { makeTempCar } from "./utils/tempcar.js";
19
19
  import { createStorachaClient } from "./utils/client.js";
20
20
  import { decodeDelegation, encodeDelegation } from "./utils/delegation.js";
21
+ import { extract } from "@storacha/client/delegation";
22
+ import { makeEncryptionConfig, makeDecryptionConfig, getEncryptedClient } from "./utils/crypto.js";
21
23
  export class SyncEngine {
22
24
  workspace;
23
25
  blocks;
@@ -27,6 +29,9 @@ export class SyncEngine {
27
29
  carFile = null;
28
30
  lastSync = null;
29
31
  syncLock = Promise.resolve();
32
+ encryptionConfig;
33
+ decryptionConfig;
34
+ encryptedClient;
30
35
  constructor(workspace) {
31
36
  this.workspace = workspace;
32
37
  this.blocks = createWorkspaceBlockstore(workspace);
@@ -46,7 +51,6 @@ export class SyncEngine {
46
51
  }
47
52
  else if (config.nameDelegation) {
48
53
  // Reconstruct from delegation (granted by another device)
49
- const { extract } = await import("@storacha/client/delegation");
50
54
  const nameBytes = decodeDelegation(config.nameDelegation);
51
55
  const { ok: delegation } = await extract(nameBytes);
52
56
  if (!delegation)
@@ -58,6 +62,24 @@ export class SyncEngine {
58
62
  name = await Name.create(agent);
59
63
  }
60
64
  this.state = { initialized: true, running: true, name, storachaClient };
65
+ // Set up encryption for private spaces
66
+ if (config.access?.type === "private") {
67
+ if (!config.planDelegation) {
68
+ throw new Error("Private space requires a plan delegation for KMS access");
69
+ }
70
+ const planBytes = decodeDelegation(config.planDelegation);
71
+ const { ok: planDel } = await extract(planBytes);
72
+ if (!planDel)
73
+ throw new Error("Failed to extract plan delegation");
74
+ this.encryptionConfig = makeEncryptionConfig(agent, config.spaceDID, [planDel]);
75
+ // For decrypt, uploadDelegation covers space/content/decrypt
76
+ const uploadBytes = decodeDelegation(config.uploadDelegation);
77
+ const { ok: uploadDel } = await extract(uploadBytes);
78
+ if (!uploadDel)
79
+ throw new Error("Failed to extract upload delegation");
80
+ this.decryptionConfig = makeDecryptionConfig(config.spaceDID, uploadDel);
81
+ this.encryptedClient = await getEncryptedClient(storachaClient);
82
+ }
61
83
  try {
62
84
  const result = await Revision.resolve(this.blocks, name);
63
85
  this.current = result.value;
@@ -98,7 +120,7 @@ export class SyncEngine {
98
120
  if (!this.carFile) {
99
121
  this.carFile = await makeTempCar();
100
122
  }
101
- const pendingOps = await processChanges(changes, this.workspace, this.current, this.blocks, (block) => this.carFile.put(block), (block) => this.blocks.put(block));
123
+ const pendingOps = await processChanges(changes, this.workspace, this.current, this.blocks, (block) => this.carFile.put(block), (block) => this.blocks.put(block), this.encryptionConfig);
102
124
  this.pendingOps.push(...pendingOps);
103
125
  }
104
126
  /**
@@ -203,6 +225,8 @@ export class SyncEngine {
203
225
  await applyRemoteChanges(changedPaths, entries, this.workspace, {
204
226
  blocks: this.blocks,
205
227
  current: this.current ?? undefined,
228
+ encryptedClient: this.encryptedClient,
229
+ decryptionConfig: this.decryptionConfig,
206
230
  });
207
231
  }
208
232
  /**
@@ -249,6 +273,8 @@ export class SyncEngine {
249
273
  await applyRemoteChanges(allPaths, entries, this.workspace, {
250
274
  blocks: this.blocks,
251
275
  current: this.current ?? undefined,
276
+ encryptedClient: this.encryptedClient,
277
+ decryptionConfig: this.decryptionConfig,
252
278
  });
253
279
  }
254
280
  this.lastSync = Date.now();
@@ -10,6 +10,17 @@ export interface SyncPluginConfig {
10
10
  watchPatterns: string[];
11
11
  ignorePatterns: string[];
12
12
  }
13
+ export interface PublicAccess {
14
+ type: "public";
15
+ }
16
+ export interface PrivateAccess {
17
+ type: "private";
18
+ encryption: {
19
+ provider: string;
20
+ algorithm: string;
21
+ };
22
+ }
23
+ export type SpaceAccess = PublicAccess | PrivateAccess;
13
24
  /** Stored in .storacha/config.json — device-specific, not synced */
14
25
  export interface DeviceConfig {
15
26
  /** Ed25519 agent private key (base64) */
@@ -20,8 +31,12 @@ export interface DeviceConfig {
20
31
  uploadDelegation?: string;
21
32
  /** Name → agent delegation for pail sync (base64 archive) */
22
33
  nameDelegation?: string;
34
+ /** Plan/get delegation for KMS access in private spaces (base64 archive) */
35
+ planDelegation?: string;
23
36
  /** Space DID extracted from upload delegation */
24
37
  spaceDID?: string;
38
+ /** Space access type — determines if content is encrypted */
39
+ access?: SpaceAccess;
25
40
  /** Whether setup is complete (watcher won't start without this) */
26
41
  setupComplete?: boolean;
27
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAE5C,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,yBAAyB;AACzB,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4BAA4B;IAC5B,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0BAA0B;AAC1B,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8BAA8B;AAC9B,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,GAAG,CAAC;CACb"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAE5C,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE;QACV,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,CAAC;AAEvD,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,yBAAyB;AACzB,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4BAA4B;IAC5B,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0BAA0B;AAC1B,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8BAA8B;AAC9B,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,GAAG,CAAC;CACb"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Delegation bundle — packs upload, name, and plan delegations
3
+ * into a single CAR file for the `join` workflow.
4
+ */
5
+ export interface DelegationBundle {
6
+ upload: Uint8Array;
7
+ name: Uint8Array;
8
+ plan: Uint8Array;
9
+ }
10
+ /**
11
+ * Pack three delegation archives into a single CAR file.
12
+ * Root block is DAG-CBOR: { upload, name, plan } as byte arrays.
13
+ */
14
+ export declare function createDelegationBundle(bundle: DelegationBundle): Promise<Uint8Array>;
15
+ /**
16
+ * Extract three delegation archives from a bundle CAR file.
17
+ */
18
+ export declare function extractDelegationBundle(carBytes: Uint8Array): Promise<DelegationBundle>;
19
+ //# sourceMappingURL=bundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/utils/bundle.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,UAAU,CAAC,CA2BrB;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAwB3B"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Delegation bundle — packs upload, name, and plan delegations
3
+ * into a single CAR file for the `join` workflow.
4
+ */
5
+ import * as cbor from "@ipld/dag-cbor";
6
+ import { encode, decode } from "multiformats/block";
7
+ import { sha256 } from "multiformats/hashes/sha2";
8
+ import { CarWriter } from "@ipld/car/writer";
9
+ import { CarReader } from "@ipld/car/reader";
10
+ /**
11
+ * Pack three delegation archives into a single CAR file.
12
+ * Root block is DAG-CBOR: { upload, name, plan } as byte arrays.
13
+ */
14
+ export async function createDelegationBundle(bundle) {
15
+ const rootBlock = await encode({
16
+ value: bundle,
17
+ codec: cbor,
18
+ hasher: sha256,
19
+ });
20
+ const { writer, out } = CarWriter.create([rootBlock.cid]);
21
+ const chunks = [];
22
+ const drain = (async () => {
23
+ for await (const chunk of out) {
24
+ chunks.push(chunk);
25
+ }
26
+ })();
27
+ await writer.put(rootBlock);
28
+ await writer.close();
29
+ await drain;
30
+ // Concatenate chunks
31
+ const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
32
+ const result = new Uint8Array(totalLength);
33
+ let offset = 0;
34
+ for (const chunk of chunks) {
35
+ result.set(chunk, offset);
36
+ offset += chunk.length;
37
+ }
38
+ return result;
39
+ }
40
+ /**
41
+ * Extract three delegation archives from a bundle CAR file.
42
+ */
43
+ export async function extractDelegationBundle(carBytes) {
44
+ const reader = await CarReader.fromBytes(carBytes);
45
+ const roots = await reader.getRoots();
46
+ if (roots.length !== 1) {
47
+ throw new Error(`Expected 1 root in delegation bundle, got ${roots.length}`);
48
+ }
49
+ const rootBlock = await reader.get(roots[0]);
50
+ if (!rootBlock) {
51
+ throw new Error("Could not read root block from delegation bundle");
52
+ }
53
+ const decoded = await decode({
54
+ bytes: rootBlock.bytes,
55
+ codec: cbor,
56
+ hasher: sha256,
57
+ });
58
+ const value = decoded.value;
59
+ if (!value.upload || !value.name || !value.plan) {
60
+ throw new Error("Delegation bundle missing required keys (upload, name, plan)");
61
+ }
62
+ return {
63
+ upload: value.upload,
64
+ name: value.name,
65
+ plan: value.plan,
66
+ };
67
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Encryption helpers for private spaces.
3
+ * Uses @storacha/encrypt-upload-client with KMS-based key management.
4
+ */
5
+ import type { Block } from "multiformats";
6
+ import type { CID } from "multiformats/cid";
7
+ import type { Client } from "@storacha/client";
8
+ import type { Proof } from "@ucanto/interface";
9
+ type SpaceDID = `did:key:${string}`;
10
+ import type { CryptoAdapter, EncryptionConfig, DecryptionConfig, BlobLike, EncryptedClient } from "@storacha/encrypt-upload-client/types";
11
+ export declare function getKMSCryptoAdapter(): CryptoAdapter;
12
+ export declare function getEncryptedClient(storachaClient: Client): Promise<EncryptedClient>;
13
+ export declare function makeEncryptionConfig(issuer: {
14
+ did: () => `did:key:${string}`;
15
+ }, spaceDID: SpaceDID, proofs: Proof[]): EncryptionConfig;
16
+ export declare function makeDecryptionConfig(spaceDID: SpaceDID, decryptDelegation: Proof, proofs?: Proof[]): DecryptionConfig;
17
+ /**
18
+ * Encrypt a BlobLike and return a block stream
19
+ * (UnixFS-encoded encrypted content + metadata block appended).
20
+ */
21
+ export declare function encryptToBlockStream(file: BlobLike, encryptionConfig: EncryptionConfig): Promise<ReadableStream<Block>>;
22
+ /**
23
+ * Wrap raw bytes as a BlobLike for encryption.
24
+ */
25
+ export declare function bytesToBlobLike(bytes: Uint8Array): BlobLike;
26
+ /**
27
+ * Create a decrypt function for mdsync resolveValue.
28
+ * Fetches encrypted content by CID via EncryptedClient and returns decrypted bytes.
29
+ */
30
+ export declare function makeDecryptFn(encryptedClient: EncryptedClient, decryptionConfig: DecryptionConfig): (cid: CID) => Promise<Uint8Array>;
31
+ export {};
32
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,KAAK,QAAQ,GAAG,WAAW,MAAM,EAAE,CAAC;AACpC,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,QAAQ,EACR,eAAe,EAChB,MAAM,uCAAuC,CAAC;AAU/C,wBAAgB,mBAAmB,IAAI,aAAa,CAInD;AAED,wBAAsB,kBAAkB,CACtC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,eAAe,CAAC,CAM1B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,WAAW,MAAM,EAAE,CAAA;CAAE,EAC1C,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,KAAK,EAAE,GACd,gBAAgB,CAMlB;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,iBAAiB,EAAE,KAAK,EACxB,MAAM,CAAC,EAAE,KAAK,EAAE,GACf,gBAAgB,CAMlB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,QAAQ,EACd,gBAAgB,EAAE,gBAAgB,GACjC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAIhC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,CAU3D;AAwBD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,eAAe,EAChC,gBAAgB,EAAE,gBAAgB,GACjC,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,UAAU,CAAC,CAQnC"}