@allbaseai/openclaw-allbase 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # OpenClaw Allbase Plugin
2
2
 
3
- Connect OpenClaw agents to Allbase profiles (workspace/agent/api key), with per-agent mapping.
3
+ Connect OpenClaw agents to Allbase profiles with **OAuth (recommended)** and env/api-key fallback.
4
4
 
5
5
  ## What it supports
6
6
 
7
7
  - Map each OpenClaw agent to a different Allbase profile
8
- - Or map all OpenClaw agents to the same Allbase profile
8
+ - OAuth-first auth (access token or refresh-token flow)
9
+ - Env/api-key fallback for server-side automation
9
10
  - Tools exposed in agent runtime:
10
11
  - `allbase` with actions:
11
12
  - `status`
@@ -18,7 +19,7 @@ Connect OpenClaw agents to Allbase profiles (workspace/agent/api key), with per-
18
19
  ## Install (npm)
19
20
 
20
21
  ```bash
21
- openclaw plugins install @allbase/openclaw-allbase
22
+ openclaw plugins install @allbaseai/openclaw-allbase
22
23
  ```
23
24
 
24
25
  ## Install (local path)
@@ -31,9 +32,45 @@ openclaw plugins install -l /home/jason/Documents/botscripts/skills/openclaw-all
31
32
 
32
33
  Then restart gateway.
33
34
 
34
- ## Config example
35
+ ## Config path
35
36
 
36
- Put this under `plugins.entries.allbase.config`:
37
+ Use `plugins.entries.openclaw-allbase.config`.
38
+
39
+ ## OAuth config (recommended)
40
+
41
+ ```json5
42
+ {
43
+ baseUrl: "https://allbase.ai",
44
+ defaultProfile: "team",
45
+ profiles: {
46
+ team: {
47
+ // Option A: direct access token
48
+ oauthAccessToken: "eyJ...",
49
+
50
+ // Option B: refresh flow
51
+ // oauthRefreshToken: "...",
52
+ // oauthClientId: "oac-...",
53
+ // oauthTokenEndpoint: "https://allbase.ai/oauth/token"
54
+ }
55
+ },
56
+ mapByOpenclawAgent: {
57
+ "alan-allbase": "team"
58
+ }
59
+ }
60
+ ```
61
+
62
+ With OAuth, users can authorize/select their Allbase agent upstream and avoid static env wiring.
63
+
64
+ ## Env/API-key fallback (secondary)
65
+
66
+ If OAuth is not configured, the plugin can use profile credentials or env vars:
67
+
68
+ - `ALLBASE_WORKSPACE_ID`
69
+ - `ALLBASE_AGENT_ID`
70
+ - `ALLBASE_AGENT_API_KEY` (or `ALLBASE_API_KEY`)
71
+ - optional: `ALLBASE_BASE_URL`, `ALLBASE_TOKEN_PATH`
72
+
73
+ Profile-based api-key config still works:
37
74
 
38
75
  ```json5
39
76
  {
@@ -45,16 +82,7 @@ Put this under `plugins.entries.allbase.config`:
45
82
  workspaceId: "ws-TEAM",
46
83
  agentId: "team-agent",
47
84
  apiKey: "abk_..."
48
- },
49
- personal: {
50
- workspaceId: "ws-PERSONAL",
51
- agentId: "jason-agent",
52
- apiKey: "abk_..."
53
85
  }
54
- },
55
- mapByOpenclawAgent: {
56
- "alan-allbase": "team",
57
- "assistant-personal": "personal"
58
86
  }
59
87
  }
60
88
  ```
@@ -72,6 +100,6 @@ Optional `profile` parameter overrides automatic mapping for one call.
72
100
 
73
101
  ## Notes
74
102
 
75
- - Keep API keys private.
76
- - If token exchange fails, verify workspace_id + agent_id + api_key belong together.
103
+ - Plugin id is `openclaw-allbase` (use this id in `plugins.entries.*`).
104
+ - Keep tokens/API keys private.
77
105
  - `upload_document` requires local file path available to the gateway host.
