@storacha/clawracha 0.0.11-rc.0 → 0.1.1

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
@@ -21,12 +21,14 @@ openclaw plugins install @storacha/clawracha
21
21
 
22
22
  ## Setup
23
23
 
24
- Setup is done via slash commands in your chat session with the agent (not CLI commands).
24
+ Setup is done via CLI commands on the host (not slash commands in chat).
25
+
26
+ All commands require `--agent <id>` to specify which agent workspace to configure.
25
27
 
26
28
  ### Step 1: Initialize the agent
27
29
 
28
- ```
29
- /storacha-init
30
+ ```bash
31
+ openclaw clawracha init --agent <id>
30
32
  ```
31
33
 
32
34
  Generates an agent identity and displays the Agent DID. You'll need this DID to create delegations.
@@ -35,32 +37,32 @@ Generates an agent identity and displays the Agent DID. You'll need this DID to
35
37
 
36
38
  **New workspace (first device):**
37
39
 
38
- ```
39
- /storacha-setup <upload-delegation-b64>
40
+ ```bash
41
+ openclaw clawracha setup <delegation> --agent <id>
40
42
  ```
41
43
 
42
- Have the space owner create an upload delegation for your Agent DID, then import it. Creates a fresh UCN Name and starts syncing.
44
+ `<delegation>` can be a file path (raw CAR) or a base64 CID string. Have the space owner create an upload delegation for your Agent DID, then import it.
43
45
 
44
46
  **Join an existing workspace (additional devices):**
45
47
 
46
- ```
47
- /storacha-join <upload-delegation-b64> <name-delegation-b64>
48
+ ```bash
49
+ openclaw clawracha join <upload-delegation> <name-delegation> --agent <id>
48
50
  ```
49
51
 
50
- Get both delegations by running `/storacha-grant` on the existing device. The join command pulls all remote files before the watcher starts, so your local workspace is fully synced from the start.
52
+ Get both delegations by running `openclaw clawracha grant` on the existing device. Arguments can be file paths or base64 CID strings. The join command pulls all remote files before the watcher starts.
51
53
 
52
54
  ### Grant access to another device
53
55
 
54
- ```
55
- /storacha-grant <target-agent-DID>
56
+ ```bash
57
+ openclaw clawracha grant <target-agent-DID> --agent <id>
56
58
  ```
57
59
 
58
- Generates upload and name delegations for the target device. The target device uses these with `/storacha-join`.
60
+ Generates upload and name delegations for the target device.
59
61
 
60
62
  ### Check status
61
63
 
