@byoky/openclaw-plugin 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +501 -0
- package/openclaw.plugin.json +26 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 byoky contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { OpenClawPluginApi } from 'openclaw/plugin-sdk/core';
|
|
2
|
+
|
|
3
|
+
type PluginConfigUiHint = {
|
|
4
|
+
label?: string;
|
|
5
|
+
help?: string;
|
|
6
|
+
tags?: string[];
|
|
7
|
+
advanced?: boolean;
|
|
8
|
+
sensitive?: boolean;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
};
|
|
11
|
+
type PluginConfigValidation = {
|
|
12
|
+
ok: true;
|
|
13
|
+
value?: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
errors: string[];
|
|
17
|
+
};
|
|
18
|
+
type OpenClawPluginConfigSchema = {
|
|
19
|
+
safeParse?: (value: unknown) => {
|
|
20
|
+
success: boolean;
|
|
21
|
+
data?: unknown;
|
|
22
|
+
error?: {
|
|
23
|
+
issues?: Array<{
|
|
24
|
+
path: Array<string | number>;
|
|
25
|
+
message: string;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
parse?: (value: unknown) => unknown;
|
|
30
|
+
validate?: (value: unknown) => PluginConfigValidation;
|
|
31
|
+
uiHints?: Record<string, PluginConfigUiHint>;
|
|
32
|
+
jsonSchema?: Record<string, unknown>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
declare const byokyPlugin: {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
configSchema: OpenClawPluginConfigSchema;
|
|
40
|
+
register(api: OpenClawPluginApi): void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export { byokyPlugin as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
emptyPluginConfigSchema
|
|
4
|
+
} from "openclaw/plugin-sdk/core";
|
|
5
|
+
import { createServer } from "http";
|
|
6
|
+
var DEFAULT_BRIDGE_PORT = 19280;
|
|
7
|
+
var ANTHROPIC_MODELS = [
|
|
8
|
+
{
|
|
9
|
+
id: "claude-opus-4-20250514",
|
|
10
|
+
name: "Claude Opus 4",
|
|
11
|
+
reasoning: true,
|
|
12
|
+
input: ["text", "image"],
|
|
13
|
+
cost: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
14
|
+
contextWindow: 2e5,
|
|
15
|
+
maxTokens: 32e3
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "claude-sonnet-4-20250514",
|
|
19
|
+
name: "Claude Sonnet 4",
|
|
20
|
+
reasoning: true,
|
|
21
|
+
input: ["text", "image"],
|
|
22
|
+
cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
23
|
+
contextWindow: 2e5,
|
|
24
|
+
maxTokens: 16e3
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "claude-haiku-4-5-20251001",
|
|
28
|
+
name: "Claude Haiku 4.5",
|
|
29
|
+
reasoning: false,
|
|
30
|
+
input: ["text", "image"],
|
|
31
|
+
cost: { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
|
|
32
|
+
contextWindow: 2e5,
|
|
33
|
+
maxTokens: 8192
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
var OPENAI_MODELS = [
|
|
37
|
+
{
|
|
38
|
+
id: "gpt-4.1",
|
|
39
|
+
name: "GPT-4.1",
|
|
40
|
+
reasoning: false,
|
|
41
|
+
input: ["text", "image"],
|
|
42
|
+
cost: { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
|
|
43
|
+
contextWindow: 1047576,
|
|
44
|
+
maxTokens: 32768
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "o3",
|
|
48
|
+
name: "o3",
|
|
49
|
+
reasoning: true,
|
|
50
|
+
input: ["text", "image"],
|
|
51
|
+
cost: { input: 10, output: 40, cacheRead: 2.5, cacheWrite: 0 },
|
|
52
|
+
contextWindow: 2e5,
|
|
53
|
+
maxTokens: 1e5
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "o4-mini",
|
|
57
|
+
name: "o4-mini",
|
|
58
|
+
reasoning: true,
|
|
59
|
+
input: ["text", "image"],
|
|
60
|
+
cost: { input: 1.1, output: 4.4, cacheRead: 0.275, cacheWrite: 0 },
|
|
61
|
+
contextWindow: 2e5,
|
|
62
|
+
maxTokens: 1e5
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "gpt-4.1-mini",
|
|
66
|
+
name: "GPT-4.1 Mini",
|
|
67
|
+
reasoning: false,
|
|
68
|
+
input: ["text", "image"],
|
|
69
|
+
cost: { input: 0.4, output: 1.6, cacheRead: 0.1, cacheWrite: 0 },
|
|
70
|
+
contextWindow: 1047576,
|
|
71
|
+
maxTokens: 32768
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
var GEMINI_MODELS = [
|
|
75
|
+
{
|
|
76
|
+
id: "gemini-2.5-pro",
|
|
77
|
+
name: "Gemini 2.5 Pro",
|
|
78
|
+
reasoning: true,
|
|
79
|
+
input: ["text", "image"],
|
|
80
|
+
cost: { input: 1.25, output: 10, cacheRead: 0.315, cacheWrite: 0 },
|
|
81
|
+
contextWindow: 1048576,
|
|
82
|
+
maxTokens: 65536
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "gemini-2.5-flash",
|
|
86
|
+
name: "Gemini 2.5 Flash",
|
|
87
|
+
reasoning: true,
|
|
88
|
+
input: ["text", "image"],
|
|
89
|
+
cost: { input: 0.15, output: 0.6, cacheRead: 0.0375, cacheWrite: 0 },
|
|
90
|
+
contextWindow: 1048576,
|
|
91
|
+
maxTokens: 65536
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
var DEEPSEEK_MODELS = [
|
|
95
|
+
{
|
|
96
|
+
id: "deepseek-chat",
|
|
97
|
+
name: "DeepSeek V3",
|
|
98
|
+
reasoning: false,
|
|
99
|
+
input: ["text"],
|
|
100
|
+
cost: { input: 0.27, output: 1.1, cacheRead: 0.07, cacheWrite: 0 },
|
|
101
|
+
contextWindow: 65536,
|
|
102
|
+
maxTokens: 8192
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "deepseek-reasoner",
|
|
106
|
+
name: "DeepSeek R1",
|
|
107
|
+
reasoning: true,
|
|
108
|
+
input: ["text"],
|
|
109
|
+
cost: { input: 0.55, output: 2.19, cacheRead: 0.14, cacheWrite: 0 },
|
|
110
|
+
contextWindow: 65536,
|
|
111
|
+
maxTokens: 8192
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
var XAI_MODELS = [
|
|
115
|
+
{
|
|
116
|
+
id: "grok-3",
|
|
117
|
+
name: "Grok 3",
|
|
118
|
+
reasoning: false,
|
|
119
|
+
input: ["text"],
|
|
120
|
+
cost: { input: 3, output: 15, cacheRead: 0, cacheWrite: 0 },
|
|
121
|
+
contextWindow: 131072,
|
|
122
|
+
maxTokens: 16384
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "grok-3-mini",
|
|
126
|
+
name: "Grok 3 Mini",
|
|
127
|
+
reasoning: true,
|
|
128
|
+
input: ["text"],
|
|
129
|
+
cost: { input: 0.3, output: 0.5, cacheRead: 0, cacheWrite: 0 },
|
|
130
|
+
contextWindow: 131072,
|
|
131
|
+
maxTokens: 16384
|
|
132
|
+
}
|
|
133
|
+
];
|
|
134
|
+
var MISTRAL_MODELS = [
|
|
135
|
+
{
|
|
136
|
+
id: "mistral-large-latest",
|
|
137
|
+
name: "Mistral Large",
|
|
138
|
+
reasoning: false,
|
|
139
|
+
input: ["text", "image"],
|
|
140
|
+
cost: { input: 2, output: 6, cacheRead: 0, cacheWrite: 0 },
|
|
141
|
+
contextWindow: 128e3,
|
|
142
|
+
maxTokens: 8192
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
var GROQ_MODELS = [
|
|
146
|
+
{
|
|
147
|
+
id: "llama-3.3-70b-versatile",
|
|
148
|
+
name: "Llama 3.3 70B",
|
|
149
|
+
reasoning: false,
|
|
150
|
+
input: ["text"],
|
|
151
|
+
cost: { input: 0.59, output: 0.79, cacheRead: 0, cacheWrite: 0 },
|
|
152
|
+
contextWindow: 128e3,
|
|
153
|
+
maxTokens: 32768
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
var EMPTY_MODELS = [];
|
|
157
|
+
var PROVIDERS = [
|
|
158
|
+
{ id: "anthropic", name: "Anthropic", api: "anthropic-messages", models: ANTHROPIC_MODELS },
|
|
159
|
+
{ id: "openai", name: "OpenAI", api: "openai-completions", models: OPENAI_MODELS },
|
|
160
|
+
{ id: "gemini", name: "Google Gemini", api: "openai-completions", models: GEMINI_MODELS },
|
|
161
|
+
{ id: "mistral", name: "Mistral", api: "openai-completions", models: MISTRAL_MODELS },
|
|
162
|
+
{ id: "cohere", name: "Cohere", api: "openai-completions", models: EMPTY_MODELS },
|
|
163
|
+
{ id: "xai", name: "xAI (Grok)", api: "openai-completions", models: XAI_MODELS },
|
|
164
|
+
{ id: "deepseek", name: "DeepSeek", api: "openai-completions", models: DEEPSEEK_MODELS },
|
|
165
|
+
{ id: "perplexity", name: "Perplexity", api: "openai-completions", models: EMPTY_MODELS },
|
|
166
|
+
{ id: "groq", name: "Groq", api: "openai-completions", models: GROQ_MODELS },
|
|
167
|
+
{ id: "together", name: "Together AI", api: "openai-completions", models: EMPTY_MODELS },
|
|
168
|
+
{ id: "fireworks", name: "Fireworks AI", api: "openai-completions", models: EMPTY_MODELS },
|
|
169
|
+
{ id: "openrouter", name: "OpenRouter", api: "openai-completions", models: EMPTY_MODELS },
|
|
170
|
+
{ id: "replicate", name: "Replicate", api: "openai-completions", models: EMPTY_MODELS },
|
|
171
|
+
{ id: "huggingface", name: "Hugging Face", api: "openai-completions", models: EMPTY_MODELS },
|
|
172
|
+
{ id: "azure_openai", name: "Azure OpenAI", api: "openai-completions", models: EMPTY_MODELS }
|
|
173
|
+
];
|
|
174
|
+
var byokyPlugin = {
|
|
175
|
+
id: "byoky",
|
|
176
|
+
name: "Byoky Wallet",
|
|
177
|
+
description: "Route LLM API calls through your Byoky browser wallet \u2014 keys never leave the extension",
|
|
178
|
+
configSchema: emptyPluginConfigSchema(),
|
|
179
|
+
register(api) {
|
|
180
|
+
for (const provider of PROVIDERS) {
|
|
181
|
+
const openclawId = `byoky-${provider.id}`;
|
|
182
|
+
api.registerProvider({
|
|
183
|
+
id: openclawId,
|
|
184
|
+
label: `${provider.name} (via Byoky)`,
|
|
185
|
+
docsPath: "/providers/byoky",
|
|
186
|
+
auth: [
|
|
187
|
+
{
|
|
188
|
+
id: "browser",
|
|
189
|
+
label: `${provider.name} (via Byoky)`,
|
|
190
|
+
hint: "Route through Byoky wallet \u2014 key stays in extension",
|
|
191
|
+
kind: "custom",
|
|
192
|
+
run: (ctx) => runProviderAuth(ctx, provider)
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
wizard: {
|
|
196
|
+
onboarding: {
|
|
197
|
+
choiceId: openclawId,
|
|
198
|
+
choiceLabel: `${provider.name} (via Byoky)`,
|
|
199
|
+
choiceHint: "Route through Byoky wallet",
|
|
200
|
+
groupId: "byoky",
|
|
201
|
+
groupLabel: "Byoky Wallet",
|
|
202
|
+
groupHint: "Keys never leave the extension",
|
|
203
|
+
methodId: "browser"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
api.registerCommand({
|
|
209
|
+
name: "byoky",
|
|
210
|
+
description: "Show Byoky bridge status and connected providers",
|
|
211
|
+
acceptsArgs: false,
|
|
212
|
+
handler: async () => {
|
|
213
|
+
const health = await checkBridgeHealth();
|
|
214
|
+
if (!health) {
|
|
215
|
+
return {
|
|
216
|
+
text: "Byoky Bridge: **offline**\n\nStart the bridge with `openclaw models auth login --provider byoky-anthropic` or ensure it is running on port " + DEFAULT_BRIDGE_PORT + "."
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const providerList = health.providers.length > 0 ? health.providers.join(", ") : "none";
|
|
220
|
+
return {
|
|
221
|
+
text: `Byoky Bridge: **online** (port ${DEFAULT_BRIDGE_PORT})
|
|
222
|
+
Providers: ${providerList}`
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var index_default = byokyPlugin;
|
|
229
|
+
async function checkBridgeHealth() {
|
|
230
|
+
try {
|
|
231
|
+
const controller = new AbortController();
|
|
232
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
233
|
+
const res = await fetch(`http://127.0.0.1:${DEFAULT_BRIDGE_PORT}/health`, {
|
|
234
|
+
signal: controller.signal
|
|
235
|
+
});
|
|
236
|
+
clearTimeout(timeout);
|
|
237
|
+
if (!res.ok) return null;
|
|
238
|
+
const data = await res.json();
|
|
239
|
+
if (data.status !== "ok") return null;
|
|
240
|
+
return { providers: data.providers ?? [] };
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function runProviderAuth(ctx, provider) {
|
|
246
|
+
ctx.prompter.note(
|
|
247
|
+
`Opening your browser to connect ${provider.name} via Byoky wallet.
|
|
248
|
+
Unlock your wallet and approve the connection.
|
|
249
|
+
The Byoky Bridge must be installed: npm i -g @byoky/bridge && byoky-bridge install`
|
|
250
|
+
);
|
|
251
|
+
const result = await startCallbackServer(ctx, provider.id);
|
|
252
|
+
try {
|
|
253
|
+
if (!result.providers || !result.providers.includes(provider.id)) {
|
|
254
|
+
throw new Error(`${provider.name} not available in your Byoky wallet`);
|
|
255
|
+
}
|
|
256
|
+
const openclawId = `byoky-${provider.id}`;
|
|
257
|
+
return {
|
|
258
|
+
profiles: [
|
|
259
|
+
{
|
|
260
|
+
profileId: `${openclawId}:byoky`,
|
|
261
|
+
credential: {
|
|
262
|
+
type: "api_key",
|
|
263
|
+
provider: openclawId,
|
|
264
|
+
key: "byoky-proxy"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
],
|
|
268
|
+
configPatch: {
|
|
269
|
+
models: {
|
|
270
|
+
providers: {
|
|
271
|
+
[openclawId]: {
|
|
272
|
+
baseUrl: `http://127.0.0.1:${result.port}/${provider.id}`,
|
|
273
|
+
api: provider.api,
|
|
274
|
+
apiKey: "byoky-proxy",
|
|
275
|
+
models: provider.models
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
defaultModel: provider.models.length > 0 ? `${openclawId}/${provider.models[0].id}` : void 0,
|
|
281
|
+
notes: [
|
|
282
|
+
`Connected ${provider.name} via Byoky Bridge on port ${result.port}.`,
|
|
283
|
+
"Key stays in your browser extension \u2014 the bridge relays requests.",
|
|
284
|
+
"The bridge must be running for API calls to work.",
|
|
285
|
+
...provider.models.length > 0 ? [`Available models: ${provider.models.map((m) => m.id).join(", ")}`] : ["No pre-defined models \u2014 set agents.defaults.model manually."]
|
|
286
|
+
]
|
|
287
|
+
};
|
|
288
|
+
} finally {
|
|
289
|
+
result.server.close();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function startCallbackServer(ctx, requestProviderId) {
|
|
293
|
+
return new Promise((resolve) => {
|
|
294
|
+
let resolved = false;
|
|
295
|
+
const server = createServer((req, res) => {
|
|
296
|
+
const reqOrigin = req.headers.origin || "";
|
|
297
|
+
let isLocalhost = false;
|
|
298
|
+
try {
|
|
299
|
+
const parsed = new URL(reqOrigin);
|
|
300
|
+
isLocalhost = parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost";
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
res.setHeader(
|
|
304
|
+
"Access-Control-Allow-Origin",
|
|
305
|
+
isLocalhost ? reqOrigin : "http://127.0.0.1"
|
|
306
|
+
);
|
|
307
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
308
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
309
|
+
if (req.method === "OPTIONS") {
|
|
310
|
+
res.writeHead(204);
|
|
311
|
+
res.end();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (req.method === "POST" && req.url === "/callback") {
|
|
315
|
+
let body = "";
|
|
316
|
+
req.on("data", (chunk) => {
|
|
317
|
+
body += chunk.toString();
|
|
318
|
+
});
|
|
319
|
+
req.on("end", () => {
|
|
320
|
+
try {
|
|
321
|
+
const data = JSON.parse(body);
|
|
322
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
323
|
+
res.end(JSON.stringify({ ok: true }));
|
|
324
|
+
if (!resolved) {
|
|
325
|
+
resolved = true;
|
|
326
|
+
resolve({
|
|
327
|
+
providers: data.providers || [],
|
|
328
|
+
port: data.bridgePort || DEFAULT_BRIDGE_PORT,
|
|
329
|
+
server
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
} catch {
|
|
333
|
+
res.writeHead(400);
|
|
334
|
+
res.end("Invalid JSON");
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (req.method === "GET") {
|
|
340
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
341
|
+
res.end(buildAuthPage(requestProviderId));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
res.writeHead(404);
|
|
345
|
+
res.end();
|
|
346
|
+
});
|
|
347
|
+
server.listen(0, "127.0.0.1", () => {
|
|
348
|
+
const addr = server.address();
|
|
349
|
+
if (typeof addr === "object" && addr) {
|
|
350
|
+
ctx.openUrl(`http://127.0.0.1:${addr.port}`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
setTimeout(() => {
|
|
354
|
+
if (!resolved) {
|
|
355
|
+
resolved = true;
|
|
356
|
+
resolve({ providers: [], port: DEFAULT_BRIDGE_PORT, server });
|
|
357
|
+
}
|
|
358
|
+
}, 12e4);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
var VALID_PROVIDER_IDS = new Set(PROVIDERS.map((p) => p.id));
|
|
362
|
+
function buildAuthPage(requestProviderId) {
|
|
363
|
+
let providerFilter;
|
|
364
|
+
if (VALID_PROVIDER_IDS.has(requestProviderId)) {
|
|
365
|
+
providerFilter = `[{ id: ${JSON.stringify(requestProviderId)}, required: true }]`;
|
|
366
|
+
} else {
|
|
367
|
+
providerFilter = "[]";
|
|
368
|
+
}
|
|
369
|
+
return `<!DOCTYPE html>
|
|
370
|
+
<html lang="en">
|
|
371
|
+
<head>
|
|
372
|
+
<meta charset="UTF-8" />
|
|
373
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
374
|
+
<title>Byoky \u2014 Connect to OpenClaw</title>
|
|
375
|
+
<style>
|
|
376
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
377
|
+
body {
|
|
378
|
+
font-family: -apple-system, system-ui, sans-serif;
|
|
379
|
+
background: #0a0a18; color: #f5f5f7;
|
|
380
|
+
min-height: 100vh; display: flex;
|
|
381
|
+
align-items: center; justify-content: center;
|
|
382
|
+
}
|
|
383
|
+
.card {
|
|
384
|
+
background: #1c1c22; border-radius: 16px;
|
|
385
|
+
padding: 40px; max-width: 440px; width: 100%; text-align: center;
|
|
386
|
+
}
|
|
387
|
+
h1 {
|
|
388
|
+
font-size: 24px; font-weight: 700;
|
|
389
|
+
background: linear-gradient(135deg, #e0f2fe, #0ea5e9);
|
|
390
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
391
|
+
margin-bottom: 8px;
|
|
392
|
+
}
|
|
393
|
+
.subtitle { color: #8e8e9a; font-size: 14px; margin-bottom: 24px; }
|
|
394
|
+
.status {
|
|
395
|
+
padding: 16px; border-radius: 8px;
|
|
396
|
+
margin-bottom: 16px; font-size: 14px; line-height: 1.6;
|
|
397
|
+
}
|
|
398
|
+
.waiting { background: rgba(14,165,233,0.1); color: #7dd3fc; }
|
|
399
|
+
.success { background: rgba(52,211,153,0.1); color: #34d399; }
|
|
400
|
+
.error { background: rgba(244,63,94,0.1); color: #f43f5e; }
|
|
401
|
+
.info { color: #55555f; font-size: 12px; line-height: 1.6; }
|
|
402
|
+
.security { color: #34d399; font-size: 12px; margin-top: 16px; line-height: 1.6; }
|
|
403
|
+
</style>
|
|
404
|
+
</head>
|
|
405
|
+
<body>
|
|
406
|
+
<div class="card">
|
|
407
|
+
<h1>Byoky</h1>
|
|
408
|
+
<p class="subtitle">Connect wallet to OpenClaw</p>
|
|
409
|
+
<div id="status" class="status waiting">
|
|
410
|
+
Connecting to Byoky wallet...
|
|
411
|
+
</div>
|
|
412
|
+
<p class="info">
|
|
413
|
+
Your Byoky extension must be installed and unlocked.<br />
|
|
414
|
+
The Byoky Bridge must be installed for the proxy to work.
|
|
415
|
+
</p>
|
|
416
|
+
<p class="security">
|
|
417
|
+
Keys never leave your browser extension.<br />
|
|
418
|
+
API calls are proxied through the local Byoky Bridge.
|
|
419
|
+
</p>
|
|
420
|
+
</div>
|
|
421
|
+
<script>
|
|
422
|
+
(async () => {
|
|
423
|
+
const status = document.getElementById('status');
|
|
424
|
+
try {
|
|
425
|
+
// Step 1: Connect to Byoky wallet
|
|
426
|
+
const requestId = crypto.randomUUID();
|
|
427
|
+
window.postMessage({
|
|
428
|
+
type: 'BYOKY_CONNECT_REQUEST',
|
|
429
|
+
id: requestId,
|
|
430
|
+
requestId,
|
|
431
|
+
payload: { providers: ${providerFilter} },
|
|
432
|
+
}, '*');
|
|
433
|
+
|
|
434
|
+
const response = await new Promise((resolve, reject) => {
|
|
435
|
+
function handler(event) {
|
|
436
|
+
const msg = event.detail;
|
|
437
|
+
if (!msg || msg.requestId !== requestId) return;
|
|
438
|
+
document.removeEventListener('byoky-message', handler);
|
|
439
|
+
if (msg.type === 'BYOKY_CONNECT_RESPONSE') resolve(msg.payload);
|
|
440
|
+
else reject(new Error(msg.payload?.message || 'Connection failed'));
|
|
441
|
+
}
|
|
442
|
+
document.addEventListener('byoky-message', handler);
|
|
443
|
+
setTimeout(() => reject(new Error('Timeout \u2014 is Byoky installed and unlocked?')), 30000);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const providers = response.providers || {};
|
|
447
|
+
const available = Object.entries(providers)
|
|
448
|
+
.filter(([, v]) => v.available)
|
|
449
|
+
.map(([id]) => id);
|
|
450
|
+
|
|
451
|
+
if (available.length === 0) {
|
|
452
|
+
throw new Error('No matching providers found in your Byoky wallet');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
status.textContent = 'Wallet connected! Starting bridge proxy...';
|
|
456
|
+
|
|
457
|
+
// Step 2: Tell the extension to start the bridge proxy
|
|
458
|
+
const bridgeRequestId = crypto.randomUUID();
|
|
459
|
+
window.postMessage({
|
|
460
|
+
type: 'BYOKY_INTERNAL_FROM_PAGE',
|
|
461
|
+
requestId: bridgeRequestId,
|
|
462
|
+
action: 'startBridgeProxy',
|
|
463
|
+
payload: { sessionKey: response.sessionKey, port: ${DEFAULT_BRIDGE_PORT} },
|
|
464
|
+
}, '*');
|
|
465
|
+
|
|
466
|
+
const bridgeResult = await new Promise((resolve, reject) => {
|
|
467
|
+
function handler(event) {
|
|
468
|
+
const msg = event.detail;
|
|
469
|
+
if (!msg || msg.requestId !== bridgeRequestId) return;
|
|
470
|
+
document.removeEventListener('byoky-message', handler);
|
|
471
|
+
resolve(msg.payload);
|
|
472
|
+
}
|
|
473
|
+
document.addEventListener('byoky-message', handler);
|
|
474
|
+
setTimeout(() => reject(new Error('Bridge proxy start timed out')), 15000);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const bridgePort = bridgeResult?.port || ${DEFAULT_BRIDGE_PORT};
|
|
478
|
+
|
|
479
|
+
status.textContent = 'Bridge proxy active on port ' + bridgePort + '. Connected ' + available.length + ' provider(s): ' + available.join(', ');
|
|
480
|
+
status.className = 'status success';
|
|
481
|
+
|
|
482
|
+
// Step 3: Send result back to OpenClaw callback server
|
|
483
|
+
await fetch('/callback', {
|
|
484
|
+
method: 'POST',
|
|
485
|
+
headers: { 'Content-Type': 'application/json' },
|
|
486
|
+
body: JSON.stringify({ providers: available, bridgePort: bridgePort }),
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
setTimeout(() => { status.textContent = 'Done \u2014 you can close this tab.'; }, 2000);
|
|
490
|
+
} catch (err) {
|
|
491
|
+
status.textContent = 'Error: ' + err.message;
|
|
492
|
+
status.className = 'status error';
|
|
493
|
+
}
|
|
494
|
+
})();
|
|
495
|
+
</script>
|
|
496
|
+
</body>
|
|
497
|
+
</html>`;
|
|
498
|
+
}
|
|
499
|
+
export {
|
|
500
|
+
index_default as default
|
|
501
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "byoky",
|
|
3
|
+
"enabledByDefault": false,
|
|
4
|
+
"providers": [
|
|
5
|
+
"byoky-anthropic",
|
|
6
|
+
"byoky-openai",
|
|
7
|
+
"byoky-gemini",
|
|
8
|
+
"byoky-mistral",
|
|
9
|
+
"byoky-cohere",
|
|
10
|
+
"byoky-xai",
|
|
11
|
+
"byoky-deepseek",
|
|
12
|
+
"byoky-perplexity",
|
|
13
|
+
"byoky-groq",
|
|
14
|
+
"byoky-together",
|
|
15
|
+
"byoky-fireworks",
|
|
16
|
+
"byoky-openrouter",
|
|
17
|
+
"byoky-replicate",
|
|
18
|
+
"byoky-huggingface",
|
|
19
|
+
"byoky-azure_openai"
|
|
20
|
+
],
|
|
21
|
+
"configSchema": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"properties": {}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@byoky/openclaw-plugin",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Byoky wallet provider plugin for OpenClaw — manage AI API keys from your browser wallet",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"openclaw.plugin.json",
|
|
11
|
+
"auth-page"
|
|
12
|
+
],
|
|
13
|
+
"openclaw": {
|
|
14
|
+
"extensions": [
|
|
15
|
+
"./dist/index.js"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"byoky",
|
|
20
|
+
"openclaw",
|
|
21
|
+
"ai",
|
|
22
|
+
"api-key",
|
|
23
|
+
"wallet",
|
|
24
|
+
"plugin"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/MichaelLod/byoky.git",
|
|
30
|
+
"directory": "packages/openclaw-plugin"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "25.5.0",
|
|
34
|
+
"openclaw": "^2026.3.13",
|
|
35
|
+
"tsup": "^8.0.0",
|
|
36
|
+
"typescript": "^5.5.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"dev": "tsup --watch"
|
|
41
|
+
}
|
|
42
|
+
}
|