package/index.ts CHANGED
@@ -7,10 +7,15 @@ import { basename } from "node:path";
7
7
  type AgentResult = { content: Array<{ type: string; text: string }>; details?: unknown };
8
8
 
9
9
  type AllbaseProfile = {
10
- workspaceId: string;
11
- agentId: string;
12
- apiKey: string;
10
+ workspaceId?: string;
11
+ agentId?: string;
12
+ apiKey?: string;
13
13
  baseUrl?: string;
14
+ oauthAccessToken?: string;
15
+ oauthRefreshToken?: string;
16
+ oauthClientId?: string;
17
+ oauthTokenEndpoint?: string;
18
+ oauthScope?: string;
14
19
  };
15
20
 
16
21
  type AllbasePluginConfig = {
@@ -46,7 +51,7 @@ const AllbaseToolSchema = Type.Object(
46
51
  tag: Type.Optional(Type.String({ description: "Single tag filter" })),
47
52
 
48
53
  filePath: Type.Optional(Type.String({ description: "Local file path to upload" })),
49
- title: Type.Optional(Type.String({ description: "Optional document title" }))
54
+ title: Type.Optional(Type.String({ description: "Optional document title" })),
50
55
  },
51
56
  { additionalProperties: false },
52
57
  );
@@ -73,10 +78,24 @@ function getConfig(api: OpenClawPluginApi): AllbasePluginConfig {
73
78
  }
74
79
 
75
80
  function resolveBaseUrl(cfg: AllbasePluginConfig, profile?: AllbaseProfile): string {
76
- const base = (profile?.baseUrl || cfg.baseUrl || "https://allbase.ai").trim();
81
+ const base = (profile?.baseUrl || cfg.baseUrl || process.env.ALLBASE_BASE_URL || "https://allbase.ai").trim();
77
82
  return base.replace(/\/+$/, "");
78
83
  }
79
84
 
