@ottocode/openclaw-setu 0.1.3 → 0.1.4

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/src/config.ts DELETED
@@ -1,498 +0,0 @@
1
- import {
2
- readFileSync,
3
- writeFileSync,
4
- existsSync,
5
- readdirSync,
6
- mkdirSync,
7
- renameSync,
8
- } from "node:fs";
9
- import { join } from "node:path";
10
- import { homedir } from "node:os";
11
- import type { ModelApi } from "./types.ts";
12
-
13
- const OPENCLAW_DIR = join(homedir(), ".openclaw");
14
- const OPENCLAW_CONFIG_PATH = join(OPENCLAW_DIR, "openclaw.json");
15
-
16
- const PROVIDER_KEY = "setu";
17
- const DEFAULT_PROXY_PORT = 8403;
18
- const DEFAULT_BASE_URL = "https://api.setu.ottocode.io";
19
- const SETU_PROXY_PORT_PATTERN = /[:\/]8403/;
20
-
21
- export interface SetuModelConfig {
22
- id: string;
23
- name: string;
24
- api?: ModelApi;
25
- reasoning?: boolean;
26
- input?: Array<"text" | "image">;
27
- contextWindow?: number;
28
- maxTokens?: number;
29
- }
30
-
31
- export interface SetuProviderConfig {
32
- baseUrl: string;
33
- apiKey: string;
34
- api: ModelApi;
35
- authHeader: boolean;
36
- models: SetuModelConfig[];
37
- }
38
-
39
- const DUMMY_API_KEY = "setu-proxy-handles-auth";
40
-
41
- const MODEL_ALIASES: Array<{ id: string; alias: string }> = [
42
- { id: "claude-sonnet-4-6", alias: "sonnet-4.6" },
43
- { id: "claude-sonnet-4-5", alias: "sonnet-4.5" },
44
- { id: "claude-opus-4-6", alias: "opus" },
45
- { id: "claude-3-5-haiku-20241022", alias: "haiku" },
46
- { id: "gpt-5.1-codex", alias: "codex" },
47
- { id: "gpt-5", alias: "gpt5" },
48
- { id: "gpt-5-mini", alias: "gpt5-mini" },
49
- { id: "codex-mini-latest", alias: "codex-mini" },
50
- { id: "gemini-3-pro-preview", alias: "gemini-pro" },
51
- { id: "gemini-3-flash-preview", alias: "gemini-flash" },
52
- { id: "kimi-k2.5", alias: "kimi" },
53
- { id: "glm-5", alias: "glm" },
54
- { id: "MiniMax-M2.5", alias: "minimax" },
55
- ];
56
-
57
- function readOpenClawConfig(): Record<string, unknown> {
58
- if (!existsSync(OPENCLAW_CONFIG_PATH)) return {};
59
- try {
60
- const content = readFileSync(OPENCLAW_CONFIG_PATH, "utf-8").trim();
61
- if (!content) return {};
62
- return JSON.parse(content);
63
- } catch {
64
- return {};
65
- }
66
- }
67
-
68
- function writeOpenClawConfigAtomic(config: Record<string, unknown>): void {
69
- if (!existsSync(OPENCLAW_DIR)) {
70
- mkdirSync(OPENCLAW_DIR, { recursive: true });
71
- }
72
- const tmpPath = `${OPENCLAW_CONFIG_PATH}.tmp.${process.pid}`;
73
- writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
74
- renameSync(tmpPath, OPENCLAW_CONFIG_PATH);
75
- }
76
-
77
- interface CatalogModel {
78
- id: string;
79
- owned_by: string;
80
- context_length: number;
81
- max_output: number;
82
- capabilities?: { tool_call?: boolean; reasoning?: boolean };
83
- }
84
-
85
- function apiForOwner(owner: string): ModelApi {
86
- switch (owner) {
87
- case "anthropic":
88
- return "anthropic-messages";
89
- case "google":
90
- return "google-generative-ai";
91
- default:
92
- return "openai-completions";
93
- }
94
- }
95
-
96
- function displayName(id: string, owner: string): string {
97
- return `${id} (${owner}, via Setu)`;
98
- }
99
-
100
- export async function fetchModelsFromCatalog(
101
- baseURL: string = DEFAULT_BASE_URL,
102
- ): Promise<SetuModelConfig[]> {
103
- try {
104
- const resp = await fetch(`${baseURL}/v1/models`);
105
- if (!resp.ok) return getDefaultModels();
106
- const data = (await resp.json()) as { data: CatalogModel[] };
107
- return data.data.map((m) => ({
108
- id: m.id,
109
- name: displayName(m.id, m.owned_by),
110
- api: apiForOwner(m.owned_by),
111
- reasoning: m.capabilities?.reasoning ?? false,
112
- input: ["text"] as Array<"text" | "image">,
113
- contextWindow: m.context_length,
114
- maxTokens: m.max_output,
115
- }));
116
- } catch {
117
- return getDefaultModels();
118
- }
119
- }
120
-
121
- export function getDefaultModels(): SetuModelConfig[] {
122
- return [
123
- {
124
- id: "claude-sonnet-4-6",
125
- name: "Claude Sonnet 4.6 (anthropic, via Setu)",
126
- api: "anthropic-messages",
127
- reasoning: true,
128
- input: ["text", "image"],
129
- contextWindow: 200000,
130
- maxTokens: 64000,
131
- },
132
- {
133
- id: "claude-sonnet-4-5",
134
- name: "Claude Sonnet 4.5 (anthropic, via Setu)",
135
- api: "anthropic-messages",
136
- reasoning: true,
137
- input: ["text", "image"],
138
- contextWindow: 200000,
139
- maxTokens: 64000,
140
- },
141
- {
142
- id: "claude-opus-4-6",
143
- name: "Claude Opus 4.6 (anthropic, via Setu)",
144
- api: "anthropic-messages",
145
- reasoning: true,
146
- input: ["text", "image"],
147
- contextWindow: 200000,
148
- maxTokens: 128000,
149
- },
150
- {
151
- id: "claude-3-5-haiku-20241022",
152
- name: "Claude 3.5 Haiku (anthropic, via Setu)",
153
- api: "anthropic-messages",
154
- reasoning: false,
155
- input: ["text", "image"],
156
- contextWindow: 200000,
157
- maxTokens: 8192,
158
- },
159
- {
160
- id: "gpt-5.1-codex",
161
- name: "GPT-5.1 Codex (openai, via Setu)",
162
- api: "openai-completions",
163
- reasoning: true,
164
- input: ["text", "image"],
165
- contextWindow: 400000,
166
- maxTokens: 128000,
167
- },
168
- {
169
- id: "gpt-5",
170
- name: "GPT-5 (openai, via Setu)",
171
- api: "openai-completions",
172
- reasoning: true,
173
- input: ["text", "image"],
174
- contextWindow: 400000,
175
- maxTokens: 128000,
176
- },
177
- {
178
- id: "gpt-5-mini",
179
- name: "GPT-5 Mini (openai, via Setu)",
180
- api: "openai-completions",
181
- reasoning: true,
182
- input: ["text", "image"],
183
- contextWindow: 400000,
184
- maxTokens: 128000,
185
- },
186
- {
187
- id: "codex-mini-latest",
188
- name: "Codex Mini (openai, via Setu)",
189
- api: "openai-completions",
190
- reasoning: true,
191
- input: ["text"],
192
- contextWindow: 200000,
193
- maxTokens: 100000,
194
- },
195
- {
196
- id: "gemini-3-pro-preview",
197
- name: "Gemini 3 Pro (google, via Setu)",
198
- api: "openai-completions",
199
- reasoning: true,
200
- input: ["text", "image"],
201
- contextWindow: 1000000,
202
- maxTokens: 64000,
203
- },
204
- {
205
- id: "gemini-3-flash-preview",
206
- name: "Gemini 3 Flash (google, via Setu)",
207
- api: "openai-completions",
208
- reasoning: true,
209
- input: ["text", "image"],
210
- contextWindow: 1048576,
211
- maxTokens: 65536,
212
- },
213
- {
214
- id: "kimi-k2.5",
215
- name: "Kimi K2.5 (moonshot, via Setu)",
216
- api: "openai-completions",
217
- reasoning: true,
218
- input: ["text"],
219
- contextWindow: 262144,
220
- maxTokens: 262144,
221
- },
222
- {
223
- id: "glm-5",
224
- name: "GLM-5 (zai, via Setu)",
225
- api: "openai-completions",
226
- reasoning: true,
227
- input: ["text"],
228
- contextWindow: 204800,
229
- maxTokens: 131072,
230
- },
231
- {
232
- id: "MiniMax-M2.5",
233
- name: "MiniMax M2.5 (minimax, via Setu)",
234
- api: "openai-completions",
235
- reasoning: true,
236
- input: ["text"],
237
- contextWindow: 204800,
238
- maxTokens: 131072,
239
- },
240
- ];
241
- }
242
-
243
- export function buildProviderConfig(
244
- port: number = DEFAULT_PROXY_PORT,
245
- ): SetuProviderConfig {
246
- return {
247
- baseUrl: `http://localhost:${port}/v1`,
248
- apiKey: "setu-proxy-handles-auth",
249
- api: "openai-completions",
250
- authHeader: false,
251
- models: getDefaultModels(),
252
- };
253
- }
254
-
255
- export async function buildProviderConfigWithCatalog(
256
- port: number = DEFAULT_PROXY_PORT,
257
- baseURL: string = DEFAULT_BASE_URL,
258
- ): Promise<SetuProviderConfig> {
259
- const models = await fetchModelsFromCatalog(baseURL);
260
- return {
261
- baseUrl: `http://localhost:${port}/v1`,
262
- apiKey: "setu-proxy-handles-auth",
263
- api: "openai-completions",
264
- authHeader: false,
265
- models,
266
- };
267
- }
268
-
269
- function removeConflictingCustomProviders(
270
- providers: Record<string, unknown>,
271
- port: number,
272
- ): string[] {
273
- const removed: string[] = [];
274
- for (const key of Object.keys(providers)) {
275
- if (key === PROVIDER_KEY) continue;
276
- if (!key.startsWith("custom-")) continue;
277
- const p = providers[key] as Record<string, unknown> | undefined;
278
- if (!p?.baseUrl) continue;
279
- const baseUrl = String(p.baseUrl);
280
- const pointsToSetuProxy =
281
- baseUrl.includes(`:${port}`) || SETU_PROXY_PORT_PATTERN.test(baseUrl);
282
- if (pointsToSetuProxy) {
283
- delete providers[key];
284
- removed.push(key);
285
- }
286
- }
287
- return removed;
288
- }
289
-
290
- function migrateDefaultModel(
291
- config: Record<string, unknown>,
292
- removedKeys: string[],
293
- ): void {
294
- if (removedKeys.length === 0) return;
295
- const agents = config.agents as Record<string, unknown> | undefined;
296
- const defaults = agents?.defaults as Record<string, unknown> | undefined;
297
- const model = defaults?.model as Record<string, unknown> | undefined;
298
- if (!model?.primary) return;
299
- const primary = String(model.primary);
300
- for (const oldKey of removedKeys) {
301
- if (primary.startsWith(`${oldKey}/`)) {
302
- const modelId = primary.slice(oldKey.length + 1);
303
- model.primary = `setu/${modelId}`;
304
- break;
305
- }
306
- }
307
- }
308
-
309
- export async function injectConfig(port: number = DEFAULT_PROXY_PORT): Promise<void> {
310
- const config = readOpenClawConfig();
311
- let needsWrite = false;
312
-
313
- if (!config.models) {
314
- config.models = {};
315
- needsWrite = true;
316
- }
317
- const models = config.models as Record<string, unknown>;
318
- if (!models.providers) {
319
- models.providers = {};
320
- needsWrite = true;
321
- }
322
- const providers = models.providers as Record<string, unknown>;
323
-
324
- const removed = removeConflictingCustomProviders(providers, port);
325
- if (removed.length > 0) {
326
- migrateDefaultModel(config, removed);
327
- needsWrite = true;
328
- }
329
-
330
- const expectedBaseUrl = `http://localhost:${port}/v1`;
331
- const existing = providers[PROVIDER_KEY] as Record<string, unknown> | undefined;
332
-
333
- if (!existing) {
334
- providers[PROVIDER_KEY] = await buildProviderConfigWithCatalog(port);
335
- needsWrite = true;
336
- } else {
337
- if (!existing.baseUrl || existing.baseUrl !== expectedBaseUrl) {
338
- existing.baseUrl = expectedBaseUrl;
339
- needsWrite = true;
340
- }
341
- if (!existing.apiKey) {
342
- existing.apiKey = DUMMY_API_KEY;
343
- needsWrite = true;
344
- }
345
- if (!existing.api) {
346
- existing.api = "openai-completions";
347
- needsWrite = true;
348
- }
349
- const currentModels = existing.models as Array<{ id?: string }> | undefined;
350
- const defaultModels = getDefaultModels();
351
- const currentIds = new Set(
352
- Array.isArray(currentModels) ? currentModels.map((m) => m?.id).filter(Boolean) : [],
353
- );
354
- const expectedIds = defaultModels.map((m) => m.id);
355
- if (
356
- !currentModels ||
357
- !Array.isArray(currentModels) ||
358
- currentModels.length !== defaultModels.length ||
359
- expectedIds.some((id) => !currentIds.has(id))
360
- ) {
361
- existing.models = await fetchModelsFromCatalog();
362
- needsWrite = true;
363
- }
364
- }
365
-
366
- if (!config.agents) {
367
- config.agents = {};
368
- needsWrite = true;
369
- }
370
- const agents = config.agents as Record<string, unknown>;
371
- if (!agents.defaults) {
372
- agents.defaults = {};
373
- needsWrite = true;
374
- }
375
- const defaults = agents.defaults as Record<string, unknown>;
376
- if (!defaults.model) {
377
- defaults.model = {};
378
- needsWrite = true;
379
- }
380
- const model = defaults.model as Record<string, unknown>;
381
-
382
- if (!model.primary) {
383
- model.primary = "setu/claude-sonnet-4-6";
384
- needsWrite = true;
385
- }
386
-
387
- if (!defaults.models) {
388
- defaults.models = {};
389
- needsWrite = true;
390
- }
391
- const allowlist = defaults.models as Record<string, unknown>;
392
- for (const m of MODEL_ALIASES) {
393
- const fullId = `setu/${m.id}`;
394
- const entry = allowlist[fullId] as Record<string, unknown> | undefined;
395
- if (!entry) {
396
- allowlist[fullId] = { alias: m.alias };
397
- needsWrite = true;
398
- } else if (entry.alias !== m.alias) {
399
- entry.alias = m.alias;
400
- needsWrite = true;
401
- }
402
- }
403
-
404
- if (needsWrite) {
405
- writeOpenClawConfigAtomic(config);
406
- }
407
- }
408
-
409
- export function injectAuthProfile(): void {
410
- const agentsDir = join(OPENCLAW_DIR, "agents");
411
-
412
- if (!existsSync(agentsDir)) {
413
- try {
414
- mkdirSync(agentsDir, { recursive: true });
415
- } catch {
416
- return;
417
- }
418
- }
419
-
420
- let agents: string[];
421
- try {
422
- agents = readdirSync(agentsDir, { withFileTypes: true })
423
- .filter((d) => d.isDirectory())
424
- .map((d) => d.name);
425
- } catch {
426
- agents = [];
427
- }
428
-
429
- if (!agents.includes("main")) {
430
- agents = ["main", ...agents];
431
- }
432
-
433
- for (const agentId of agents) {
434
- const authDir = join(agentsDir, agentId, "agent");
435
- const authPath = join(authDir, "auth-profiles.json");
436
-
437
- if (!existsSync(authDir)) {
438
- try {
439
- mkdirSync(authDir, { recursive: true });
440
- } catch {
441
- continue;
442
- }
443
- }
444
-
445
- let store: { version: number; profiles: Record<string, unknown> } = {
446
- version: 1,
447
- profiles: {},
448
- };
449
- if (existsSync(authPath)) {
450
- try {
451
- const existing = JSON.parse(readFileSync(authPath, "utf-8"));
452
- if (existing.version && existing.profiles) {
453
- store = existing;
454
- }
455
- } catch {
456
- // use fresh store
457
- }
458
- }
459
-
460
- const profileKey = "setu:default";
461
- if (store.profiles[profileKey]) continue;
462
-
463
- store.profiles[profileKey] = {
464
- type: "api_key",
465
- provider: "setu",
466
- key: DUMMY_API_KEY,
467
- };
468
-
469
- try {
470
- writeFileSync(authPath, JSON.stringify(store, null, 2));
471
- } catch {
472
- // skip
473
- }
474
- }
475
- }
476
-
477
- export function removeConfig(): void {
478
- const config = readOpenClawConfig();
479
-
480
- const models = config.models as Record<string, unknown> | undefined;
481
- if (!models?.providers) return;
482
- const providers = models.providers as Record<string, unknown>;
483
- delete providers[PROVIDER_KEY];
484
-
485
- writeOpenClawConfigAtomic(config);
486
- }
487
-
488
- export function isConfigured(): boolean {
489
- const config = readOpenClawConfig();
490
- const models = config.models as Record<string, unknown> | undefined;
491
- if (!models?.providers) return false;
492
- const providers = models.providers as Record<string, unknown>;
493
- return PROVIDER_KEY in providers;
494
- }
495
-
496
- export function getConfigPath(): string {
497
- return OPENCLAW_CONFIG_PATH;
498
- }
package/src/index.ts DELETED
@@ -1,225 +0,0 @@
1
- import type {
2
- OpenClawPluginDefinition,
3
- OpenClawPluginApi,
4
- OpenClawPluginCommandDefinition,
5
- } from "./types.ts";
6
- import {
7
- loadWallet,
8
- ensureWallet,
9
- getSetuBalance,
10
- getWalletKeyPath,
11
- } from "./wallet.ts";
12
- import {
13
- buildProviderConfig,
14
- injectConfig,
15
- injectAuthProfile,
16
- isConfigured,
17
- } from "./config.ts";
18
- import { isValidPrivateKey } from "@ottocode/ai-sdk";
19
-
20
- const DEFAULT_PORT = 8403;
21
-
22
- function getPort(api: OpenClawPluginApi): number {
23
- const cfg = api.pluginConfig as Record<string, unknown> | undefined;
24
- return (cfg?.port as number) ?? DEFAULT_PORT;
25
- }
26
-
27
- const plugin: OpenClawPluginDefinition = {
28
- id: "setu",
29
- name: "Setu",
30
- description: "Pay for AI with Solana USDC — no API keys, just a wallet.",
31
- version: "0.1.0",
32
-
33
- async register(api: OpenClawPluginApi) {
34
- const port = getPort(api);
35
-
36
- await injectConfig(port).catch(() => {});
37
- try { injectAuthProfile(); } catch {}
38
-
39
- if (!api.config.models) {
40
- api.config.models = { providers: {} };
41
- }
42
- if (!api.config.models.providers) {
43
- api.config.models.providers = {};
44
- }
45
- const providerConfig = buildProviderConfig(port);
46
- api.config.models.providers.setu = {
47
- baseUrl: providerConfig.baseUrl,
48
- api: providerConfig.api,
49
- apiKey: providerConfig.apiKey,
50
- models: providerConfig.models,
51
- };
52
-
53
- if (!api.config.agents) api.config.agents = {};
54
- const agents = api.config.agents as Record<string, unknown>;
55
- if (!agents.defaults) agents.defaults = {};
56
- const defaults = agents.defaults as Record<string, unknown>;
57
- if (!defaults.model) defaults.model = {};
58
- const model = defaults.model as Record<string, unknown>;
59
- if (!model.primary) {
60
- model.primary = "setu/claude-sonnet-4-6";
61
- }
62
-
63
- api.registerProvider({
64
- id: "setu",
65
- label: "Setu (Solana USDC)",
66
- aliases: ["setu-solana"],
67
- envVars: ["SETU_PRIVATE_KEY"],
68
- models: buildProviderConfig(port),
69
- auth: [
70
- {
71
- id: "setu-wallet",
72
- label: "Solana Wallet",
73
- hint: "Generate or import a Solana wallet — pay per token with USDC",
74
- kind: "custom",
75
- async run(ctx) {
76
- const existing = loadWallet();
77
-
78
- if (existing) {
79
- ctx.prompter.note(
80
- `Existing Setu wallet found: ${existing.publicKey}`,
81
- );
82
- return {
83
- profiles: [
84
- {
85
- profileId: "setu-wallet",
86
- credential: {
87
- apiKey: "setu-proxy-handles-auth",
88
- type: "wallet",
89
- walletAddress: existing.publicKey,
90
- },
91
- },
92
- ],
93
- configPatch: {
94
- models: { providers: { setu: buildProviderConfig(port) } },
95
- },
96
- defaultModel: `setu/claude-sonnet-4-6`,
97
- notes: [
98
- `Wallet: ${existing.publicKey}`,
99
- `Fund with USDC on Solana to start using.`,
100
- `Run \`openclaw-setu start\` to start the proxy.`,
101
- ],
102
- };
103
- }
104
-
105
- const keyInput = await ctx.prompter.text({
106
- message:
107
- "Enter Solana private key (base58) or press Enter to generate a new one:",
108
- validate: (value: string) => {
109
- if (value && !isValidPrivateKey(value)) {
110
- return "Invalid Solana private key";
111
- }
112
- return undefined;
113
- },
114
- });
115
-
116
- const key = typeof keyInput === "string" ? keyInput : "";
117
- const wallet = key ? ensureWallet() : ensureWallet();
118
- if (key && isValidPrivateKey(key)) {
119
- const { saveWallet } = await import("./wallet.ts");
120
- saveWallet(key);
121
- }
122
-
123
- const finalWallet = loadWallet()!;
124
-
125
- await injectConfig(port);
126
-
127
- return {
128
- profiles: [
129
- {
130
- profileId: "setu-wallet",
131
- credential: {
132
- apiKey: "setu-proxy-handles-auth",
133
- type: "wallet",
134
- walletAddress: finalWallet.publicKey,
135
- },
136
- },
137
- ],
138
- configPatch: {
139
- models: { providers: { setu: buildProviderConfig(port) } },
140
- },
141
- defaultModel: `setu/claude-sonnet-4-6`,
142
- notes: [
143
- `Wallet generated: ${finalWallet.publicKey}`,
144
- `Key stored at: ${getWalletKeyPath()}`,
145
- `Fund with USDC on Solana: ${finalWallet.publicKey}`,
146
- `Run \`openclaw-setu start\` to start the proxy.`,
147
- ],
148
- };
149
- },
150
- },
151
- ],
152
- });
153
-
154
- const walletCmd: OpenClawPluginCommandDefinition = {
155
- name: "wallet",
156
- description: "Show your Setu wallet address and balances",
157
- requireAuth: true,
158
- async handler() {
159
- const wallet = loadWallet();
160
- if (!wallet) {
161
- return { text: "No Setu wallet found. Run `openclaw-setu setup`." };
162
- }
163
-
164
- const balances = await getSetuBalance(wallet.privateKey);
165
- const lines = [`Wallet: ${wallet.publicKey}`];
166
-
167
- if (balances.setu) {
168
- lines.push(`Setu Balance: $${balances.setu.balance.toFixed(4)}`);
169
- lines.push(`Total Spent: $${balances.setu.totalSpent.toFixed(4)}`);
170
- lines.push(`Requests: ${balances.setu.requestCount}`);
171
- }
172
- if (balances.wallet) {
173
- lines.push(
174
- `On-chain USDC: $${balances.wallet.usdcBalance.toFixed(4)} (${balances.wallet.network})`,
175
- );
176
- }
177
-
178
- return { text: lines.join("\n") };
179
- },
180
- };
181
-
182
- api.registerCommand(walletCmd);
183
-
184
- const statusCmd: OpenClawPluginCommandDefinition = {
185
- name: "setu-status",
186
- description: "Check Setu plugin configuration status",
187
- async handler() {
188
- const wallet = loadWallet();
189
- const configured = isConfigured();
190
- const lines = [
191
- `Wallet: ${wallet ? wallet.publicKey : "not set up"}`,
192
- `OpenClaw config: ${configured ? "injected" : "not configured"}`,
193
- `Proxy port: ${port}`,
194
- ];
195
- return { text: lines.join("\n") };
196
- },
197
- };
198
-
199
- api.registerCommand(statusCmd);
200
-
201
- api.registerService({
202
- id: "setu-proxy",
203
- async start() {
204
- const wallet = loadWallet();
205
- if (!wallet) {
206
- api.logger.warn(
207
- "Setu: No wallet found. Run `openclaw-setu setup` first.",
208
- );
209
- return;
210
- }
211
- try {
212
- const { createProxy } = await import("./proxy.ts");
213
- createProxy({ port, verbose: false });
214
- api.logger.info(
215
- `Setu proxy running on http://localhost:${port}`,
216
- );
217
- } catch (err) {
218
- api.logger.error(`Setu proxy failed: ${(err as Error).message}`);
219
- }
220
- },
221
- });
222
- },
223
- };
224
-
225
- export default plugin;