feat(plugin): fabric-canvas tool; fabric-register env=AGENT_ID only
- bin/fabric-register.mjs: only AGENT_ID is read from the environment; --api-key is flag-only (no FABRIC_API_KEY); dropped FABRIC_CENTER_API_BASE / FABRIC_IDENTITY_FILE / OPENCLAW_PATH env fallbacks (flags + sensible defaults; --center still falls back to openclaw.json). - New fabric-canvas tool (one tool, four actions): read / share / update / close the channel's single pinned canvas. Backed by FabricClient get/share/update/removeCanvas (GET/PUT/PATCH/DELETE; empty 2xx body -> null). update/close are sharer-only server-side. - README updated. Verified: client-level smoke against the running guild — read(empty→null) → share(v1) → read → update(v2) → close(→null) all pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
38
dist/fabric/src/fabric-client.js
vendored
38
dist/fabric/src/fabric-client.js
vendored
@@ -21,6 +21,24 @@ export class FabricClient {
|
||||
}
|
||||
return (await res.json());
|
||||
}
|
||||
// Generic JSON request (GET/PUT/PATCH/DELETE). Empty 2xx body -> null
|
||||
// (Fabric returns an empty body when a channel has no canvas).
|
||||
async req(method, url, auth, body) {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
||||
...(auth ? { authorization: `Bearer ${auth}` } : {}),
|
||||
},
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
throw new Error(`${method} ${url} -> ${res.status} ${text}`);
|
||||
}
|
||||
const text = await res.text();
|
||||
return (text ? JSON.parse(text) : null);
|
||||
}
|
||||
// Exchange an agent API key for a Fabric user session (+ guild tokens).
|
||||
agentLogin(apiKey) {
|
||||
return this.post(`${this.centerApiBase}/auth/agent/login`, { apiKey });
|
||||
@@ -50,4 +68,24 @@ export class FabricClient {
|
||||
joinChannel(guildEndpoint, guildToken, channelId) {
|
||||
return this.post(`${guildEndpoint}/api/channels/${channelId}/join`, {}, guildToken);
|
||||
}
|
||||
// ---- channel canvas (one pinned doc per channel) ----
|
||||
canvasUrl(endpoint, channelId) {
|
||||
return `${endpoint}/api/channels/${channelId}/canvas`;
|
||||
}
|
||||
// null when the channel has no canvas
|
||||
getCanvas(endpoint, token, channelId) {
|
||||
return this.req('GET', this.canvasUrl(endpoint, channelId), token);
|
||||
}
|
||||
// share / replace (caller becomes the sharer)
|
||||
shareCanvas(endpoint, token, channelId, body) {
|
||||
return this.req('PUT', this.canvasUrl(endpoint, channelId), token, body);
|
||||
}
|
||||
// update in place (original sharer only — else the guild returns 403)
|
||||
updateCanvas(endpoint, token, channelId, body) {
|
||||
return this.req('PATCH', this.canvasUrl(endpoint, channelId), token, body);
|
||||
}
|
||||
// remove ("close") the canvas (original sharer only)
|
||||
removeCanvas(endpoint, token, channelId) {
|
||||
return this.req('DELETE', this.canvasUrl(endpoint, channelId), token);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user