85
+ function getEnvFallbackProfile(): AllbaseProfile {
86
+ return {
87
+ workspaceId: process.env.ALLBASE_WORKSPACE_ID,
88
+ agentId: process.env.ALLBASE_AGENT_ID,
89
+ apiKey: process.env.ALLBASE_AGENT_API_KEY || process.env.ALLBASE_API_KEY,
90
+ oauthAccessToken: process.env.ALLBASE_OAUTH_ACCESS_TOKEN,
91
+ oauthRefreshToken: process.env.ALLBASE_OAUTH_REFRESH_TOKEN,
92
+ oauthClientId: process.env.ALLBASE_OAUTH_CLIENT_ID,
93
+ oauthTokenEndpoint: process.env.ALLBASE_OAUTH_TOKEN_ENDPOINT,
94
+ oauthScope: process.env.ALLBASE_OAUTH_SCOPE,
95
+ baseUrl: process.env.ALLBASE_BASE_URL,
96
+ };
97
+ }
98
+
80
99
  function resolveProfile(
81
100
  api: OpenClawPluginApi,
82
101
  ctx: OpenClawPluginToolContext,
@@ -89,52 +108,125 @@ function resolveProfile(
89
108
  (profileOverride && profileOverride.trim()) ||
90
109
  (ctx.agentId ? cfg.mapByOpenclawAgent?.[ctx.agentId] : undefined) ||
91
110
  cfg.defaultProfile ||
92
- Object.keys(profiles)[0];
111
+ Object.keys(profiles)[0] ||
112
+ "env";
113
+
114
+ const fromConfig = profiles[selectedName];
115
+ const profile = selectedName === "env" ? { ...getEnvFallbackProfile(), ...(fromConfig || {}) } : fromConfig;
93
116
 
94
- if (!selectedName) {
117
+ if (!profile) {
95
118
  throw new Error(
96
- "No Allbase profile resolved. Configure plugins.entries.allbase.config.profiles and defaultProfile.",
119
+ "No Allbase profile resolved. Configure plugins.entries.openclaw-allbase.config.profiles or use env fallback variables.",
97
120
  );
98
121
  }
99
122
 
100
- const profile = profiles[selectedName];
101
- if (!profile) {
102
- throw new Error(`Configured profile not found: ${selectedName}`);
103
- }
123
+ const hasOAuth = Boolean(profile.oauthAccessToken || profile.oauthRefreshToken);
124
+ const hasApiKey = Boolean(profile.workspaceId && profile.agentId && profile.apiKey);
104
125
 
105
- if (!profile.workspaceId || !profile.agentId || !profile.apiKey) {
106
- throw new Error(`Profile ${selectedName} is missing workspaceId/agentId/apiKey`);
126
+ if (!hasOAuth && !hasApiKey) {
127
+ throw new Error(
128
+ `Profile ${selectedName} is missing auth config. Provide OAuth (oauthAccessToken/refresh) or api credentials (workspaceId/agentId/apiKey).`,
129
+ );
107
130
  }
108
131
 
109
132
  const baseUrl = resolveBaseUrl(cfg, profile);
110
- const tokenPath = (cfg.tokenPath || "/token").trim() || "/token";
133
+ const tokenPath = (cfg.tokenPath || process.env.ALLBASE_TOKEN_PATH || "/token").trim() || "/token";
111
134
 
112
135
  return { profileName: selectedName, profile, baseUrl, tokenPath };
113
136
  }
114
137
 
138
+ function jwtExpMs(token: string): number | null {
139
+ try {
140
+ const part = token.split(".")[1];
141
+ if (!part) return null;
142
+ const payload = JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
143
+ if (typeof payload?.exp === "number") return payload.exp * 1000;
144
+ } catch {
145
+ // ignore parse issues
146
+ }
147
+ return null;
148
+ }
149
+
150
+ async function getOAuthToken(baseUrl: string, profile: AllbaseProfile): Promise<{ token: string; exp: number }> {
151
+ const directToken = profile.oauthAccessToken?.trim();
152
+ if (directToken) {
153
+ return { token: directToken, exp: jwtExpMs(directToken) ?? Date.now() + 15 * 60_000 };
154
+ }
155
+
156
+ const refreshToken = profile.oauthRefreshToken?.trim();
157
+ const clientId = profile.oauthClientId?.trim();
158
+ if (!refreshToken || !clientId) {
159
+ throw new Error("OAuth config requires oauthAccessToken or oauthRefreshToken+oauthClientId");
160
+ }
161
+
162
+ const tokenEndpoint = (profile.oauthTokenEndpoint || `${baseUrl}/oauth/token`).trim();
163
+ const body = new URLSearchParams({
164
+ grant_type: "refresh_token",
165
+ refresh_token: refreshToken,
166
+ client_id: clientId,
167
+ });
168
+ if (profile.oauthScope?.trim()) body.set("scope", profile.oauthScope.trim());
169
+
170
+ const response = await fetch(tokenEndpoint, {
171
+ method: "POST",
172
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
173
+ body: body.toString(),
174
+ });
175
+
176
+ if (!response.ok) {
177
+ throw new Error(`OAuth refresh failed (${response.status}): ${await response.text()}`);
178
+ }
179
+
180
+ const data = (await response.json()) as { access_token?: string; expires_in?: number };
181
+ if (!data.access_token) throw new Error("OAuth refresh did not return access_token");
182
+
183
+ const exp = Date.now() + Math.max(60_000, (data.expires_in ?? 3600) * 1000);
184
+ return { token: data.access_token, exp };
185
+ }
186
+
115
187
  async function getAccessToken(
116
188
  api: OpenClawPluginApi,
117
189
  ctx: OpenClawPluginToolContext,
118
190
  profileOverride?: string,
119
- ): Promise<{ token: string; resolved: ReturnType<typeof resolveProfile> }> {
191
+ ): Promise<{ token: string; resolved: ReturnType<typeof resolveProfile>; authMode: "oauth" | "api_key" }> {
120
192
  const resolved = resolveProfile(api, ctx, profileOverride);
121
193
  const { profile, baseUrl, tokenPath } = resolved;
122
- const cacheKey = `${baseUrl}|${profile.workspaceId}|${profile.agentId}|${profile.apiKey}`;
194
+
195
+ const hasOAuth = Boolean(profile.oauthAccessToken || profile.oauthRefreshToken);
196
+ const hasApiKey = Boolean(profile.workspaceId && profile.agentId && profile.apiKey);
197
+
198
+ if (hasOAuth) {
199
+ const cacheKey = `oauth|${baseUrl}|${resolved.profileName}|${profile.oauthClientId || ""}`;
200
+ const now = Date.now();
201
+ const cached = tokenCache.get(cacheKey);
202
+ if (cached && cached.exp > now + 30_000) return { token: cached.token, resolved, authMode: "oauth" };
203
+
204
+ const refreshed = await getOAuthToken(baseUrl, profile);
205
+ tokenCache.set(cacheKey, refreshed);
206
+ return { token: refreshed.token, resolved, authMode: "oauth" };
207
+ }
208
+
209
+ if (!hasApiKey) {
210
+ throw new Error("No usable auth mode found. Configure OAuth or workspaceId/agentId/apiKey.");
211
+ }
212
+
213
+ const cacheKey = `apikey|${baseUrl}|${profile.workspaceId}|${profile.agentId}|${profile.apiKey}`;
123
214
  const now = Date.now();
124
215
  const cached = tokenCache.get(cacheKey);
125
216
  if (cached && cached.exp > now + 30_000) {
126
- return { token: cached.token, resolved };
217
+ return { token: cached.token, resolved, authMode: "api_key" };
127
218
  }
128
219
 
129
220
  const url = new URL(`${baseUrl}${tokenPath}`);
130
- url.searchParams.set("workspace_id", profile.workspaceId);
131
- url.searchParams.set("agent_id", profile.agentId);
132
- url.searchParams.set("api_key", profile.apiKey);
221
+ url.searchParams.set("workspace_id", profile.workspaceId!);
222
+ url.searchParams.set("agent_id", profile.agentId!);
223
+ url.searchParams.set("api_key", profile.apiKey!);
133
224
 
134
225
  const response = await fetch(url, { method: "POST" });
135
226
  if (!response.ok) {
136
227
  throw new Error(`Token exchange failed (${response.status}): ${await response.text()}`);
137
228
  }
229
+
138
230
  const data = (await response.json()) as { access_token?: string; expires_in?: number };
139
231
  if (!data.access_token) {
140
232
  throw new Error("Token exchange did not return access_token");
@@ -142,7 +234,7 @@ async function getAccessToken(
142
234
 
143
235
  const ttlMs = Math.max(60_000, (data.expires_in ?? 3600) * 1000);
144
236
  tokenCache.set(cacheKey, { token: data.access_token, exp: now + ttlMs });
145
- return { token: data.access_token, resolved };
237
+ return { token: data.access_token, resolved, authMode: "api_key" };
146
238
  }
147
239
 
148
240
  async function callAllbase(
@@ -152,7 +244,7 @@ async function callAllbase(
152
244
  path: string,
153
245
  init: RequestInit,
154
246
  ) {
155
- const { token, resolved } = await getAccessToken(api, ctx, profileOverride);
247
+ const { token, resolved, authMode } = await getAccessToken(api, ctx, profileOverride);
156
248
  const response = await fetch(`${resolved.baseUrl}${path}`, {
157
249
  ...init,
158
250
  headers: {
@@ -169,7 +261,7 @@ async function callAllbase(
169
261
  // keep text body
170
262
  }
171
263
 
172
- return { ok: response.ok, status: response.status, body, resolved };
264
+ return { ok: response.ok, status: response.status, body, resolved, authMode };
173
265
  }
174
266
 
175
267
  async function executeAllbaseTool(
@@ -198,15 +290,17 @@ async function executeAllbaseTool(
198
290
  if (action === "status") {
199
291
  const cfg = getConfig(api);
200
292
  const resolved = resolveProfile(api, ctx, profileOverride);
293
+ const auth = await getAccessToken(api, ctx, profileOverride);
201
294
  const healthResp = await fetch(`${resolveBaseUrl(cfg, resolved.profile)}/healthz`);
202
295
  const health = await healthResp.json().catch(() => ({}));
203
296
  return json({
204
297
  ok: true,
205
298
  profile: resolved.profileName,
206
299
  openclawAgentId: ctx.agentId || null,
207
- workspaceId: resolved.profile.workspaceId,
208
- allbaseAgentId: resolved.profile.agentId,
300
+ workspaceId: resolved.profile.workspaceId || null,
301
+ allbaseAgentId: resolved.profile.agentId || null,
209
302
  baseUrl: resolved.baseUrl,
303
+ authMode: auth.authMode,
210
304
  health,
211
305
  });
212
306
  }
@@ -221,7 +315,7 @@ async function executeAllbaseTool(
221
315
  tags: parseTags(params.tags),
222
316
  visibility: params.visibility === "public" ? "public" : "private",
223
317
  is_verified: false,
224
- metadata: { source: "openclaw-plugin", plugin: "allbase" },
318
+ metadata: { source: "openclaw-plugin", plugin: "openclaw-allbase" },
225
319
  };
226
320
 
227
321
  const result = await callAllbase(api, ctx, profileOverride, "/memories", {
@@ -279,7 +373,7 @@ async function executeAllbaseTool(
279
373
  form.append("file", new Blob([fileData]), basename(filePath));
280
374
  if (params.title?.trim()) form.append("title", params.title.trim());
281
375
 
282
- const { token, resolved } = await getAccessToken(api, ctx, profileOverride);
376
+ const { token, resolved, authMode } = await getAccessToken(api, ctx, profileOverride);
283
377
  const response = await fetch(`${resolved.baseUrl}/documents/upload`, {
284
378
  method: "POST",
285
379
  headers: { Authorization: `Bearer ${token}` },
@@ -292,25 +386,25 @@ async function executeAllbaseTool(
292
386
  } catch {
293
387
  // keep text
294
388
  }
295
- return json({ ok: response.ok, status: response.status, body, resolved });
389
+ return json({ ok: response.ok, status: response.status, body, resolved, authMode });
296
390
  }
297
391
 
298
392
  throw new Error(`Unsupported action: ${String(action)}`);
299
393
  } catch (error) {
300
394
  const message = error instanceof Error ? error.message : String(error);
301
- api.logger.warn(`[allbase] tool error: ${message}`);
395
+ api.logger.warn(`[openclaw-allbase] tool error: ${message}`);
302
396
  return json({ ok: false, error: message });
303
397
  }
304
398
  }
305
399
 
306
400
  const plugin = {
307
- id: "allbase",
401
+ id: "openclaw-allbase",
308
402
  name: "Allbase",
309
403
  description: "Allbase memory/document tools with per-agent profile mapping",
310
404
  configSchema: emptyPluginConfigSchema(),
311
405
  register(api: OpenClawPluginApi) {
312
406
  api.registerTool(
313
- ((ctx: OpenClawPluginToolContext) => {
407
+ (ctx: OpenClawPluginToolContext) => {
314
408
  return {
315
409
  name: "allbase",
316
410
  label: "Allbase",
@@ -319,7 +413,7 @@ const plugin = {
319
413
  parameters: AllbaseToolSchema,
320
414
  execute: (toolCallId: string, params: any) => executeAllbaseTool(api, ctx, toolCallId, params),
321
415
  } as AnyAgentTool;
322
- }),
416
+ },
323
417
  { optional: true },
324
418
  );
325
419
 
@@ -328,16 +422,18 @@ const plugin = {
328
422
  description: "Show resolved Allbase profile for this agent",
329
423
  acceptsArgs: true,
330
424
  requireAuth: true,
331
- handler: (cmdCtx) => {
425
+ handler: async (cmdCtx) => {
332
426
  const profileOverride = (cmdCtx.args || "").trim() || undefined;
333
427
  try {
334
428
  const resolved = resolveProfile(api, { agentId: cmdCtx.config.defaultAgentId }, profileOverride);
429
+ const auth = await getAccessToken(api, { agentId: cmdCtx.config.defaultAgentId }, profileOverride);
335
430
  return {
336
431
  text:
337
432
  `Allbase profile: ${resolved.profileName}\n` +
338
433
  `Base URL: ${resolved.baseUrl}\n` +
339
- `Workspace: ${resolved.profile.workspaceId}\n` +
340
- `Allbase Agent: ${resolved.profile.agentId}`,
434
+ `Auth mode: ${auth.authMode}\n` +
435
+ `Workspace: ${resolved.profile.workspaceId || "(from OAuth)"}\n` +
436
+ `Allbase Agent: ${resolved.profile.agentId || "(from OAuth)"}`,
341
437
  };
342
438
  } catch (err) {
343
439
  return { text: `Allbase profile resolution failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "allbase",
2
+ "id": "openclaw-allbase",
3
3
  "name": "Allbase",
4
4
  "description": "Allbase memory/document tools with per-OpenClaw-agent profile mapping.",
5
5
  "uiHints": {
@@ -11,7 +11,12 @@
11
11
  "profiles.*.workspaceId": { "label": "Workspace ID" },
12
12
  "profiles.*.agentId": { "label": "Allbase Agent ID" },
13
13
  "profiles.*.apiKey": { "label": "Allbase API Key", "sensitive": true },
14
- "profiles.*.baseUrl": { "label": "Profile Base URL", "advanced": true }
14
+ "profiles.*.baseUrl": { "label": "Profile Base URL", "advanced": true },
15
+ "profiles.*.oauthAccessToken": { "label": "OAuth Access Token", "sensitive": true },
16
+ "profiles.*.oauthRefreshToken": { "label": "OAuth Refresh Token", "sensitive": true },
17
+ "profiles.*.oauthClientId": { "label": "OAuth Client ID" },
18
+ "profiles.*.oauthTokenEndpoint": { "label": "OAuth Token Endpoint", "advanced": true },
19
+ "profiles.*.oauthScope": { "label": "OAuth Scope", "advanced": true }
15
20
  },
16
21
  "configSchema": {
17
22
  "type": "object",
@@ -25,12 +30,16 @@
25
30
  "additionalProperties": {
26
31
  "type": "object",
27
32
  "additionalProperties": false,
28
- "required": ["workspaceId", "agentId", "apiKey"],
29
33
  "properties": {
30
34
  "workspaceId": { "type": "string" },
31
35
  "agentId": { "type": "string" },
32
36
  "apiKey": { "type": "string" },
33
- "baseUrl": { "type": "string" }
37
+ "baseUrl": { "type": "string" },
38
+ "oauthAccessToken": { "type": "string" },
39
+ "oauthRefreshToken": { "type": "string" },
40
+ "oauthClientId": { "type": "string" },
41
+ "oauthTokenEndpoint": { "type": "string" },
42
+ "oauthScope": { "type": "string" }
34
43
  }
35
44
  }
36
45
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allbaseai/openclaw-allbase",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin to map OpenClaw agents to Allbase agent/workspace profiles.",
6
6
  "license": "MIT",