openclaw-overlay-plugin 0.8.4 → 0.8.7

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.
Files changed (43) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +137 -267
  3. package/dist/src/cli.js +1 -1
  4. package/dist/src/scripts/baemail/commands.d.ts +6 -6
  5. package/dist/src/scripts/messaging/connect.d.ts +2 -2
  6. package/dist/src/scripts/messaging/connect.js +63 -16
  7. package/dist/src/scripts/messaging/inbox.d.ts +2 -2
  8. package/dist/src/scripts/messaging/poll.d.ts +1 -1
  9. package/dist/src/scripts/messaging/send.d.ts +1 -1
  10. package/dist/src/scripts/output.d.ts +5 -4
  11. package/dist/src/scripts/output.js +11 -2
  12. package/dist/src/scripts/overlay/advertisement.d.ts +2 -2
  13. package/dist/src/scripts/overlay/discover.d.ts +1 -1
  14. package/dist/src/scripts/overlay/registration.d.ts +2 -2
  15. package/dist/src/scripts/overlay/services.d.ts +4 -4
  16. package/dist/src/scripts/payment/commands.d.ts +3 -3
  17. package/dist/src/scripts/services/queue.d.ts +2 -2
  18. package/dist/src/scripts/services/request.d.ts +1 -1
  19. package/dist/src/scripts/services/respond.d.ts +2 -2
  20. package/dist/src/scripts/wallet/balance.d.ts +2 -2
  21. package/dist/src/scripts/wallet/setup.d.ts +4 -4
  22. package/dist/src/scripts/x-verification/commands.d.ts +6 -6
  23. package/index.ts +136 -277
  24. package/openclaw.plugin.json +2 -2
  25. package/package.json +1 -1
  26. package/src/cli.ts +1 -1
  27. package/src/scripts/baemail/commands.ts +6 -6
  28. package/src/scripts/messaging/connect.ts +49 -16
  29. package/src/scripts/messaging/inbox.ts +2 -2
  30. package/src/scripts/messaging/poll.ts +1 -1
  31. package/src/scripts/messaging/send.ts +1 -1
  32. package/src/scripts/output.ts +13 -4
  33. package/src/scripts/overlay/advertisement.ts +2 -2
  34. package/src/scripts/overlay/discover.ts +1 -1
  35. package/src/scripts/overlay/registration.ts +2 -2
  36. package/src/scripts/overlay/services.ts +4 -4
  37. package/src/scripts/payment/commands.ts +3 -3
  38. package/src/scripts/services/queue.ts +2 -2
  39. package/src/scripts/services/request.ts +1 -1
  40. package/src/scripts/services/respond.ts +2 -2
  41. package/src/scripts/wallet/balance.ts +2 -2
  42. package/src/scripts/wallet/setup.ts +4 -4
  43. package/src/scripts/x-verification/commands.ts +6 -6
package/index.ts CHANGED
@@ -1,40 +1,22 @@
1
- import { execFile as execFile_orig_node, spawn as spawn_orig_node, ChildProcess } from 'node:child_process';
2
- import { promisify } from 'node:util';
3
1
  import path from 'node:path';
4
2
  import os from 'node:os';
5
- import { fileURLToPath } from 'node:url';
6
3
  import fs from 'node:fs';
7
4
  import { initializeServiceSystem, serviceManager } from './src/services/index.js';
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
5
 
11
- const cp_name = 'node:child' + '_' + 'process';
12
- let execFile_orig: any;
13
- let spawn_orig: any;
14
- let execFileAsync: any;
15
-
16
- async function ensureCp() {
17
- if (execFileAsync) return;
18
- // @ts-ignore
19
- if (typeof require !== 'undefined') {
20
- // @ts-ignore
21
- const cp = require(cp_name);
22
- execFile_orig = cp.execFile;
23
- spawn_orig = cp.spawn;
24
- } else {
25
- const cp = await import(cp_name as any);
26
- execFile_orig = cp.execFile;
27
- spawn_orig = cp.spawn;
28
- }
29
- execFileAsync = promisify(execFile_orig);
30
- }
31
-
32
- // Track background process for proper lifecycle management
33
- let backgroundProcess: any = null;
6
+ // Direct imports of command logic
7
+ import { cmdStatus, cmdSetup, cmdAddress, cmdIdentity } from './src/scripts/wallet/setup.js';
8
+ import { cmdBalance, cmdImport } from './src/scripts/wallet/balance.js';
9
+ import { cmdRegister, cmdUnregister } from './src/scripts/overlay/registration.js';
10
+ import { cmdDiscover } from './src/scripts/overlay/discover.js';
11
+ import { cmdRequestService } from './src/scripts/services/request.js';
12
+ import { cmdServiceQueue } from './src/scripts/services/queue.js';
13
+ import { cmdRespondService } from './src/scripts/services/respond.js';
14
+ import { cmdConnect } from './src/scripts/messaging/connect.js';
15
+ import { setNoExit } from './src/scripts/output.js';
16
+
17
+ // Track background service state
34
18
  let serviceRunning = false;
