@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 +16 -14
- package/dist/plugin.d.ts +2 -5
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +328 -386
- package/dist/utils/delegation.d.ts +5 -0
- package/dist/utils/delegation.d.ts.map +1 -1
- package/dist/utils/delegation.js +22 -0
- package/dist/utils/ignore.d.ts +6 -0
- package/dist/utils/ignore.d.ts.map +1 -0
- package/dist/utils/ignore.js +21 -0
- package/dist/utils/workspace.d.ts +20 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +72 -0
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +1 -20
- package/package.json +3 -2
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
48
|
+
```bash
|
|
49
|
+
openclaw clawracha join <upload-delegation> <name-delegation> --agent <id>
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
Get both delegations by running
|
|
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
|
-
|
|
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.
|
|
60
|
+
Generates upload and name delegations for the target device.
|
|
59
61
|
|
|
60
62
|
### Check status
|
|
61
63
|
|
|
62
|
-
```
|
|
63
|
-
|
|
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
|
|
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
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
99
|
-
await
|
|
100
|
-
|
|
114
|
+
for (const [workspace, sync] of activeSyncers) {
|
|
115
|
+
await sync.watcher.stop();
|
|
116
|
+
ctx.logger.info(`[${sync.agentId}] Stopped syncing: ${workspace}`);
|
|
101
117
|
}
|
|
102
|
-
|
|
103
|
-
ctx.logger.info("Stopped");
|
|
118
|
+
activeSyncers.clear();
|
|
104
119
|
},
|
|
105
120
|
});
|
|
106
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
151
|
-
const status = await
|
|
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
|
-
// ---
|
|
164
|
-
api.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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 =
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
};
|
|
233
|
+
console.error(`Error: ${err.message}`);
|
|
234
|
+
process.exit(1);
|
|
214
235
|
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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 =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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 (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
};
|
|
250
|
+
if (deviceConfig.setupComplete) {
|
|
251
|
+
console.log("Setup already complete.");
|
|
252
|
+
return;
|
|
236
253
|
}
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
263
|
-
|
|
264
|
-
|
|
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(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
};
|
|
300
|
+
console.error(`Error: ${err.message}`);
|
|
301
|
+
process.exit(1);
|
|
289
302
|
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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 =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
|
319
|
-
const
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
};
|
|
354
|
+
console.error(`Error: ${err.message}`);
|
|
355
|
+
process.exit(1);
|
|
393
356
|
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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 =
|
|
403
|
-
if (!
|
|
404
|
-
|
|
405
|
-
|
|
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
|
|
410
|
-
if (!
|
|
411
|
-
|
|
412
|
-
|
|
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 (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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("
|
|
394
|
+
results.push("⚠️ No upload delegation to re-delegate.");
|
|
439
395
|
}
|
|
440
|
-
// Re-delegate name
|
|
441
|
-
if (
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
470
|
-
|
|
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("
|
|
422
|
+
results.push("⚠️ No name delegation to re-delegate.");
|
|
475
423
|
}
|
|
476
|
-
|
|
477
|
-
|
|
424
|
+
console.log(`🔥 Delegations for ${targetDID}:\n`);
|
|
425
|
+
for (const r of results) {
|
|
426
|
+
console.log(r);
|
|
427
|
+
console.log();
|
|
478
428
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
492
|
-
|
|
493
|
-
};
|
|
434
|
+
console.error(`Error: ${err.message}`);
|
|
435
|
+
process.exit(1);
|
|
494
436
|
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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 =
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
529
|
-
|
|
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;
|
|
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"}
|
package/dist/utils/delegation.js
CHANGED
|
@@ -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 @@
|
|
|
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
|
+
}
|
package/dist/watcher.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
39
|
+
"glob": "^13.0.5",
|
|
40
|
+
"multiformats": "^13.0.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@ipld/unixfs": "^3.0.0",
|