62
- ```
63
- /storacha-status
64
+ ```bash
65
+ openclaw clawracha status --agent <id>
64
66
  ```
65
67
 
66
68
  After setup, restart the gateway to start syncing:
package/dist/plugin.d.ts CHANGED
@@ -2,13 +2,10 @@
2
2
  * OpenClaw Plugin Entry Point
3
3
  *
4
4
  * Registers:
5
- * - Background service for file watching and sync
5
+ * - Background service that syncs ALL agent workspaces with .storacha configs
6
+ * - CLI commands for setup and management (openclaw clawracha ...)
6
7
  * - Agent tools for manual sync control
7
- * - Slash commands for setup
8
8
  */
9
9
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
10
- /**
11
- * Plugin entry — called by OpenClaw when the plugin is loaded.
12
- */
13
10
  export default function plugin(api: OpenClawPluginApi): void;
14
11
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,qBAAqB,CAAC;AAyC7B;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,GAAG,EAAE,iBAAiB,QA4hBpD"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,qBAAqB,CAAC;AAiH7B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,GAAG,EAAE,iBAAiB,QAkgBpD"}
package/dist/plugin.js CHANGED
@@ -2,24 +2,20 @@
2
2
  * OpenClaw Plugin Entry Point
3
3
  *
4
4
  * Registers:
5
- * - Background service for file watching and sync
5
+ * - Background service that syncs ALL agent workspaces with .storacha configs
6
+ * - CLI commands for setup and management (openclaw clawracha ...)
6
7
  * - Agent tools for manual sync control
7
- * - Slash commands for setup
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";
12
11
  import { SyncEngine } from "./sync.js";
13
- import { decodeDelegation, encodeDelegation } from "./utils/delegation.js";
14
12
  import { FileWatcher } from "./watcher.js";
15
13
  import { createStorachaClient } from "./utils/client.js";
16
- // Global state
17
- let syncEngine = null;
18
- let fileWatcher = null;
19
- let workspaceDir;
20
- /**
21
- * Load device config from .storacha/config.json
22
- */
14
+ import { decodeDelegation, encodeDelegation, readDelegationArg, } from "./utils/delegation.js";
15
+ import { resolveAgentWorkspace, getAgentIds } from "./utils/workspace.js";
16
+ import { readIgnoreFile } from "./utils/ignore.js";
17
+ const activeSyncers = new Map();
18
+ // --- Config helpers ---
23
19
  async function loadDeviceConfig(workspace) {
24
20
  const configPath = path.join(workspace, ".storacha", "config.json");
25
21
  try {
@@ -32,22 +28,61 @@ async function loadDeviceConfig(workspace) {
32
28
  throw err;
33
29
  }
34
30
  }
35
- /**
36
- * Save device config
37
- */
38
31
  async function saveDeviceConfig(workspace, config) {
39
32
  const configDir = path.join(workspace, ".storacha");
40
33
  await fs.mkdir(configDir, { recursive: true });
41
34
  const configPath = path.join(configDir, "config.json");
42
35
  await fs.writeFile(configPath, JSON.stringify(config, null, 2));
43
36
  }
37
+ // --- Service helpers ---
38
+ async function startWorkspaceSync(workspace, agentId, pluginConfig, logger) {
39
+ const deviceConfig = await loadDeviceConfig(workspace);
40
+ if (!deviceConfig || !deviceConfig.setupComplete) {
41
+ return null;
42
+ }
43
+ const storachaClient = await createStorachaClient(deviceConfig);
44
+ const engine = new SyncEngine(storachaClient, workspace);
45
+ await engine.init(deviceConfig);
46
+ const watcher = new FileWatcher({
47
+ workspace,
48
+ config: {
49
+ enabled: true,
50
+ watchPatterns: pluginConfig.watchPatterns ?? ["**/*"],
51
+ ignorePatterns: pluginConfig.ignorePatterns ?? [
52
+ ".storacha/**",
53
+ "node_modules/**",
54
+ ".git/**",
55
+ "dist/**",
56
+ ],
57
+ },
58
+ onChanges: async (changes) => {
59
+ await engine.processChanges(changes);
60
+ await engine.sync();
61
+ const nameArchive = await engine.exportNameArchive();
62
+ const updatedConfig = { ...deviceConfig, nameArchive };
63
+ await saveDeviceConfig(workspace, updatedConfig);
64
+ },
65
+ });
66
+ await watcher.start();
67
+ logger.info(`[${agentId}] Started syncing workspace: ${workspace}`);
68
+ return { engine, watcher, workspace, agentId };
69
+ }
44
70
  /**
45
- * Plugin entry called by OpenClaw when the plugin is loaded.
71
+ * Scan workspace for files, excluding patterns.
72
+ * Returns paths relative to workspace.
46
73
  */
74
+ async function scanWorkspaceFiles(workspace, ignorePatterns) {
75
+ const { glob } = await import("glob");
76
+ return glob("**/*", {
77
+ cwd: workspace,
78
+ nodir: true,
79
+ ignore: ignorePatterns.map((p) => p.includes("/") || p.includes("*") ? p : `${p}/**`),
80
+ });
81
+ }
82
+ // --- Plugin entry ---
47
83
  export default function plugin(api) {
48
- // Capture plugin-specific config at registration time
49
84
  const pluginConfig = (api.pluginConfig ?? {});
50
- // Register background service
85
+ // --- Background service: one syncer per agent workspace ---
51
86
  api.registerService({
52
87
  id: "storacha-sync",
53
88
  async start(ctx) {
@@ -55,73 +90,55 @@ export default function plugin(api) {
55
90
  ctx.logger.info("Storacha sync disabled via config.");
56
91
  return;
57
92
  }
58
- workspaceDir = ctx.workspaceDir;
59
- const workspace = workspaceDir;
60
- if (!workspace) {
61
- ctx.logger.warn("No workspace directory configured");
62
- return;
93
+ const agentIds = getAgentIds(ctx.config);
94
+ for (const agentId of agentIds) {
95
+ const workspace = resolveAgentWorkspace(ctx.config, agentId);
96
+ try {
97
+ const sync = await startWorkspaceSync(workspace, agentId, pluginConfig, ctx.logger);
98
+ if (sync) {
99
+ activeSyncers.set(workspace, sync);
100
+ }
101
+ }
102
+ catch (err) {
103
+ ctx.logger.warn(`[${agentId}] Failed to start sync: ${err.message}`);
104
+ }
63
105
  }
64
- const deviceConfig = await loadDeviceConfig(workspace);
65
- if (!deviceConfig || !deviceConfig.setupComplete) {
66
- ctx.logger.info("Setup not complete. Run /storacha-init first, then /storacha-setup or /storacha-join.");
67
- return;
106
+ if (activeSyncers.size === 0) {
107
+ ctx.logger.info("No agent workspaces configured for Storacha sync. Use `openclaw clawracha init --agent <id>` to set up.");
108
+ }
109
+ else {
110
+ ctx.logger.info(`Storacha sync active for ${activeSyncers.size} workspace(s).`);
68
111
  }
69
- const storachaClient = await createStorachaClient(deviceConfig);
70
- syncEngine = new SyncEngine(storachaClient, workspace);
71
- await syncEngine.init(deviceConfig);
72
- fileWatcher = new FileWatcher({
73
- workspace,
74
- config: {
75
- enabled: true,
76
- watchPatterns: pluginConfig.watchPatterns ?? ["**/*"],
77
- ignorePatterns: pluginConfig.ignorePatterns ?? [
78
- ".storacha/**",
79
- "node_modules/**",
80
- ".git/**",
81
- "dist/**",
82
- ],
83
- },
84
- onChanges: async (changes) => {
85
- if (!syncEngine)
86
- return;
87
- await syncEngine.processChanges(changes);
88
- await syncEngine.sync();
89
- const nameArchive = await syncEngine.exportNameArchive();
90
- const updatedConfig = { ...deviceConfig, nameArchive };
91
- await saveDeviceConfig(workspace, updatedConfig);
92
- },
93
- });
94
- await fileWatcher.start();
95
- ctx.logger.info("Started watching workspace");
96
112
  },
97
113
  async stop(ctx) {
98
- if (fileWatcher) {
99
- await fileWatcher.stop();
100
- fileWatcher = null;
114
+ for (const [workspace, sync] of activeSyncers) {
115
+ await sync.watcher.stop();
116
+ ctx.logger.info(`[${sync.agentId}] Stopped syncing: ${workspace}`);
101
117
  }
102
- syncEngine = null;
103
- ctx.logger.info("Stopped");
118
+ activeSyncers.clear();
104
119
  },
105
120
  });