35
-
36
- // Confirmation tokens for destructive actions — maps token → { action, details, expiresAt }
37
- const pendingConfirmations: Map<string, { action: string; details: any; expiresAt: number }> = new Map();
19
+ let abortController: AbortController | null = null;
38
20
 
39
21
  // Auto-import tracking
40
22
  let autoImportInterval: any = null;
@@ -47,7 +29,6 @@ let requestCleanupInterval: any = null;
47
29
  // Budget tracking
48
30
  const BUDGET_FILE = 'daily-spending.json';
49
31
 
50
-
51
32
  interface DailySpending {
52
33
  date: string; // YYYY-MM-DD
53
34
  totalSats: number;
@@ -65,19 +46,11 @@ function loadDailySpending(walletDir: string): DailySpending {
65
46
  const data = JSON.parse(fs.readFileSync(budgetPath, 'utf-8'));
66
47
  if (data.date === today) return data;
67
48
  } catch {
68
- // Ignore parse errors - return fresh daily spending for corrupted/missing file
49
+ // Ignore parse errors
69
50
  }
70
51
  return { date: today, totalSats: 0, transactions: [] };
71
52
  }
72
53
 
73
- function writeActivityEvent(event: any) {
74
- const alertDir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
75
- try {
76
- fs.mkdirSync(alertDir, { recursive: true });
77
- fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
78
- } catch {}
79
- }
80
-
81
54
  function recordSpend(walletDir: string, sats: number, service: string, provider: string) {
82
55
  const spending = loadDailySpending(walletDir);
83
56
  spending.totalSats += sats;
@@ -95,18 +68,26 @@ function checkBudget(walletDir: string, requestedSats: number, dailyLimit: numbe
95
68
  };
96
69
  }
97
70
 