106
- // Register agent tools
121
+ // --- Agent tools (keyed by workspace dir) ---
107
122
  api.registerTool({
108
123
  name: "storacha_sync_status",
109
124
  label: "Storacha Sync Status",
110
125
  description: "Get the current Storacha workspace sync status",
111
126
  parameters: { type: "object", properties: {} },
112
- execute: async () => {
113
- if (!syncEngine) {
127
+ execute: async (_params, ctx) => {
128
+ const workspace = ctx?.workspaceDir;
129
+ const sync = workspace ? activeSyncers.get(workspace) : undefined;
130
+ if (!sync) {
114
131
  return {
115
132
  content: [
116
133
  {
117
134
  type: "text",
118
- text: "Sync not initialized. Run /storacha-init first, then /storacha-setup or /storacha-join.",
135
+ text: "Sync not active for this workspace. Set up with `openclaw clawracha init --agent <id>`.",
119
136
  },
120
137
  ],
121
138
  details: null,
122
139
  };
123
140
  }
124
- const status = await syncEngine.status();
141
+ const status = await sync.engine.status();
125
142
  return {
126
143
  content: [
127
144
  { type: "text", text: JSON.stringify(status, null, 2) },
@@ -135,20 +152,22 @@ export default function plugin(api) {
135
152
  label: "Storacha Sync Now",
136
153
  description: "Trigger an immediate workspace sync to Storacha",
137
154
  parameters: { type: "object", properties: {} },
138
- execute: async () => {
139
- if (!syncEngine) {
155
+ execute: async (_params, ctx) => {
156
+ const workspace = ctx?.workspaceDir;
157
+ const sync = workspace ? activeSyncers.get(workspace) : undefined;
158
+ if (!sync) {
140
159
  return {
141
160
  content: [
142
161
  {
143
162
  type: "text",
144
- text: "Sync not initialized. Run /storacha-init first, then /storacha-setup or /storacha-join.",
163
+ text: "Sync not active for this workspace. Set up with `openclaw clawracha init --agent <id>`.",
145
164
  },
146
165
  ],
147
166
  details: null,
148
167
  };
149
168
  }
150
- await syncEngine.sync();
151
- const status = await syncEngine.status();
169
+ await sync.engine.sync();
170
+ const status = await sync.engine.status();
152
171
  return {
153
172
  content: [
154
173
  {
@@ -160,375 +179,298 @@ export default function plugin(api) {
160
179
  };
161
180
  },
162
181
  });
163
- // --- Slash Commands ---
164
- api.registerCommand({
165
- name: "storacha-init",
166
- description: "Generate an agent identity for Storacha sync",
167
- handler: async (_ctx) => {
182
+ // --- CLI commands: openclaw clawracha <subcommand> ---
183
+ api.registerCli(({ program, config }) => {
184
+ const clawracha = program
185
+ .command("clawracha")
186
+ .description("Storacha workspace sync commands");
187
+ // Helper to resolve workspace from --agent
188
+ function requireAgent(agentId) {
189
+ if (!agentId) {
190
+ console.error("Error: --agent <id> is required. Specify which agent workspace to configure.");
191
+ process.exit(1);
192
+ }
193
+ return {
194
+ agentId,
195
+ workspace: resolveAgentWorkspace(config, agentId),
196
+ };
197
+ }
198
+ // --- init ---
199
+ clawracha
200
+ .command("init")
201
+ .description("Generate an agent identity for Storacha sync")
202
+ .requiredOption("--agent <id>", "Agent ID")
203
+ .action(async (opts) => {
168
204
  try {
169
- const workspace = workspaceDir;
170
- if (!workspace)
171
- return { text: "No workspace configured." };
172
- // Check if already initialized
205
+ const { agentId, workspace } = requireAgent(opts.agent);
173
206
  const existing = await loadDeviceConfig(workspace);
174
207
  if (existing?.agentKey) {
175
208
  const { Agent } = await import("@storacha/ucn/pail");
176
209
  const agent = Agent.parse(existing.agentKey);
177
- return {
178
- text: [
179
- "Agent already initialized.",
180
- `Agent DID: \`${agent.did()}\``,
181
- "",
182
- existing.setupComplete
183
- ? "Setup is complete. Use `/storacha-status` to check sync state."
184
- : "**Next step choose one:**",
185
- ...(!existing.setupComplete
186
- ? [
187
- "- **New workspace:** Have the space owner create an upload delegation for this DID, then run `/storacha-setup <upload-b64>`",
188
- "- **Join existing:** Have the other device run `/storacha-grant <this-DID>`, then run `/storacha-join <upload-b64> <name-b64>`",
189
- ]
190
- : []),
191
- ].join("\n"),
192
- };
210
+ console.log(`Agent already initialized for ${agentId}.`);
211
+ console.log(`Agent DID: ${agent.did()}`);
212
+ if (existing.setupComplete) {
213
+ console.log(`\nSetup is complete. Use \`openclaw clawracha status --agent ${agentId}\` to check sync state.`);
214
+ }
215
+ else {
216
+ console.log("\nNext step choose one:");
217
+ console.log(` New workspace: openclaw clawracha setup <delegation> --agent ${agentId}`);
218
+ console.log(` Join existing: openclaw clawracha join <upload> <name> --agent ${agentId}`);
219
+ }
220
+ return;
193
221
  }
194
222
  const { Agent } = await import("@storacha/ucn/pail");
195
223
  const agent = await Agent.generate();
196
224
  const agentKey = Agent.format(agent);
197
- const config = { agentKey };
198
- await saveDeviceConfig(workspace, config);
199
- return {
200
- text: [
201
- "\u{1f525} Agent initialized!",
202
- `Agent DID: \`${agent.did()}\``,
203
- "",
204
- "**Next step \u2014 choose one:**",
205
- "- **New workspace:** Have the space owner create an upload delegation for this DID, then run `/storacha-setup <upload-b64>`",
206
- "- **Join existing workspace:** Have the other device run `/storacha-grant <this-DID>`, then run `/storacha-join <upload-b64> <name-b64>`",
207
- ].join("\n"),
208
- };
225
+ await saveDeviceConfig(workspace, { agentKey });
226
+ console.log(`🔥 Agent initialized for ${agentId}!`);
227
+ console.log(`Agent DID: ${agent.did()}`);
228
+ console.log("\nNext step — choose one:");
229
+ console.log(` New workspace: openclaw clawracha setup <delegation> --agent ${agentId}`);
230
+ console.log(` Join existing: openclaw clawracha join <upload> <name> --agent ${agentId}`);
209
231
  }
210
232
  catch (err) {
211
- return {
212
- text: `\u274c Command failed: ${err.message}\n\`\`\`\n${err.stack ?? err}\n\`\`\``,
213
- };
233
+ console.error(`Error: ${err.message}`);
234
+ process.exit(1);
214
235
  }
215
- },
216
- });
217
- api.registerCommand({
218
- name: "storacha-setup",
219
- description: "Set up a NEW Storacha workspace (first device). Usage: /storacha-setup <upload-delegation-b64>",
220
- acceptsArgs: true,
221
- handler: async (_ctx) => {
236
+ });
237
+ // --- setup ---
238
+ clawracha
239
+ .command("setup <delegation>")
240
+ .description("Set up a NEW workspace (first device). <delegation> is a file path or base64 CID string.")
241
+ .requiredOption("--agent <id>", "Agent ID")
242
+ .action(async (delegationArg, opts) => {
222
243
  try {
223
- const workspace = workspaceDir;
224
- if (!workspace)
225
- return { text: "No workspace configured." };
226
- const config = await loadDeviceConfig(workspace);
227
- if (!config?.agentKey) {
228
- return {
229
- text: "Run `/storacha-init` first to generate an agent identity.",
230
- };
244
+ const { agentId, workspace } = requireAgent(opts.agent);
245
+ const deviceConfig = await loadDeviceConfig(workspace);
246
+ if (!deviceConfig?.agentKey) {
247
+ console.error(`Run \`openclaw clawracha init --agent ${agentId}\` first.`);
248
+ process.exit(1);
231
249
  }
232
- if (config.setupComplete) {
233
- return {
234
- text: "Setup already complete. Use `/storacha-status` to check sync state.",
235
- };
250
+ if (deviceConfig.setupComplete) {
251
+ console.log("Setup already complete.");
252
+ return;
236
253
  }
237
- const b64 = _ctx.args?.trim();
238
- if (!b64) {
239
- return {
240
- text: [
241
- "Usage: `/storacha-setup <upload-delegation-b64>`",
242
- "",
243
- "This creates a **new** workspace. If you're joining an existing workspace, use `/storacha-join` instead.",
244
- ].join("\n"),
245
- };
254
+ const delegation = await readDelegationArg(delegationArg);
255
+ const spaceDID = delegation.capabilities[0]?.with;
256
+ const { ok: archiveBytes } = await delegation.archive();
257
+ if (!archiveBytes) {
258
+ throw new Error("Failed to archive delegation");
246
259
  }
247
- return {
248
- text: [
249
- "You ran '/storacha-setup' without the following command argument:",
250
- "",
251
- b64,
252
- ].join("\n"),
253
- };
254
- /*
255
- // Validate delegation
256
- const bytes = decodeDelegation(b64);
257
- const { ok: delegation, error } = await extractDelegation(bytes);
258
- if (!delegation) {
259
- return { text: `Invalid delegation: ${error}` };
260
+ deviceConfig.uploadDelegation = encodeDelegation(archiveBytes);
261
+ deviceConfig.spaceDID = spaceDID ?? undefined;
262
+ deviceConfig.setupComplete = true;
263
+ await saveDeviceConfig(workspace, deviceConfig);
264
+ // Initial upload: scan all existing workspace files and sync to Storacha
265
+ const storachaClient = await createStorachaClient(deviceConfig);
266
+ const engine = new SyncEngine(storachaClient, workspace);
267
+ await engine.init(deviceConfig);
268
+ const userIgnored = await readIgnoreFile(workspace);
269
+ const ignorePatterns = [
270
+ ".storacha",
271
+ "node_modules",
272
+ ".git",
273
+ "dist",
274
+ ...userIgnored,
275
+ ];
276
+ const allFiles = await scanWorkspaceFiles(workspace, ignorePatterns);
277
+ if (allFiles.length > 0) {
278
+ const changes = allFiles.map((f) => ({
279
+ type: "add",
280
+ path: f,
281
+ }));
282
+ await engine.processChanges(changes);
283
+ await engine.sync();
284
+ console.log(`Uploaded ${allFiles.length} existing files to Storacha.`);
260
285
  }
261
-
262
- const spaceDID = delegation.capabilities[0]?.with;
263
-
264
- config.uploadDelegation = b64;
265
- config.spaceDID = spaceDID ?? undefined;
266
- config.setupComplete = true;
267
- await saveDeviceConfig(workspace, config);
268
-
286
+ // Save name archive after initial sync
287
+ const exportedArchive = await engine.exportNameArchive();
288
+ deviceConfig.nameArchive = exportedArchive;
289
+ await saveDeviceConfig(workspace, deviceConfig);
269
290
  const { Agent } = await import("@storacha/ucn/pail");
270
- const agent = Agent.parse(config.agentKey);
271
-
272
- return {
273
- text: [
274
- "\u{1f525} Storacha workspace ready!",
275
- `Agent DID: \`${agent.did()}\``,
276
- `Space: \`${spaceDID ?? "unknown"}\``,
277
- "",
278
- "Restart the gateway to start syncing.",
279
- "",
280
- "To add another device, run `/storacha-grant <their-agent-DID>` here,",
281
- "then `/storacha-join <upload-b64> <name-b64>` on the other device.",
282
- ].join("\n"),
283
- };*/
291
+ const agent = Agent.parse(deviceConfig.agentKey);
292
+ console.log(`🔥 Storacha workspace ready for ${agentId}!`);
293
+ console.log(`Agent DID: ${agent.did()}`);
294
+ console.log(`Space: ${spaceDID ?? "unknown"}`);
295
+ console.log("\nRestart the gateway to start syncing: `openclaw gateway restart`");
296
+ console.log(`\nTo add another device, run \`openclaw clawracha grant <their-DID> --agent ${agentId}\` here,`);
297
+ console.log(`then \`openclaw clawracha join <upload> <name> --agent <id>\` on the other device.`);
284
298
  }
285
299
  catch (err) {
286
- return {
287
- text: `\u274c Command failed: ${err.message}\n\`\`\`\n${err.stack ?? err}\n\`\`\``,
288
- };
300
+ console.error(`Error: ${err.message}`);
301
+ process.exit(1);
289
302
  }
290
- },
291
- });
292
- api.registerCommand({
293
- name: "storacha-join",
294
- description: "Join an existing Storacha workspace from another device. Run /storacha-init first. Usage: /storacha-join <upload-delegation-b64> <name-delegation-b64>",
295
- acceptsArgs: true,
296
- handler: async (_ctx) => {
303
+ });
304
+ // --- join ---
305
+ clawracha
306
+ .command("join <upload-delegation> <name-delegation>")
307
+ .description("Join an existing workspace from another device. Arguments are file paths or base64 CID strings.")
308
+ .requiredOption("--agent <id>", "Agent ID")
309
+ .action(async (uploadArg, nameArg, opts) => {
297
310
  try {
298
- const workspace = workspaceDir;
299
- if (!workspace)
300
- return { text: "No workspace configured." };
301
- const args = _ctx.args?.trim();
302
- if (!args) {
303
- return {
304
- text: [
305
- "Usage: `/storacha-join <upload-delegation-b64> <name-delegation-b64>`",
306
- "",
307
- "Get both delegations by running `/storacha-grant` on the existing device.",
308
- "If you're setting up a **new** workspace, use `/storacha-setup` instead.",
309
- ].join("\n"),
310
- };
311
+ const { agentId, workspace } = requireAgent(opts.agent);
312
+ const deviceConfig = await loadDeviceConfig(workspace);
313
+ if (!deviceConfig?.agentKey) {
314
+ console.error(`Run \`openclaw clawracha init --agent ${agentId}\` first.`);
315
+ process.exit(1);
311
316
  }
312
- const spaceIdx = args.indexOf(" ");
313
- if (spaceIdx === -1) {
314
- return {
315
- text: "Two arguments required: `/storacha-join <upload-b64> <name-b64>`",
316
- };
317
+ if (deviceConfig.setupComplete) {
318
+ console.log("Setup already complete.");
319
+ return;
317
320
  }
318
- const uploadB64 = args.slice(0, spaceIdx).trim();
319
- const nameB64 = args.slice(spaceIdx + 1).trim();
320
- if (!uploadB64 || !nameB64) {
321
- return {
322
- text: "Two arguments required: `/storacha-join <upload-b64> <name-b64>`",
323
- };
324
- }
325
- // Validate upload delegation
326
- const uploadBytes = decodeDelegation(uploadB64);
327
- const { ok: uploadDelegation, error: uploadErr } = await extractDelegation(uploadBytes);
328
- if (!uploadDelegation) {
329
- return { text: `Invalid upload delegation: ${uploadErr}` };
330
- }
331
- // Validate name delegation
332
- const nameBytes = decodeDelegation(nameB64);
333
- const { ok: nameDelegation, error: nameErr } = await extractDelegation(nameBytes);
334
- if (!nameDelegation) {
335
- return { text: `Invalid name delegation: ${nameErr}` };
336
- }
337
- const config = await loadDeviceConfig(workspace);
338
- if (!config?.agentKey) {
339
- return {
340
- text: "Run `/storacha-init` first to generate an agent identity.",
341
- };
342
- }
343
- if (config.setupComplete) {
344
- return {
345
- text: "Setup already complete. Use `/storacha-status` to check sync state.",
346
- };
347
- }
348
- const { Agent } = await import("@storacha/ucn/pail");
349
- const agent = Agent.parse(config.agentKey);
321
+ const uploadDelegation = await readDelegationArg(uploadArg);
322
+ const nameDelegation = await readDelegationArg(nameArg);
350
323
  const spaceDID = uploadDelegation.capabilities[0]?.with;
351
- config.uploadDelegation = uploadB64;
352
- config.nameDelegation = nameB64;
353
- config.spaceDID = spaceDID ?? undefined;
354
- config.setupComplete = true;
355
- await saveDeviceConfig(workspace, config);
356
- // Pull remote state immediately before watcher starts
324
+ const { ok: uploadArchive } = await uploadDelegation.archive();
325
+ if (!uploadArchive)
326
+ throw new Error("Failed to archive upload delegation");
327
+ const { ok: nameArchiveBytes } = await nameDelegation.archive();
328
+ if (!nameArchiveBytes)
329
+ throw new Error("Failed to archive name delegation");
330
+ deviceConfig.uploadDelegation = encodeDelegation(uploadArchive);
331
+ deviceConfig.nameDelegation = encodeDelegation(nameArchiveBytes);
332
+ deviceConfig.spaceDID = spaceDID ?? undefined;
333
+ deviceConfig.setupComplete = true;
334
+ await saveDeviceConfig(workspace, deviceConfig);
335
+ // Pull remote state before watcher starts
357
336
  let pullCount = 0;
358
- try {
359
- const storachaClient = await createStorachaClient(config);
360
- const engine = new SyncEngine(storachaClient, workspace);
361
- await engine.init(config);
362
- pullCount = await engine.pullRemote();
363
- // Save name archive after pull
364
- const nameArchive = await engine.exportNameArchive();
365
- config.nameArchive = nameArchive;
366
- await saveDeviceConfig(workspace, config);
367
- }
368
- catch (err) {
369
- return {
370
- text: [
371
- "\u26a0\ufe0f Delegations saved but initial pull failed:",
372
- `\`${err.message}\``,
373
- "",
374
- "Restart the gateway to retry.",
375
- ].join("\n"),
376
- };
377
- }
378
- return {
379
- text: [
380
- "\u{1f525} Joined existing Storacha workspace!",
381
- `Agent DID: \`${agent.did()}\``,
382
- `Space: \`${spaceDID ?? "unknown"}\``,
383
- `Pulled ${pullCount} files from remote.`,
384
- "",
385
- "Restart the gateway to start syncing.",
386
- ].join("\n"),
387
- };
337
+ const storachaClient = await createStorachaClient(deviceConfig);
338
+ const engine = new SyncEngine(storachaClient, workspace);
339
+ await engine.init(deviceConfig);
340
+ pullCount = await engine.pullRemote();
341
+ // Save name archive after pull
342
+ const exportedArchive = await engine.exportNameArchive();
343
+ deviceConfig.nameArchive = exportedArchive;
344
+ await saveDeviceConfig(workspace, deviceConfig);
345
+ const { Agent } = await import("@storacha/ucn/pail");
346
+ const agent = Agent.parse(deviceConfig.agentKey);
347
+ console.log(`🔥 Joined existing Storacha workspace for ${agentId}!`);
348
+ console.log(`Agent DID: ${agent.did()}`);
349
+ console.log(`Space: ${spaceDID ?? "unknown"}`);
350
+ console.log(`Pulled ${pullCount} files from remote.`);
351
+ console.log("\nRestart the gateway to start syncing: `openclaw gateway restart`");
388
352
  }
389
353
  catch (err) {
390
- return {
391
- text: `\u274c Command failed: ${err.message}\n\`\`\`\n${err.stack ?? err}\n\`\`\``,
392
- };
354
+ console.error(`Error: ${err.message}`);
355
+ process.exit(1);
393
356
  }
394
- },
395
- });
396
- api.registerCommand({
397
- name: "storacha-grant",
398
- description: "Grant another device access. Usage: /storacha-grant <target-DID>",
399
- acceptsArgs: true,
400
- handler: async (_ctx) => {
357
+ });
358
+ // --- grant ---
359
+ clawracha
360
+ .command("grant <target-DID>")
361
+ .description("Grant another device access to this workspace")
362
+ .requiredOption("--agent <id>", "Agent ID")
363
+ .action(async (targetDID, opts) => {
401
364
  try {
402
- const workspace = workspaceDir;
403
- if (!workspace)
404
- return { text: "No workspace configured." };
405
- const targetDID = _ctx.args?.trim();
406
- if (!targetDID || !targetDID.startsWith("did:")) {
407
- return { text: "Usage: `/storacha-grant <did:key:z...>`" };
365
+ const { agentId, workspace } = requireAgent(opts.agent);
366
+ if (!targetDID.startsWith("did:")) {
367
+ console.error("Error: target must be a DID (did:key:z...)");
368
+ process.exit(1);
408
369
  }
409
- const config = await loadDeviceConfig(workspace);
410
- if (!config) {
411
- return {
412
- text: "Not initialized. Run `/storacha-init` first.",
413
- };
370
+ const deviceConfig = await loadDeviceConfig(workspace);
371
+ if (!deviceConfig) {
372
+ console.error(`Not initialized. Run \`openclaw clawracha init --agent ${agentId}\` first.`);
373
+ process.exit(1);
414
374
  }
415
375
  const results = [];
416
376
  // Re-delegate upload capability
417
- if (config.uploadDelegation) {
418
- try {
419
- const storachaClient = await createStorachaClient(config);
420
- const audience = { did: () => targetDID };
421
- const uploadDelegation = await storachaClient.createDelegation(audience, [
422
- "space/blob/add",
423
- "space/index/add",
424
- "upload/add",
425
- "filecoin/offer",
426
- ]);
427
- const { ok: archiveBytes } = await uploadDelegation.archive();
428
- if (archiveBytes) {
429
- const b64 = encodeDelegation(archiveBytes);
430
- results.push("**Upload delegation:**\n```\n" + b64 + "\n```");
431
- }
432
- }
433
- catch (err) {
434
- results.push(`\u274c Failed to create upload delegation: ${err.message}`);
377
+ if (deviceConfig.uploadDelegation) {
378
+ const storachaClient = await createStorachaClient(deviceConfig);
379
+ const audience = {
380
+ did: () => targetDID,
381
+ };
382
+ const uploadDel = await storachaClient.createDelegation(audience, [
383
+ "space/blob/add",
384
+ "space/index/add",
385
+ "upload/add",
386
+ "filecoin/offer",
387
+ ]);
388
+ const { ok: archiveBytes } = await uploadDel.archive();
389
+ if (archiveBytes) {
390
+ results.push(`Upload delegation:\n${encodeDelegation(archiveBytes)}`);
435
391
  }
436
392
  }
437
393
  else {
438
- results.push("\u26a0\ufe0f No upload delegation to re-delegate.");
394
+ results.push("⚠️ No upload delegation to re-delegate.");
439
395
  }
440
- // Re-delegate name (pail sync) capability
441
- if (config.nameDelegation) {
442
- try {
443
- const { Agent, Name } = await import("@storacha/ucn/pail");
444
- const agent = Agent.parse(config.agentKey);
445
- let name;
446
- if (config.nameArchive) {
447
- const archiveBytes = decodeDelegation(config.nameArchive);
448
- name = await Name.extract(agent, archiveBytes);
449
- }
450
- else {
451
- const nameBytes = decodeDelegation(config.nameDelegation);
452
- const { ok: nameDel } = await extractDelegation(nameBytes);
453
- if (!nameDel) {
454
- results.push("\u274c Failed to extract name delegation.");
455
- }
456
- else {
457
- name = Name.from(agent, [nameDel]);
458
- }
459
- }
460
- if (name) {
461
- const nameDel = await name.grant(targetDID);
462
- const { ok: archiveBytes } = await nameDel.archive();
463
- if (archiveBytes) {
464
- const b64 = encodeDelegation(archiveBytes);
465
- results.push("**Name delegation:**\n```\n" + b64 + "\n```");
466
- }
396
+ // Re-delegate name capability
397
+ if (deviceConfig.nameDelegation) {
398
+ const { Agent, Name } = await import("@storacha/ucn/pail");
399
+ const { extract } = await import("@storacha/client/delegation");
400
+ const agent = Agent.parse(deviceConfig.agentKey);
401
+ let name;
402
+ if (deviceConfig.nameArchive) {
403
+ const archiveBytes = decodeDelegation(deviceConfig.nameArchive);
404
+ name = await Name.extract(agent, archiveBytes);
405
+ }
406
+ else {
407
+ const nameBytes = decodeDelegation(deviceConfig.nameDelegation);
408
+ const { ok: nameDel } = await extract(nameBytes);
409
+ if (nameDel) {
410
+ name = Name.from(agent, [nameDel]);
467
411
  }
468
412
  }
469
- catch (err) {
470
- results.push(`\u274c Failed to create name delegation: ${err.message}`);
413
+ if (name) {
414
+ const nameDel = await name.grant(targetDID);
415
+ const { ok: archiveBytes } = await nameDel.archive();
416
+ if (archiveBytes) {
417
+ results.push(`Name delegation:\n${encodeDelegation(archiveBytes)}`);
418
+ }
471
419
  }
472
420
  }
473
421
  else {
474
- results.push("\u26a0\ufe0f No name delegation to re-delegate.");
422
+ results.push("⚠️ No name delegation to re-delegate.");
475
423
  }
476
- if (results.length === 0) {
477
- return { text: "Nothing to grant. Set up this device first." };
424
+ console.log(`🔥 Delegations for ${targetDID}:\n`);
425
+ for (const r of results) {
426
+ console.log(r);
427
+ console.log();
478
428
  }
479
- return {
480
- text: [
481
- `\u{1f525} Delegations for \`${targetDID}\`:`,
482
- "",
483
- ...results,
484
- "",
485
- "The target device should run:",
486
- "`/storacha-join <upload-b64> <name-b64>`",
487
- ].join("\n"),
488
- };
429
+ console.log("The target device should run:");
430
+ console.log(` openclaw clawracha join <upload-delegation> <name-delegation> --agent <id>`);
431
+ console.log("\nThen restart the gateway: `openclaw gateway restart`");
489
432
  }
490
433
  catch (err) {
491
- return {
492
- text: `\u274c Command failed: ${err.message}\n\`\`\`\n${err.stack ?? err}\n\`\`\``,
493
- };
434
+ console.error(`Error: ${err.message}`);
435
+ process.exit(1);
494
436
  }
495
- },
496
- });
497
- api.registerCommand({
498
- name: "storacha-status",
499
- description: "Show Storacha sync status",
500
- handler: async (_ctx) => {
437
+ });
438
+ // --- status ---
439
+ clawracha
440
+ .command("status")
441
+ .description("Show Storacha sync status for an agent workspace")
442
+ .requiredOption("--agent <id>", "Agent ID")
443
+ .action(async (opts) => {
501
444
  try {
502
- const workspace = workspaceDir;
503
- if (!workspace)
504
- return { text: "No workspace configured." };
505
- const config = await loadDeviceConfig(workspace);
506
- if (!config)
507
- return {
508
- text: "Not initialized. Run `/storacha-init` first.",
509
- };
510
- const lines = [
511
- "\u{1f525} Storacha Sync Status",
512
- `Agent: configured`,
513
- `Upload delegation: ${config.uploadDelegation ? "\u2705" : "\u274c not set"}`,
514
- `Name delegation: ${config.nameDelegation ? "\u2705" : "\u274c not set"}`,
515
- `Space DID: ${config.spaceDID ?? "unknown"}`,
516
- `Name Archive: ${config.nameArchive ? "saved" : "not created"}`,
517
- `Setup complete: ${config.setupComplete ? "\u2705" : "\u274c"}`,
518
- ];
519
- if (syncEngine) {
520
- const status = await syncEngine.status();
521
- lines.push(`Running: ${status.running}`, `Last Sync: ${status.lastSync
522
- ? new Date(status.lastSync).toISOString()
523
- : "never"}`, `Entries: ${status.entryCount}`, `Pending: ${status.pendingChanges}`);
445
+ const { agentId, workspace } = requireAgent(opts.agent);
446
+ const deviceConfig = await loadDeviceConfig(workspace);
447
+ if (!deviceConfig) {
448
+ console.log(`Not initialized. Run \`openclaw clawracha init --agent ${agentId}\` first.`);
449
+ return;
450
+ }
451
+ console.log(`🔥 Storacha Sync Status [${agentId}]`);
452
+ console.log(`Workspace: ${workspace}`);
453
+ console.log(`Upload delegation: ${deviceConfig.uploadDelegation ? "✅" : "❌ not set"}`);
454
+ console.log(`Name delegation: ${deviceConfig.nameDelegation ? "✅" : "❌ not set"}`);
455
+ console.log(`Space DID: ${deviceConfig.spaceDID ?? "unknown"}`);
456
+ console.log(`Name Archive: ${deviceConfig.nameArchive ? "saved" : "not created"}`);
457
+ console.log(`Setup complete: ${deviceConfig.setupComplete ? "" : ""}`);
458
+ const sync = activeSyncers.get(workspace);
459
+ if (sync) {
460
+ const status = await sync.engine.status();
461
+ console.log(`Running: true`);
462
+ console.log(`Last Sync: ${status.lastSync ? new Date(status.lastSync).toISOString() : "never"}`);
463
+ console.log(`Entries: ${status.entryCount}`);
464
+ console.log(`Pending: ${status.pendingChanges}`);
465
+ }
466
+ else {
467
+ console.log(`Running: false`);
524
468
  }
525
- return { text: lines.join("\n") };
526
469
  }
527
470
  catch (err) {
528
- return {
529
- text: `\u274c Command failed: ${err.message}\n\`\`\`\n${err.stack ?? err}\n\`\`\``,
530
- };
471
+ console.error(`Error: ${err.message}`);
472
+ process.exit(1);
531
473
  }
532
- },
533
- });
474
+ });
475
+ }, { commands: ["clawracha"] });
534
476
  }
@@ -15,4 +15,9 @@ export declare function encodeDelegation(archiveBytes: Uint8Array): string;
15
15
  * base64 string → CID → identity multihash digest → bytes
16
16
  */
17
17
  export declare function decodeDelegation(encoded: string): Uint8Array;
18
+ /**
19
+ * Read a delegation from either a file path (raw CAR) or a CID string (base64).
20
+ * Returns the extracted delegation.
21
+ */
22
+ export declare function readDelegationArg(input: string): Promise<import("@ucanto/interface").Delegation<import("@ipld/dag-ucan").Capabilities>>;
18
23
  //# sourceMappingURL=delegation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../../src/utils/delegation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,UAAU,GAAG,MAAM,CAGjE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAQ5D"}
1
+ {"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../../src/utils/delegation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,UAAU,GAAG,MAAM,CAGjE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAQ5D;AAGD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,0FAcpD"}
@@ -5,7 +5,9 @@
5
5
  * and identity multihash, encoded as base64. This matches the format
6
6
  * used by the Storacha CLI (`storacha delegation create --base64`).
7
7
  */
8
+ import * as fs from "node:fs/promises";
8
9
  import { CID } from "multiformats/cid";
10
+ import { extract } from "@storacha/client/delegation";
9
11
  import { base64 } from "multiformats/bases/base64";
10
12
  import { identity } from "multiformats/hashes/identity";
11
13
  /** CAR codec code (multicodec 0x0202) */
@@ -29,3 +31,23 @@ export function decodeDelegation(encoded) {
29
31
  }
30
32
  return cid.multihash.digest;
31
33
  }
34
+ /**
35
+ * Read a delegation from either a file path (raw CAR) or a CID string (base64).
36
+ * Returns the extracted delegation.
37
+ */
38
+ export async function readDelegationArg(input) {
39
+ let bytes;
40
+ try {
41
+ // Try reading as file first
42
+ bytes = await fs.readFile(input);
43
+ }
44
+ catch {
45
+ // Not a file — treat as CID string
46
+ bytes = decodeDelegation(input);
47
+ }
48
+ const { ok: delegation, error } = await extract(bytes);
49
+ if (!delegation) {
50
+ throw new Error(`Invalid delegation: ${error}`);
51
+ }
52
+ return delegation;
53
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Read .clawrachaignore from workspace root (gitignore-style).
3
+ * Returns parsed patterns, or empty array if file doesn't exist.
4
+ */
5
+ export declare function readIgnoreFile(workspace: string): Promise<string[]>;
6
+ //# sourceMappingURL=ignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ignore.d.ts","sourceRoot":"","sources":["../../src/utils/ignore.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYzE"}
@@ -0,0 +1,21 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ /**
4
+ * Read .clawrachaignore from workspace root (gitignore-style).
5
+ * Returns parsed patterns, or empty array if file doesn't exist.
6
+ */
7
+ export async function readIgnoreFile(workspace) {
8
+ const ignorePath = path.join(workspace, ".clawrachaignore");
9
+ try {
10
+ const content = await fs.readFile(ignorePath, "utf-8");
11
+ return content
12
+ .split("\n")
13
+ .map((line) => line.trim())
14
+ .filter((line) => line && !line.startsWith("#"));
15
+ }
16
+ catch (err) {
17
+ if (err.code === "ENOENT")
18
+ return [];
19
+ throw err;
20
+ }
21
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Agent workspace resolution.
3
+ *
4
+ * Mirrors OpenClaw's resolveAgentWorkspaceDir logic from
5
+ * src/agents/agent-scope.ts — resolves workspace dir for a given agent ID.
6
+ */
7
+ import type { OpenClawConfig } from "openclaw/plugin-sdk";
8
+ /**
9
+ * Resolve the workspace directory for an agent by ID.
10
+ * Matches OpenClaw's resolution order:
11
+ * 1. Agent-specific workspace from config
12
+ * 2. For default agent: agents.defaults.workspace or ~/.openclaw/workspace
13
+ * 3. For other agents: ~/.openclaw/workspace-{agentId}
14
+ */
15
+ export declare function resolveAgentWorkspace(config: OpenClawConfig, agentId: string): string;
16
+ /**
17
+ * Get all agent IDs from config.
18
+ */
19
+ export declare function getAgentIds(config: OpenClawConfig): string[];
20
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/utils/workspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,GACd,MAAM,CAsBR;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAQ5D"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Agent workspace resolution.
3
+ *
4
+ * Mirrors OpenClaw's resolveAgentWorkspaceDir logic from
5
+ * src/agents/agent-scope.ts — resolves workspace dir for a given agent ID.
6
+ */
7
+ import * as path from "node:path";
8
+ import * as os from "node:os";
9
+ /**
10
+ * Resolve the workspace directory for an agent by ID.
11
+ * Matches OpenClaw's resolution order:
12
+ * 1. Agent-specific workspace from config
13
+ * 2. For default agent: agents.defaults.workspace or ~/.openclaw/workspace
14
+ * 3. For other agents: ~/.openclaw/workspace-{agentId}
15
+ */
16
+ export function resolveAgentWorkspace(config, agentId) {
17
+ const agent = config.agents?.list?.find((a) => a.id.toLowerCase() === agentId.toLowerCase());
18
+ // Agent-specific workspace
19
+ if (agent?.workspace?.trim()) {
20
+ return resolveUserPath(agent.workspace.trim());
21
+ }
22
+ // Default agent gets the default workspace
23
+ const defaultId = resolveDefaultAgentId(config);
24
+ if (agentId.toLowerCase() === defaultId.toLowerCase()) {
25
+ const fallback = config.agents?.defaults?.workspace?.trim();
26
+ if (fallback) {
27
+ return resolveUserPath(fallback);
28
+ }
29
+ return path.join(resolveStateDir(), "workspace");
30
+ }
31
+ // Other agents: workspace-{id}
32
+ return path.join(resolveStateDir(), `workspace-${agentId}`);
33
+ }
34
+ /**
35
+ * Get all agent IDs from config.
36
+ */
37
+ export function getAgentIds(config) {
38
+ const list = config.agents?.list;
39
+ if (!Array.isArray(list) || list.length === 0) {
40
+ return ["default"];
41
+ }
42
+ return list
43
+ .filter((a) => a && typeof a === "object" && a.id)
44
+ .map((a) => a.id);
45
+ }
46
+ /**
47
+ * Resolve the default agent ID from config.
48
+ */
49
+ function resolveDefaultAgentId(config) {
50
+ const list = config.agents?.list;
51
+ if (!Array.isArray(list) || list.length === 0) {
52
+ return "default";
53
+ }
54
+ const defaultAgent = list.find((a) => a.default);
55
+ return (defaultAgent ?? list[0])?.id?.trim() || "default";
56
+ }
57
+ /**
58
+ * Resolve OpenClaw state directory.
59
+ */
60
+ function resolveStateDir() {
61
+ return (process.env.OPENCLAW_STATE_DIR ||
62
+ path.join(os.homedir(), ".openclaw"));
63
+ }
64
+ /**
65
+ * Expand ~ in paths.
66
+ */
67
+ function resolveUserPath(p) {
68
+ if (p.startsWith("~/")) {
69
+ return path.join(os.homedir(), p.slice(2));
70
+ }
71
+ return p;
72
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoBD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,cAAc;IAKnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAmBpB;;OAEG;YACW,KAAK;IAanB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAOlC"}
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,cAAc;IAKnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAmBpB;;OAEG;YACW,KAAK;IAanB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAOlC"}
package/dist/watcher.js CHANGED
@@ -5,27 +5,8 @@
5
5
  * before triggering sync.
6
6
  */
7
7
  import chokidar from "chokidar";
8
- import * as fs from "node:fs/promises";
8
+ import { readIgnoreFile } from "./utils/ignore.js";
9
9
  import * as path from "node:path";
10
- /**
11
- * Read .clawrachaignore from workspace root (gitignore-style).
12
- * Returns parsed patterns, or empty array if file doesn't exist.
13
- */
14
- async function readIgnoreFile(workspace) {
15
- const ignorePath = path.join(workspace, ".clawrachaignore");
16
- try {
17
- const content = await fs.readFile(ignorePath, "utf-8");
18
- return content
19
- .split("\n")
20
- .map((line) => line.trim())
21
- .filter((line) => line && !line.startsWith("#"));
22
- }
23
- catch (err) {
24
- if (err.code === "ENOENT")
25
- return [];
26
- throw err;
27
- }
28
- }
29
10
  export class FileWatcher {
30
11
  watcher = null;
31
12
  pendingChanges = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storacha/clawracha",
3
- "version": "0.0.11-rc.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenClaw plugin for Storacha workspace sync via UCN Pail",
5
5
  "type": "module",
6
6
  "files": [
@@ -36,7 +36,8 @@
36
36
  "@web3-storage/pail": "0.6.3-rc.3",
37
37
  "carstream": "^2.3.0",
38
38
  "chokidar": "^3.6.0",
39
- "multiformats": "^13.3.6"
39
+ "glob": "^13.0.5",
40
+ "multiformats": "^13.0.0"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@ipld/unixfs": "^3.0.0",