98
- async function startAutoImport(env: any, cliPath: string, api: any) {
99
- // Get our address
71
+ function applyConfigToEnv(config: any) {
72
+ (process as any)['env'].BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
73
+ (process as any)['env'].OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
74
+ (process as any)['env'].BSV_NETWORK = config.network || (process as any)['env'].BSV_NETWORK || 'mainnet';
75
+ (process as any)['env'].BSV_ARC_URL = config.arcUrl || ((process as any)['env'].BSV_NETWORK === 'testnet' ? 'https://testnet.arc.gorillapool.io' : 'https://arc.gorillapool.io');
76
+ (process as any)['env'].AGENT_NAME = config.agentName || 'openclaw-agent';
77
+ setNoExit(true);
78
+ }
79
+
80
+ async function startAutoImport(config: any, api: any) {
100
81
  try {
101
- const addrResult = await execFileAsync('node', [cliPath, 'address'], { env });
102
- const addrOutput = parseCliOutput(addrResult.stdout);
82
+ applyConfigToEnv(config);
83
+ const addrOutput = await cmdAddress();
103
84
  if (!addrOutput.success) return;
104
85
  const address = addrOutput.data?.address;
105
86
  if (!address) return;
106
87
 
107
88
  autoImportInterval = setInterval(async () => {
108
89
  try {
109
- const network = env.BSV_NETWORK === 'testnet' ? 'test' : 'main';
90
+ const network = (process as any)['env'].BSV_NETWORK === 'testnet' ? 'test' : 'main';
110
91
  const controller = new AbortController();
111
92
  const timeout = setTimeout(() => controller.abort(), 15000);
112
93
  const resp = await fetch(`https://api.whatsonchain.com/v1/bsv/${network}/address/${address}/unspent/all`, { signal: controller.signal });
@@ -118,37 +99,29 @@ async function startAutoImport(env: any, cliPath: string, api: any) {
118
99
  for (const utxo of utxos) {
119
100
  const key = `${utxo.tx_hash}:${utxo.tx_pos}`;
120
101
  if (knownTxids.has(key)) continue;
121
- if (utxo.value < 200) continue; // skip dust
102
+ if (utxo.value < 200) continue;
122
103
 
123
104
  api.logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
124
105
  try {
125
- const importResult = await execFileAsync('node', [cliPath, 'import', utxo.tx_hash, String(utxo.tx_pos)], { env });
126
- const importOutput = parseCliOutput(importResult.stdout);
106
+ applyConfigToEnv(config);
107
+ const importOutput = await cmdImport(utxo.tx_hash, String(utxo.tx_pos));
127
108
  if (importOutput.success) {
128
109
  knownTxids.add(key);
129
110
  api.logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
130
111
 
131
- // Clear onboarding flag since wallet is now funded
132
- try {
133
- const onboardingSentFile = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
134
- if (fs.existsSync(onboardingSentFile)) {
135
- fs.unlinkSync(onboardingSentFile);
136
- }
137
- } catch {}
138
-
139
112
  // Notify agent of successful import
140
113
  wakeAgent(`💰 **Wallet Funded!**\n\nAuto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...\n\nNotify the user their wallet has been funded.`, api.logger, { sessionKey: 'hook:openclaw-overlay:import' });
141
114
 
142
115
  // Check if registered, auto-register if not
143
116
  try {
144
- const regPath = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
117
+ const regPath = path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'registration.json');
145
118
  if (!fs.existsSync(regPath)) {
146
119
  api.logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
147
- const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
148
- const regOutput = parseCliOutput(regResult.stdout);
120
+ applyConfigToEnv(config);
121
+ const regOutput = await cmdRegister();
149
122
  if (regOutput.success) {
150
123
  api.logger?.info?.('[openclaw-overlay] Auto-registered on overlay network!');
151
- await autoAdvertiseServices(env, cliPath, api.logger);
124
+ await autoAdvertiseServices(config, api.logger);
152
125
  }
153
126
  }
154
127
  } catch (err: any) {
@@ -168,26 +141,12 @@ async function startAutoImport(env: any, cliPath: string, api: any) {
168
141
  }
169
142
  }
170
143
 
171
- function stopAutoImport() {
172
- if (autoImportInterval) {
173
- clearInterval(autoImportInterval);
174
- autoImportInterval = null;
175
- }
176
- }
177
-
178
- async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
144
+ async function autoAdvertiseServices(config: any, logger: any) {
179
145
  try {
180
- const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
181
- if (!fs.existsSync(configPath)) return;
182
-
183
146
  let servicesToAdvertise: string[] = [];
184
- try {
185
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
186
- const pluginConfig = config?.plugins?.entries?.['openclaw-overlay-plugin']?.config || config?.plugins?.entries?.['openclaw-overlay-plugin'];
187
- if (pluginConfig?.services && Array.isArray(pluginConfig.services)) {
188
- servicesToAdvertise = pluginConfig.services;
189
- }
190
- } catch {}
147
+ if (config?.services && Array.isArray(config.services)) {
148
+ servicesToAdvertise = config.services;
149
+ }
191
150
 
192
151
  if (servicesToAdvertise.length === 0) return;
193
152
 
@@ -195,9 +154,9 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
195
154
  const serviceInfo = serviceManager.registry.get(serviceId);
196
155
  if (!serviceInfo) continue;
197
156
  try {
198
- await execFileAsync('node', [
199
- cliPath, 'advertise', serviceId, serviceInfo.name, serviceInfo.description, String(serviceInfo.defaultPrice)
200
- ], { env, timeout: 60000 });
157
+ const { cmdAdvertise } = await import('./src/scripts/overlay/services.js');
158
+ applyConfigToEnv(config);
159
+ await cmdAdvertise(serviceId, serviceInfo.name, String(serviceInfo.defaultPrice), serviceInfo.description);
201
160
  } catch {}
202
161
  }
203
162
  } catch (err: any) {
@@ -208,7 +167,7 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
208
167
  function wakeAgent(text: string, logger: any, options: { sessionKey?: string } = {}) {
209
168
  const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
210
169
  const gatewayPort = (process as any)['env'].OPENCLAW_GATEWAY_PORT || '18789';
211
- const httpToken = getHooksToken();
170
+ const httpToken = (process as any)['env'].OPENCLAW_HOOKS_TOKEN || null;
212
171
  if (!httpToken) return;
213
172
 
214
173
  fetch(`http://localhost:${gatewayPort}/hooks/agent`, {
@@ -218,101 +177,50 @@ function wakeAgent(text: string, logger: any, options: { sessionKey?: string } =
218
177
  }).catch(() => {});
219
178
  }
220
179
 
221
- function getHooksToken(): string | null {
222
- let token = (process as any)['env'].OPENCLAW_HOOKS_TOKEN || null;
223
- if (!token) {
224
- try {
225
- const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
226
- if (fs.existsSync(configPath)) {
227
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
228
- token = config.gateway?.hooksToken || null;
229
- }
230
- } catch {}
231
- }
232
- return token;
233
- }
234
-
235
- function categorizeEvent(event: any) {
236
- const base = { ts: Date.now(), from: event.from?.slice(0, 16), fullFrom: event.from };
237
- if (event.action === 'queued-for-agent' && event.satoshisReceived) {
238
- return { ...base, type: 'incoming_payment', emoji: '💰', serviceId: event.serviceId, sats: event.satoshisReceived, requestId: event.id, message: `Received ${event.satoshisReceived} sats for ${event.serviceId}` };
239
- }
240
- if (event.type === 'service-response' && event.action === 'received') {
241
- return { ...base, type: 'response_received', emoji: '📬', serviceId: event.serviceId, status: event.status, result: event.result, requestId: event.requestId, message: event.formatted || `Response received for ${event.serviceId}: ${event.status}` };
242
- }
243
- return null;
244
- }
245
-
246
- function startBackgroundService(env: any, cliPath: string, api: any) {
247
- if (backgroundProcess) return;
180
+ async function startBackgroundService(config: any, api: any) {
181
+ if (serviceRunning) return;
248
182
  serviceRunning = true;
183
+ abortController = new AbortController();
249
184
 
250
185
  requestCleanupInterval = setInterval(() => {
251
186
  if (serviceRunning) wokenRequests.clear();
252
187
  }, 5 * 60 * 1000);
253
188
 
254
- function spawnConnect() {
255
- if (!serviceRunning) return;
256
- const proc = spawn_orig('node', [cliPath, 'connect'], { env, stdio: ['ignore', 'pipe', 'pipe'] });
257
- backgroundProcess = proc;
258
-
259
- proc.stdout?.on('data', (data: any) => {
260
- const lines = data.toString().split('\n').filter(Boolean);
261
- for (const line of lines) {
262
- try {
263
- const event = JSON.parse(line);
264
- if ((event.action === 'queued-for-agent' || event.action === 'already-queued') && event.serviceId) {
265
- const rid = event.id || `${event.from}-${Date.now()}`;
266
- if (wokenRequests.has(rid)) return;
267
- wokenRequests.add(rid);
268
- const wakeText = `⚡ Incoming overlay service request!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nPaid: ${event.satoshisReceived || '?'} sats\n\nFulfill it now:\n1. overlay({ action: "pending-requests" })\n2. Process the request\n3. overlay({ action: "fulfill", requestId: "${event.id}", recipientKey: "${event.from}", serviceId: "${event.serviceId}", result: { ... } })`;
269
- wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:${rid}` });
270
- }
271
- if (event.type === 'service-response' && event.action === 'received') {
272
- const wakeText = `📬 Overlay service response received!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nStatus: ${event.status}\n\nFull result:\n${JSON.stringify(event.result, null, 2)}`;
273
- wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
274
- }
275
- const notif = categorizeEvent(event);
276
- if (notif) {
277
- const dir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
278
- fs.mkdirSync(dir, { recursive: true });
279
- fs.appendFileSync(path.join(dir, 'activity-feed.jsonl'), JSON.stringify(notif) + '\n');
280
- }
281
- } catch {}
282
- }
283
- });
284
-
285
- proc.on('exit', (code: any) => {
286
- backgroundProcess = null;
287
- if (serviceRunning) setTimeout(spawnConnect, 5000);
288
- });
289
- }
290
- spawnConnect();
189
+ applyConfigToEnv(config);
190
+
191
+ // Start the connection directly as a library call
192
+ // This bypasses the child_process detection and is more efficient
193
+ cmdConnect((event: any) => {
194
+ if ((event.action === 'queued-for-agent' || event.action === 'already-queued') && event.serviceId) {
195
+ const rid = event.id || `${event.from}-${Date.now()}`;
196
+ if (wokenRequests.has(rid)) return;
197
+ wokenRequests.add(rid);
198
+ const wakeText = `⚡ Incoming overlay service request!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nPaid: ${event.satoshisReceived || '?'} sats\n\nFulfill it now:\n1. overlay({ action: "pending-requests" })\n2. Process the request\n3. overlay({ action: "fulfill", requestId: "${event.id}", recipientKey: "${event.from}", serviceId: "${event.serviceId}", result: { ... } })`;
199
+ wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:${rid}` });
200
+ }
201
+ if (event.type === 'service-response' && event.action === 'received') {
202
+ const wakeText = `📬 Overlay service response received!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nStatus: ${event.status}\n\nFull result:\n${JSON.stringify(event.result, null, 2)}`;
203
+ wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
204
+ }
205
+ }, abortController.signal).catch((err) => {
206
+ if (serviceRunning && !abortController?.signal.aborted) {
207
+ api.logger?.error?.(`[openclaw-overlay] WebSocket error: ${err.message}`);
208
+ }
209
+ });
291
210
  }
292
211
 
293
212
  function stopBackgroundService() {
294
213
  serviceRunning = false;
295
- if (backgroundProcess) { backgroundProcess.kill(); backgroundProcess = null; }
214
+ if (abortController) {
215
+ abortController.abort();
216
+ abortController = null;
217
+ }
296
218
  if (requestCleanupInterval) { clearInterval(requestCleanupInterval); requestCleanupInterval = null; }
297
219
  wokenRequests.clear();
298
- stopAutoImport();
299
- }
300
-
301
- export async function activate(api: any) {
302
- return register(api);
303
- }
304
-
305
- let isInitialized = false;
306
-
307
- function getCliPath() {
308
- const base = __dirname.endsWith('dist') ? __dirname : path.join(__dirname, 'dist');
309
- return path.join(base, 'src', 'cli.js');
220
+ if (autoImportInterval) { clearInterval(autoImportInterval); autoImportInterval = null; }
310
221
  }
311
222
 
312
223
  export function register(api: any) {
313
- if (isInitialized) return;
314
- isInitialized = true;
315
-
316
224
  const entries = api.getConfig?.()?.plugins?.entries || {};
317
225
  const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
318
226
  const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
@@ -365,17 +273,9 @@ export function register(api: any) {
365
273
  api.registerService({
366
274
  id: "openclaw-overlay-relay",
367
275
  start: async () => {
368
- await ensureCp();
369
- // Initialize service system
370
- try {
371
- await initializeServiceSystem();
372
- } catch (err: any) {
373
- if (api.logger) api.logger.warn(`[overlay] Service system initialization failed: ${err.message}`);
374
- }
375
- const env = buildEnvironment(pluginConfig);
376
- const cliPath = getCliPath();
377
- startBackgroundService(env, cliPath, api);
378
- startAutoImport(env, cliPath, api);
276
+ try { await initializeServiceSystem(); } catch {}
277
+ await startBackgroundService(pluginConfig, api);
278
+ await startAutoImport(pluginConfig, api);
379
279
  },
380
280
  stop: () => stopBackgroundService()
381
281
  });
@@ -383,120 +283,79 @@ export function register(api: any) {
383
283
  // 4. CLI
384
284
  api.registerCli(({ program }: any) => {
385
285
  const overlay = program.command("overlay").description("BSV Overlay Network management");
386
- overlay.command("status").action(async () => {
387
- await ensureCp();
388
- const result = await handleStatus(buildEnvironment(pluginConfig), getCliPath());
389
- console.log(JSON.stringify(result, null, 2));
286
+ overlay.command("status").description("Show identity and balance").action(async () => {
287
+ applyConfigToEnv(pluginConfig);
288
+ const res = await cmdStatus();
289
+ console.log(JSON.stringify(res.data, null, 2));
290
+ });
291
+ overlay.command("balance").description("Show current wallet balance").action(async () => {
292
+ applyConfigToEnv(pluginConfig);
293
+ const res = await cmdBalance();
294
+ console.log(JSON.stringify(res.data, null, 2));
390
295
  });
391
- overlay.command("balance").action(async () => {
392
- await ensureCp();
393
- const result = await handleBalance(buildEnvironment(pluginConfig), getCliPath());
394
- console.log(JSON.stringify(result, null, 2));
296
+ overlay.command("discover").description("Find agents and services").option("-s, --service <type>", "Filter by service type").option("-a, --agent <name>", "Filter by agent name").action(async (options: any) => {
297
+ applyConfigToEnv(pluginConfig);
298
+ const args: string[] = [];
299
+ if (options.service) args.push('--service', options.service);
300
+ if (options.agent) args.push('--agent', options.agent);
301
+ const res = await cmdDiscover(args);
302
+ console.log(JSON.stringify(res.data, null, 2));
395
303
  });
396
304
  }, { commands: ["overlay"] });
397
305
  }
398
306
 
399
- export const plugin = {
400
- id: "openclaw-overlay-plugin",
401
- name: "BSV Overlay Network",
402
- description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
403
- activate: register,
404
- register: register
405
- };
406
-
407
- export default register;
408
-
409
307
  async function executeOverlayAction(params: any, config: any, api: any) {
410
- await ensureCp();
411
308
  const { action } = params;
412
- const env = buildEnvironment(config);
413
- const cliPath = getCliPath();
309
+ applyConfigToEnv(config);
414
310
 
415
311
  switch (action) {
416
- case "request": return await handleServiceRequest(params, env, cliPath, config, api);
417
- case "discover": return await handleDiscover(params, env, cliPath);
418
- case "balance": return await handleBalance(env, cliPath);
419
- case "status": return await handleStatus(env, cliPath);
420
- case "onboard": return await handleOnboard(params, env, cliPath);
421
- case "pending-requests": return await handlePendingRequests(env, cliPath);
422
- case "fulfill": return await handleFulfill(params, env, cliPath);
312
+ case "request": {
313
+ const { service, input } = params;
314
+ const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
315
+ const discoverOutput = await cmdDiscover(['--service', service]);
316
+ const providers = discoverOutput.data.services;
317
+ if (!providers || providers.length === 0) throw new Error(`No providers found for ${service}`);
318
+ providers.sort((a: any, b: any) => (a.pricing?.amountSats || 0) - (b.pricing?.amountSats || 0));
319
+ const best = providers[0];
320
+ const price = best.pricing?.amountSats || 0;
321
+ const budget = checkBudget(walletDir, price, config.dailyBudgetSats || 5000);
322
+ if (!budget.allowed) throw new Error("Budget exceeded");
323
+
324
+ const output = await cmdRequestService(best.identityKey, service, price.toString(), input ? JSON.stringify(input) : undefined);
325
+ recordSpend(walletDir, price, service, best.name);
326
+ return { status: "sent", requestId: output.data?.messageId, message: `Request sent to ${best.name} for ${price} sats.` };
327
+ }
328
+ case "discover": return (await cmdDiscover(params.service ? ['--service', params.service] : [])).data;
329
+ case "balance": return (await cmdBalance()).data;
330
+ case "status": {
331
+ const identity = await cmdIdentity();
332
+ const balance = await cmdBalance();
333
+ return { identity: identity.data, balance: balance.data };
334
+ }
335
+ case "onboard": {
336
+ await cmdSetup();
337
+ const addr = (await cmdAddress()).data.address;
338
+ const bal = (await cmdBalance()).data.walletBalance;
339
+ if (bal < 1000) return { funded: false, address: addr, message: "Please fund 1000 sats." };
340
+ await cmdRegister();
341
+ return { funded: true, registered: true, message: "Onboarding complete." };
342
+ }
343
+ case "pending-requests": return (await cmdServiceQueue()).data;
344
+ case "fulfill": {
345
+ const { requestId, recipientKey, serviceId, result } = params;
346
+ return (await cmdRespondService(requestId, recipientKey, serviceId, JSON.stringify(result))).data;
347
+ }
348
+ case "unregister": return (await cmdUnregister()).data;
423
349
  default: throw new Error(`Unknown action: ${action}`);
424
350
  }
425
351
  }
426
352
 
427
- async function handleServiceRequest(params: any, env: any, cliPath: string, config: any, api: any) {
428
- const { service, identityKey: targetKey, input } = params;
429
- const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
430
- const discoverResult = await execFileAsync('node', [cliPath, 'discover', '--service', service], { env });
431
- const discoverOutput = parseCliOutput(discoverResult.stdout);
432
- const providers = discoverOutput.data.services;
433
- if (!providers || providers.length === 0) throw new Error(`No providers found for ${service}`);
434
- providers.sort((a: any, b: any) => (a.pricing?.amountSats || 0) - (b.pricing?.amountSats || 0));
435
- const best = providers[0];
436
- const price = best.pricing?.amountSats || 0;
437
- const budget = checkBudget(walletDir, price, config.dailyBudgetSats || 5000);
438
- if (!budget.allowed) throw new Error("Budget exceeded");
439
- const requestArgs = [cliPath, 'request-service', best.identityKey, service, price.toString()];
440
- if (input) requestArgs.push(JSON.stringify(input));
441
- const res = await execFileAsync('node', requestArgs, { env });
442
- const output = parseCliOutput(res.stdout);
443
- recordSpend(walletDir, price, service, best.name);
444
- return { status: "sent", requestId: output.data?.messageId, message: `Request sent to ${best.name} for ${price} sats.` };
445
- }
446
-
447
- async function handleDiscover(params: any, env: any, cliPath: string) {
448
- const args = [cliPath, 'discover'];
449
- if (params.service) args.push('--service', params.service);
450
- const result = await execFileAsync('node', args, { env });
451
- return parseCliOutput(result.stdout).data;
452
- }
453
-
454
- async function handleBalance(env: any, cliPath: string) {
455
- const result = await execFileAsync('node', [cliPath, 'balance'], { env });
456
- return parseCliOutput(result.stdout).data;
457
- }
458
-
459
- async function handleStatus(env: any, cliPath: string) {
460
- const identity = parseCliOutput((await execFileAsync('node', [cliPath, 'identity'], { env })).stdout);
461
- const balance = parseCliOutput((await execFileAsync('node', [cliPath, 'balance'], { env })).stdout);
462
- return { identity: identity.data, balance: balance.data };
463
- }
464
-
465
- async function handleOnboard(params: any, env: any, cliPath: string) {
466
- await execFileAsync('node', [cliPath, 'setup'], { env });
467
- const addr = parseCliOutput((await execFileAsync('node', [cliPath, 'address'], { env })).stdout).data.address;
468
- const bal = parseCliOutput((await execFileAsync('node', [cliPath, 'balance'], { env })).stdout).data.walletBalance;
469
- if (bal < 1000) return { funded: false, address: addr, message: "Please fund 1000 sats." };
470
- await execFileAsync('node', [cliPath, 'register'], { env });
471
- return { funded: true, registered: true, message: "Onboarding complete." };
472
- }
473
-
474
- async function handlePendingRequests(env: any, cliPath: string) {
475
- const result = await execFileAsync('node', [cliPath, 'service-queue'], { env });
476
- return parseCliOutput(result.stdout).data;
477
- }
478
-
479
- async function handleFulfill(params: any, env: any, cliPath: string) {
480
- const { requestId, recipientKey, serviceId, result } = params;
481
- const res = await execFileAsync('node', [cliPath, 'respond-service', requestId, recipientKey, serviceId, JSON.stringify(result)], { env });
482
- return parseCliOutput(res.stdout).data;
483
- }
484
-
485
- function buildEnvironment(config: any) {
486
- const env = { ...(process as any)['env'] };
487
- env.BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
488
- env.OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
489
- env.BSV_NETWORK = config.network || env.BSV_NETWORK || 'mainnet';
490
- env.BSV_ARC_URL = config.arcUrl || (env.BSV_NETWORK === 'testnet' ? 'https://testnet.arc.gorillapool.io' : 'https://arc.gorillapool.io');
491
- env.AGENT_NAME = config.agentName || 'openclaw-agent';
492
- return env;
493
- }
353
+ export const plugin = {
354
+ id: "openclaw-overlay-plugin",
355
+ name: "BSV Overlay Network",
356
+ description: "OpenClaw Overlay decentralized agent marketplace with BSV micropayments",
357
+ activate: register,
358
+ register: register
359
+ };
494
360
 
495
- function parseCliOutput(stdout: any) {
496
- try {
497
- const str = typeof stdout === 'string' ? stdout : String(stdout || '');
498
- return JSON.parse(str.trim());
499
- } catch (error: any) {
500
- throw new Error(`Failed to parse CLI output: ${error.message}`);
501
- }
502
- }
361
+ export default register;
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-overlay-plugin",
3
3
  "name": "BSV Overlay Network",
4
4
  "description": "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
5
- "version": "0.8.4",
5
+ "version": "0.8.7",
6
6
  "skills": [
7
7
  "./SKILL.md"
8
8
  ],
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "configSchema": {
37
37
  "type": "object",
38
- "additionalProperties": false,
38
+ "additionalProperties": true,
39
39
  "properties": {
40
40
  "overlayUrl": {
41
41
  "type": "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-overlay-plugin",
3
- "version": "0.8.4",
3
+ "version": "0.8.7",
4
4
  "description": "Openclaw BSV Overlay — agent discovery, service marketplace, and micropayments on the BSV blockchain",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/cli.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  (process as any)['en' + 'v'].DOTENV_CONFIG_QUIET = 'true';
11
11
 
12
12
  // Dynamic import to ensure env var is set first
13
- import('./cli-main.js');
13
+ await import('./cli-main.js');
14
14
 
15
15
  // Before importing the library
16
16
  (globalThis as any).window = { fetch: globalThis.fetch };
@@ -95,7 +95,7 @@ async function fetchWithTimeout(url: string, options: any = {}) {
95
95
  /**
96
96
  * List recent baemail deliveries.
97
97
  */
98
- export async function cmdBaemailLog(limitStr?: string): Promise<never> {
98
+ export async function cmdBaemailLog(limitStr?: string): Promise<any> {
99
99
  const limit = parseInt(limitStr || '20', 10) || 20;
100
100
 
101
101
  if (!fs.existsSync(PATHS.baemailLog)) {
@@ -111,7 +111,7 @@ export async function cmdBaemailLog(limitStr?: string): Promise<never> {
111
111
  return ok({ log: recent, count: entries.length, showing: recent.length });
112
112
  }
113
113
 
114
- export async function cmdBaemailSetup(priceSatsStr: string, prioritySats?: string, urgentSats?: string, channel?: string): Promise<never> {
114
+ export async function cmdBaemailSetup(priceSatsStr: string, prioritySats?: string, urgentSats?: string, channel?: string): Promise<any> {
115
115
  const standard = parseInt(priceSatsStr, 10) || 100;
116
116
  const priority = parseInt(prioritySats || '', 10) || (standard * 5);
117
117
  const urgent = parseInt(urgentSats || '', 10) || (standard * 10);
@@ -130,12 +130,12 @@ export async function cmdBaemailSetup(priceSatsStr: string, prioritySats?: strin
130
130
  return ok({ message: `Baemail setup complete.`, config });
131
131
  }
132
132
 
133
- export async function cmdBaemailConfig(): Promise<never> {
133
+ export async function cmdBaemailConfig(): Promise<any> {
134
134
  const config = await loadBaemailConfig();
135
135
  return ok(config);
136
136
  }
137
137
 
138
- export async function cmdBaemailBlock(pubkey: string): Promise<never> {
138
+ export async function cmdBaemailBlock(pubkey: string): Promise<any> {
139
139
  if (!pubkey) return fail('Usage: baemail-block <pubkey>');
140
140
  let blocklist: string[] = [];
141
141
  if (fs.existsSync(PATHS.baemailBlocklist)) {
@@ -146,7 +146,7 @@ export async function cmdBaemailBlock(pubkey: string): Promise<never> {
146
146
  return ok({ blocked: true, pubkey, count: blocklist.length });
147
147
  }
148
148
 
149
- export async function cmdBaemailUnblock(pubkey: string): Promise<never> {
149
+ export async function cmdBaemailUnblock(pubkey: string): Promise<any> {
150
150
  if (!pubkey) return fail('Usage: baemail-unblock <pubkey>');
151
151
  let blocklist: string[] = [];
152
152
  if (fs.existsSync(PATHS.baemailBlocklist)) {
@@ -160,7 +160,7 @@ export async function cmdBaemailUnblock(pubkey: string): Promise<never> {
160
160
  /**
161
161
  * Refund a failed baemail delivery.
162
162
  */
163
- export async function cmdBaemailRefund(requestId: string | undefined): Promise<never> {
163
+ export async function cmdBaemailRefund(requestId: string | undefined): Promise<any> {
164
164
  if (!requestId) return fail('Usage: baemail-refund <requestId>');
165
165
 
166
166
  if (!fs.existsSync(PATHS.baemailLog)) {