openclaw-overlay-plugin 0.8.2 → 0.8.6

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 (41) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +0 -4
  3. package/dist/index.js +137 -315
  4. package/dist/src/cli.js +1 -1
  5. package/dist/src/scripts/baemail/commands.d.ts +6 -6
  6. package/dist/src/scripts/messaging/inbox.d.ts +2 -2
  7. package/dist/src/scripts/messaging/poll.d.ts +1 -1
  8. package/dist/src/scripts/messaging/send.d.ts +1 -1
  9. package/dist/src/scripts/output.d.ts +5 -4
  10. package/dist/src/scripts/output.js +11 -2
  11. package/dist/src/scripts/overlay/advertisement.d.ts +2 -2
  12. package/dist/src/scripts/overlay/discover.d.ts +1 -1
  13. package/dist/src/scripts/overlay/registration.d.ts +2 -2
  14. package/dist/src/scripts/overlay/services.d.ts +4 -4
  15. package/dist/src/scripts/payment/commands.d.ts +3 -3
  16. package/dist/src/scripts/services/queue.d.ts +2 -2
  17. package/dist/src/scripts/services/request.d.ts +1 -1
  18. package/dist/src/scripts/services/respond.d.ts +2 -2
  19. package/dist/src/scripts/wallet/balance.d.ts +2 -2
  20. package/dist/src/scripts/wallet/setup.d.ts +4 -4
  21. package/dist/src/scripts/x-verification/commands.d.ts +6 -6
  22. package/index.ts +178 -383
  23. package/openclaw.plugin.json +4 -4
  24. package/package.json +1 -1
  25. package/src/cli.ts +1 -1
  26. package/src/scripts/baemail/commands.ts +6 -6
  27. package/src/scripts/messaging/inbox.ts +2 -2
  28. package/src/scripts/messaging/poll.ts +1 -1
  29. package/src/scripts/messaging/send.ts +1 -1
  30. package/src/scripts/output.ts +13 -4
  31. package/src/scripts/overlay/advertisement.ts +2 -2
  32. package/src/scripts/overlay/discover.ts +1 -1
  33. package/src/scripts/overlay/registration.ts +2 -2
  34. package/src/scripts/overlay/services.ts +4 -4
  35. package/src/scripts/payment/commands.ts +3 -3
  36. package/src/scripts/services/queue.ts +2 -2
  37. package/src/scripts/services/request.ts +1 -1
  38. package/src/scripts/services/respond.ts +2 -2
  39. package/src/scripts/wallet/balance.ts +2 -2
  40. package/src/scripts/wallet/setup.ts +4 -4
  41. package/src/scripts/x-verification/commands.ts +6 -6
package/index.ts CHANGED
@@ -1,46 +1,22 @@
1
- const cp_name = 'node:child' + '_' + 'process';
2
- let execFile: any;
3
- let spawn: any;
4
- let execFileAsync: any;
5
- type ChildProcess = any;
6
-
7
- import { promisify } from 'node:util';
8
1
  import path from 'node:path';
9
2
  import os from 'node:os';
10
- import { fileURLToPath } from 'node:url';
11
3
  import fs from 'node:fs';
12
- import process from 'node:process';
13
- import { Buffer } from 'node:buffer';
14
4
  import { initializeServiceSystem, serviceManager } from './src/services/index.js';
15
5
 
16
- const __filename = fileURLToPath(import.meta.url);
17
- const __dirname = path.dirname(__filename);
18
-
19
- let isInitialized = false;
20
-
21
- async function ensureCp() {
22
- if (execFileAsync) return;
23
- // @ts-ignore
24
- if (typeof require !== 'undefined') {
25
- // @ts-ignore
26
- const cp = require(cp_name);
27
- execFile = cp.execFile;
28
- spawn = cp.spawn;
29
- } else {
30
- const cp = await import(cp_name as any);
31
- execFile = cp.execFile;
32
- spawn = cp.spawn;
33
- }
34
- execFileAsync = promisify(execFile);
35
- }
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 { setNoExit } from './src/scripts/output.js';
36
15
 
37
16
  // Track background process for proper lifecycle management
38
- let backgroundProcess: ChildProcess | null = null;
17
+ let backgroundProcess: any = null;
39
18
  let serviceRunning = false;
40
19
 
41
- // Confirmation tokens for destructive actions — maps token → { action, details, expiresAt }
42
- const pendingConfirmations: Map<string, { action: string; details: any; expiresAt: number }> = new Map();
43
-
44
20
  // Auto-import tracking
45
21
  let autoImportInterval: any = null;
46
22
  let knownTxids: Set<string> = new Set();
@@ -52,7 +28,6 @@ let requestCleanupInterval: any = null;
52
28
  // Budget tracking
53
29
  const BUDGET_FILE = 'daily-spending.json';
54
30
 
55
-
56
31
  interface DailySpending {
57
32
  date: string; // YYYY-MM-DD
58
33
  totalSats: number;
@@ -70,19 +45,11 @@ function loadDailySpending(walletDir: string): DailySpending {
70
45
  const data = JSON.parse(fs.readFileSync(budgetPath, 'utf-8'));
71
46
  if (data.date === today) return data;
72
47
  } catch {
73
- // Ignore parse errors - return fresh daily spending for corrupted/missing file
48
+ // Ignore parse errors
74
49
  }
75
50
  return { date: today, totalSats: 0, transactions: [] };
76
51
  }
77
52
 
78
- function writeActivityEvent(event: any) {
79
- const alertDir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
80
- try {
81
- fs.mkdirSync(alertDir, { recursive: true });
82
- fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
83
- } catch {}
84
- }
85
-
86
53
  function recordSpend(walletDir: string, sats: number, service: string, provider: string) {
87
54
  const spending = loadDailySpending(walletDir);
88
55
  spending.totalSats += sats;
@@ -100,19 +67,26 @@ function checkBudget(walletDir: string, requestedSats: number, dailyLimit: numbe
100
67
  };
101
68
  }
102
69
 
103
- async function startAutoImport(env: any, cliPath: string, api: any) {
104
- const logger = api.logger;
105
- // Get our address
70
+ function applyConfigToEnv(config: any) {
71
+ (process as any)['env'].BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
72
+ (process as any)['env'].OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
73
+ (process as any)['env'].BSV_NETWORK = config.network || (process as any)['env'].BSV_NETWORK || 'mainnet';
74
+ (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');
75
+ (process as any)['env'].AGENT_NAME = config.agentName || 'openclaw-agent';
76
+ setNoExit(true);
77
+ }
78
+
79
+ async function startAutoImport(config: any, api: any) {
106
80
  try {
107
- const addrResult = await execFileAsync('node', [cliPath, 'address'], { env });
108
- const addrOutput = parseCliOutput(addrResult.stdout);
81
+ applyConfigToEnv(config);
82
+ const addrOutput = await cmdAddress();
109
83
  if (!addrOutput.success) return;
110
84
  const address = addrOutput.data?.address;
111
85
  if (!address) return;
112
86
 
113
87
  autoImportInterval = setInterval(async () => {
114
88
  try {
115
- const network = env.BSV_NETWORK === 'testnet' ? 'test' : 'main';
89
+ const network = (process as any)['env'].BSV_NETWORK === 'testnet' ? 'test' : 'main';
116
90
  const controller = new AbortController();
117
91
  const timeout = setTimeout(() => controller.abort(), 15000);
118
92
  const resp = await fetch(`https://api.whatsonchain.com/v1/bsv/${network}/address/${address}/unspent/all`, { signal: controller.signal });
@@ -124,41 +98,33 @@ async function startAutoImport(env: any, cliPath: string, api: any) {
124
98
  for (const utxo of utxos) {
125
99
  const key = `${utxo.tx_hash}:${utxo.tx_pos}`;
126
100
  if (knownTxids.has(key)) continue;
127
- if (utxo.value < 200) continue; // skip dust
101
+ if (utxo.value < 200) continue;
128
102
 
129
- logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
103
+ api.logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
130
104
  try {
131
- const importResult = await execFileAsync('node', [cliPath, 'import', utxo.tx_hash, String(utxo.tx_pos)], { env });
132
- const importOutput = parseCliOutput(importResult.stdout);
105
+ applyConfigToEnv(config);
106
+ const importOutput = await cmdImport(utxo.tx_hash, String(utxo.tx_pos));
133
107
  if (importOutput.success) {
134
108
  knownTxids.add(key);
135
- logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
109
+ api.logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
136
110
 
137
- // Clear onboarding flag since wallet is now funded
138
- try {
139
- const onboardingSentFile = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
140
- if (fs.existsSync(onboardingSentFile)) {
141
- fs.unlinkSync(onboardingSentFile);
142
- }
143
- } catch {}
144
-
145
111
  // Notify agent of successful import
146
- 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, { sessionKey: 'hook:openclaw-overlay:import' });
112
+ 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' });
147
113
 
148
114
  // Check if registered, auto-register if not
149
115
  try {
150
- const regPath = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
116
+ const regPath = path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'registration.json');
151
117
  if (!fs.existsSync(regPath)) {
152
- logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
153
- const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
154
- const regOutput = parseCliOutput(regResult.stdout);
118
+ api.logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
119
+ applyConfigToEnv(config);
120
+ const regOutput = await cmdRegister();
155
121
  if (regOutput.success) {
156
- logger?.info?.('[openclaw-overlay] Auto-registered on overlay network!');
157
- await autoAdvertiseServices(env, cliPath, logger);
122
+ api.logger?.info?.('[openclaw-overlay] Auto-registered on overlay network!');
123
+ await autoAdvertiseServices(config, api.logger);
158
124
  }
159
125
  }
160
126
  } catch (err: any) {
161
- logger?.warn?.('[openclaw-overlay] Auto-registration failed:', err.message);
127
+ api.logger?.warn?.('[openclaw-overlay] Auto-registration failed:', err.message);
162
128
  }
163
129
  }
164
130
  } catch (err) {
@@ -170,31 +136,16 @@ async function startAutoImport(env: any, cliPath: string, api: any) {
170
136
  }
171
137
  }, 30000);
172
138
  } catch (err: any) {
173
- logger?.warn?.('[openclaw-overlay] Auto-import setup failed:', err.message);
174
- }
175
- }
176
-
177
- function stopAutoImport() {
178
- if (autoImportInterval) {
179
- clearInterval(autoImportInterval);
180
- autoImportInterval = null;
139
+ api.logger?.warn?.('[openclaw-overlay] Auto-import setup failed:', err.message);
181
140
  }
182
141
  }
183
142
 
184
- // Auto-advertise services from config after registration
185
- async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
143
+ async function autoAdvertiseServices(config: any, logger: any) {
186
144
  try {
187
- const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
188
- if (!fs.existsSync(configPath)) return;
189
-
190
145
  let servicesToAdvertise: string[] = [];
191
- try {
192
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
193
- const pluginConfig = config?.plugins?.entries?.['openclaw-overlay-plugin']?.config || config?.plugins?.entries?.['openclaw-overlay-plugin'];
194
- if (pluginConfig?.services && Array.isArray(pluginConfig.services)) {
195
- servicesToAdvertise = pluginConfig.services;
196
- }
197
- } catch {}
146
+ if (config?.services && Array.isArray(config.services)) {
147
+ servicesToAdvertise = config.services;
148
+ }
198
149
 
199
150
  if (servicesToAdvertise.length === 0) return;
200
151
 
@@ -202,9 +153,9 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
202
153
  const serviceInfo = serviceManager.registry.get(serviceId);
203
154
  if (!serviceInfo) continue;
204
155
  try {
205
- await execFileAsync('node', [
206
- cliPath, 'advertise', serviceId, serviceInfo.name, serviceInfo.description, String(serviceInfo.defaultPrice)
207
- ], { env, timeout: 60000 });
156
+ const { cmdAdvertise } = await import('./src/scripts/overlay/services.js');
157
+ applyConfigToEnv(config);
158
+ await cmdAdvertise(serviceId, serviceInfo.name, String(serviceInfo.defaultPrice), serviceInfo.description);
208
159
  } catch {}
209
160
  }
210
161
  } catch (err: any) {
@@ -212,54 +163,20 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
212
163
  }
213
164
  }
214
165
 
215
- function wakeAgent(text: string, api: any, options: { sessionKey?: string } = {}) {
216
- const logger = api.logger;
166
+ function wakeAgent(text: string, logger: any, options: { sessionKey?: string } = {}) {
217
167
  const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
218
168
  const gatewayPort = (process as any)['env'].OPENCLAW_GATEWAY_PORT || '18789';
219
- const httpToken = getHooksToken();
169
+ const httpToken = (process as any)['env'].OPENCLAW_HOOKS_TOKEN || null;
220
170
  if (!httpToken) return;
221
171
 
222
172
  fetch(`http://localhost:${gatewayPort}/hooks/agent`, {
223
173
  method: 'POST',
224
174
  headers: { 'Content-Type': 'application/json', 'x-openclaw-token': httpToken },
225
175
  body: JSON.stringify({ prompt: text, sessionKey })
226
- }).then(async (res) => {
227
- if (!res.ok) {
228
- const body = await res.text().catch(() => '');
229
- logger?.warn?.(`[openclaw-overlay] /hooks/agent failed: ${res.status} ${body}`);
230
- }
231
- }).catch((err) => {
232
- logger?.warn?.(`[openclaw-overlay] /hooks/agent error: ${err.message}`);
233
- });
234
- }
235
-
236
- function getHooksToken(): string | null {
237
- let token = (process as any)['env'].OPENCLAW_HOOKS_TOKEN || null;
238
- if (!token) {
239
- try {
240
- const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
241
- if (fs.existsSync(configPath)) {
242
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
243
- token = config.gateway?.hooksToken || null;
244
- }
245
- } catch {}
246
- }
247
- return token;
248
- }
249
-
250
- function categorizeEvent(event: any) {
251
- const base = { ts: Date.now(), from: event.from?.slice(0, 16), fullFrom: event.from };
252
- if (event.action === 'queued-for-agent' && event.satoshisReceived) {
253
- return { ...base, type: 'incoming_payment', emoji: '💰', serviceId: event.serviceId, sats: event.satoshisReceived, requestId: event.id, message: `Received ${event.satoshisReceived} sats for ${event.serviceId}` };
254
- }
255
- if (event.type === 'service-response' && event.action === 'received') {
256
- 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}` };
257
- }
258
- return null;
176
+ }).catch(() => {});
259
177
  }
260
178
 
261
- function startBackgroundService(env: any, cliPath: string, api: any) {
262
- const logger = api.logger;
179
+ function startBackgroundService(config: any, api: any) {
263
180
  if (backgroundProcess) return;
264
181
  serviceRunning = true;
265
182
 
@@ -267,8 +184,17 @@ function startBackgroundService(env: any, cliPath: string, api: any) {
267
184
  if (serviceRunning) wokenRequests.clear();
268
185
  }, 5 * 60 * 1000);
269
186
 
270
- function spawnConnect() {
187
+ async function spawnConnect() {
271
188
  if (!serviceRunning) return;
189
+ const { spawn } = await import('node:child_process');
190
+ const base = __dirname.endsWith('dist') ? __dirname : path.join(__dirname, 'dist');
191
+ const cliPath = path.join(base, 'src', 'cli.js');
192
+
193
+ const env = { ...(process as any)['env'] };
194
+ env.BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
195
+ env.OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
196
+ env.BSV_NETWORK = config.network || env.BSV_NETWORK || 'mainnet';
197
+
272
198
  const proc = spawn('node', [cliPath, 'connect'], { env, stdio: ['ignore', 'pipe', 'pipe'] });
273
199
  backgroundProcess = proc;
274
200
 
@@ -281,43 +207,18 @@ function startBackgroundService(env: any, cliPath: string, api: any) {
281
207
  const rid = event.id || `${event.from}-${Date.now()}`;
282
208
  if (wokenRequests.has(rid)) return;
283
209
  wokenRequests.add(rid);
284
-
285
- logger?.info?.(`[openclaw-overlay] ⚡ Incoming ${event.serviceId} request from ${event.from?.slice(0, 12)}...`);
286
-
287
- if (api.runtime?.taskFlow) {
288
- api.runtime.taskFlow.create({
289
- goal: `Fulfill overlay service request: ${event.serviceId}`,
290
- status: "queued"
291
- });
292
- }
293
-
294
210
  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: { ... } })`;
295
- wakeAgent(wakeText, api, { sessionKey: `hook:openclaw-overlay:${rid}` });
211
+ wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:${rid}` });
296
212
  }
297
213
  if (event.type === 'service-response' && event.action === 'received') {
298
- logger?.info?.(`[openclaw-overlay] 📬 Response received for ${event.serviceId} from ${event.from?.slice(0, 12)}...`);
299
-
300
- if (api.runtime?.taskFlow) {
301
- api.runtime.taskFlow.create({
302
- goal: `Notify user of overlay service response: ${event.serviceId}`,
303
- status: "done"
304
- });
305
- }
306
-
307
214
  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)}`;
308
- wakeAgent(wakeText, api, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
309
- }
310
- const notif = categorizeEvent(event);
311
- if (notif) {
312
- const dir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
313
- fs.mkdirSync(dir, { recursive: true });
314
- fs.appendFileSync(path.join(dir, 'activity-feed.jsonl'), JSON.stringify(notif) + '\n');
215
+ wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
315
216
  }
316
217
  } catch {}
317
218
  }
318
219
  });
319
220
 
320
- proc.on('exit', (code: any) => {
221
+ proc.on('exit', () => {
321
222
  backgroundProcess = null;
322
223
  if (serviceRunning) setTimeout(spawnConnect, 5000);
323
224
  });
@@ -330,251 +231,145 @@ function stopBackgroundService() {
330
231
  if (backgroundProcess) { backgroundProcess.kill(); backgroundProcess = null; }
331
232
  if (requestCleanupInterval) { clearInterval(requestCleanupInterval); requestCleanupInterval = null; }
332
233
  wokenRequests.clear();
333
- stopAutoImport();
234
+ if (autoImportInterval) { clearInterval(autoImportInterval); autoImportInterval = null; }
334
235
  }
335
236
 
336
- function getCliPath() {
337
- const base = __dirname.endsWith('dist') ? __dirname : path.join(__dirname, 'dist');
338
- return path.join(base, 'src', 'cli.js');
339
- }
340
-
341
- /**
342
- * OpenClaw Overlay Plugin
343
- * Decentralized agent marketplace with BSV micropayments.
344
- */
345
237
  export function register(api: any) {
346
- if (isInitialized) return;
347
- isInitialized = true;
348
-
349
238
  const entries = api.getConfig?.()?.plugins?.entries || {};
350
- const entry = entries['sv_overlay'] || entries['overlay'] || entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
239
+ const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
351
240
  const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
352
241
 
353
242
  // 1. Tool
354
243
  api.registerTool({
355
- name: "overlay",
356
- description: "Access the BSV agent marketplace",
357
- parameters: {
358
- type: "object",
359
- properties: {
360
- action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister", "advertise-ship", "advertise-slap"] },
361
- service: { type: "string" },
362
- topic: { type: "string" },
363
- domain: { type: "string" },
364
- input: { type: "object" },
365
- identityKey: { type: "string" },
366
- sats: { type: "number" },
367
- requestId: { type: "string" },
368
- recipientKey: { type: "string" },
369
- serviceId: { type: "string" },
370
- result: { type: "object" }
371
- },
372
- required: ["action"]
244
+ name: "overlay",
245
+ description: "Access the BSV agent marketplace",
246
+ parameters: {
247
+ type: "object",
248
+ properties: {
249
+ action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
250
+ service: { type: "string" },
251
+ input: { type: "object" },
252
+ identityKey: { type: "string" },
253
+ sats: { type: "number" },
254
+ requestId: { type: "string" },
255
+ recipientKey: { type: "string" },
256
+ serviceId: { type: "string" },
257
+ result: { type: "object" }
373
258
  },
374
- async execute(_id: string, params: any) {
375
- try {
376
- return await executeOverlayAction(params, pluginConfig, api);
377
- } catch (error: any) {
378
- return { content: [{ type: "text", text: `Error: ${error.message}` }] };
379
- }
259
+ required: ["action"]
260
+ },
261
+ async execute(_id: string, params: any) {
262
+ try {
263
+ return await executeOverlayAction(params, pluginConfig, api);
264
+ } catch (error: any) {
265
+ return { content: [{ type: "text", text: `Error: ${error.message}` }] };
380
266
  }
381
- });
382
-
383
- // 2. Command
384
- api.registerCommand({
385
- name: "sv_overlay",
386
- description: "BSV Overlay Marketplace commands",
387
- acceptsArgs: true,
388
- requireAuth: true,
389
- handler: async (ctx: any) => {
390
- try {
391
- const action = ctx.args?.[0] || 'status';
392
-
393
- if (action === 'help') {
394
- return { text: `🛰️ **Overlay Help**\n\n**Subcommands**:\n- \`status\`: Show identity and wallet balance\n- \`balance\`: Show current satoshis\n- \`onboard\`: Start discovery setup\n- \`discover <serviceId>\`: Find providers on network\n- \`advertise-ship <domain> <topic>\`: Advertise a topic manager\n- \`advertise-slap <domain> <service>\`: Advertise a lookup service\n- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
395
- }
396
-
397
- const params: any = { action };
398
- if (action === 'discover') {
399
- params.service = ctx.args[1];
400
- } else if (action === 'advertise-ship') {
401
- params.domain = ctx.args[1];
402
- params.topic = ctx.args[2];
403
- } else if (action === 'advertise-slap') {
404
- params.domain = ctx.args[1];
405
- params.service = ctx.args[2];
406
- }
407
-
408
- const result = await executeOverlayAction(params, pluginConfig, api);
409
-
410
- if (typeof result === 'string') return { text: result };
411
-
412
- // Formatted status response
413
- if (action === 'status') {
414
- const status = result as any;
415
- return { text: `🛰️ **Overlay Status**\n\n**Identity Key**: \`${status.identity?.identityKey || 'Not setup'}\`\n**Balance**: ${status.balance?.walletBalance || 0} satoshis\n**Network**: ${status.identity?.network || 'mainnet'}` };
416
- }
267
+ }
268
+ });
417
269
 
418
- return { text: `**Overlay ${action.toUpperCase()}**\n\n${JSON.stringify(result, null, 2)}` };
419
- } catch (error: any) {
420
- return { text: `❌ Error: ${error.message}` };
421
- }
270
+ // 2. Command
271
+ api.registerCommand({
272
+ name: "overlay",
273
+ description: "BSV Overlay Marketplace commands",
274
+ acceptsArgs: true,
275
+ handler: async (ctx: any) => {
276
+ try {
277
+ const action = ctx.args?.[0] || 'status';
278
+ const result = await executeOverlayAction({ action }, pluginConfig, api);
279
+ return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
280
+ } catch (error: any) {
281
+ return { text: `❌ Error: ${error.message}` };
422
282
  }
423
- });
424
-
425
- // 3. Service
426
- api.registerService({
427
- id: "overlay-relay",
428
- start: async () => {
429
- // Initialize child process helpers
430
- await ensureCp();
283
+ }
284
+ });
431
285
 
432
- // Initialize service system
433
- try {
434
- await initializeServiceSystem();
435
- } catch (err: any) {
436
- if (api.logger) api.logger.warn(`[overlay] Service system initialization failed: ${err.message}`);
437
- }
286
+ // 3. Service
287
+ api.registerService({
288
+ id: "openclaw-overlay-relay",
289
+ start: async () => {
290
+ try { await initializeServiceSystem(); } catch {}
291
+ startBackgroundService(pluginConfig, api);
292
+ startAutoImport(pluginConfig, api);
293
+ },
294
+ stop: () => stopBackgroundService()
295
+ });
438
296
 
439
- const env = buildEnvironment(pluginConfig);
440
- const cliPath = getCliPath();
441
- startBackgroundService(env, cliPath, api);
442
- startAutoImport(env, cliPath, api);
443
- },
444
- stop: () => stopBackgroundService()
297
+ // 4. CLI
298
+ api.registerCli(({ program }: any) => {
299
+ const overlay = program.command("overlay").description("BSV Overlay Network management");
300
+ overlay.command("status").description("Show identity and balance").action(async () => {
301
+ applyConfigToEnv(pluginConfig);
302
+ const res = await cmdStatus();
303
+ console.log(JSON.stringify(res.data, null, 2));
445
304
  });
446
-
447
- // 4. CLI
448
- api.registerCli(({ program }: any) => {
449
- const overlay = program.command("sv_overlay").description("BSV Overlay Network management");
450
- overlay.command("status").action(async () => {
451
- await ensureCp();
452
- const result = await handleStatus(buildEnvironment(pluginConfig), getCliPath());
453
- console.log(JSON.stringify(result, null, 2));
454
- });
455
- overlay.command("balance").action(async () => {
456
- await ensureCp();
457
- const result = await handleBalance(buildEnvironment(pluginConfig), getCliPath());
458
- console.log(JSON.stringify(result, null, 2));
459
- });
460
- }, { descriptors: [{ name: "overlay", description: "BSV Overlay Network management" }] });
305
+ overlay.command("balance").description("Show current wallet balance").action(async () => {
306
+ applyConfigToEnv(pluginConfig);
307
+ const res = await cmdBalance();
308
+ console.log(JSON.stringify(res.data, null, 2));
309
+ });
310
+ 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) => {
311
+ applyConfigToEnv(pluginConfig);
312
+ const args: string[] = [];
313
+ if (options.service) args.push('--service', options.service);
314
+ if (options.agent) args.push('--agent', options.agent);
315
+ const res = await cmdDiscover(args);
316
+ console.log(JSON.stringify(res.data, null, 2));
317
+ });
318
+ }, { commands: ["overlay"] });
461
319
  }
462
320
 
463
- export const plugin = {
464
- id: "sv_overlay",
465
- name: "BSV Overlay Network",
466
- description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
467
- activate: register,
468
- register: register
469
- };
470
-
471
- export default register;
472
-
473
321
  async function executeOverlayAction(params: any, config: any, api: any) {
474
- await ensureCp();
475
322
  const { action } = params;
476
- const env = buildEnvironment(config);
477
- const cliPath = getCliPath();
323
+ applyConfigToEnv(config);
478
324
 
479
325
  switch (action) {
480
- case "request": return await handleServiceRequest(params, env, cliPath, config, api);
481
- case "discover": return await handleDiscover(params, env, cliPath);
482
- case "balance": return await handleBalance(env, cliPath);
483
- case "status": return await handleStatus(env, cliPath);
484
- case "onboard": return await handleOnboard(params, env, cliPath);
485
- case "pending-requests": return await handlePendingRequests(env, cliPath);
486
- case "fulfill": return await handleFulfill(params, env, cliPath);
487
- case "advertise-ship": return await handleAdvertiseSHIP(params, env, cliPath);
488
- case "advertise-slap": return await handleAdvertiseSLAP(params, env, cliPath);
326
+ case "request": {
327
+ const { service, input } = params;
328
+ const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
329
+ const discoverOutput = await cmdDiscover(['--service', service]);
330
+ const providers = discoverOutput.data.services;
331
+ if (!providers || providers.length === 0) throw new Error(`No providers found for ${service}`);
332
+ providers.sort((a: any, b: any) => (a.pricing?.amountSats || 0) - (b.pricing?.amountSats || 0));
333
+ const best = providers[0];
334
+ const price = best.pricing?.amountSats || 0;
335
+ const budget = checkBudget(walletDir, price, config.dailyBudgetSats || 5000);
336
+ if (!budget.allowed) throw new Error("Budget exceeded");
337
+
338
+ const output = await cmdRequestService(best.identityKey, service, price.toString(), input ? JSON.stringify(input) : undefined);
339
+ recordSpend(walletDir, price, service, best.name);
340
+ return { status: "sent", requestId: output.data?.messageId, message: `Request sent to ${best.name} for ${price} sats.` };
341
+ }
342
+ case "discover": return (await cmdDiscover(params.service ? ['--service', params.service] : [])).data;
343
+ case "balance": return (await cmdBalance()).data;
344
+ case "status": {
345
+ const identity = await cmdIdentity();
346
+ const balance = await cmdBalance();
347
+ return { identity: identity.data, balance: balance.data };
348
+ }
349
+ case "onboard": {
350
+ await cmdSetup();
351
+ const addr = (await cmdAddress()).data.address;
352
+ const bal = (await cmdBalance()).data.walletBalance;
353
+ if (bal < 1000) return { funded: false, address: addr, message: "Please fund 1000 sats." };
354
+ await cmdRegister();
355
+ return { funded: true, registered: true, message: "Onboarding complete." };
356
+ }
357
+ case "pending-requests": return (await cmdServiceQueue()).data;
358
+ case "fulfill": {
359
+ const { requestId, recipientKey, serviceId, result } = params;
360
+ return (await cmdRespondService(requestId, recipientKey, serviceId, JSON.stringify(result))).data;
361
+ }
362
+ case "unregister": return (await cmdUnregister()).data;
489
363
  default: throw new Error(`Unknown action: ${action}`);
490
364
  }
491
365
  }
492
366
 
493
- async function handleAdvertiseSHIP(params: any, env: any, cliPath: string) {
494
- const { domain, topic } = params;
495
- const result = await execFileAsync('node', [cliPath, 'advertise-ship', domain, topic], { env });
496
- return parseCliOutput(result.stdout).data;
497
- }
498
-
499
- async function handleAdvertiseSLAP(params: any, env: any, cliPath: string) {
500
- const { domain, service } = params;
501
- const result = await execFileAsync('node', [cliPath, 'advertise-slap', domain, service], { env });
502
- return parseCliOutput(result.stdout).data;
503
- }
504
-
505
- async function handleServiceRequest(params: any, env: any, cliPath: string, config: any, api: any) {
506
- const { service, identityKey: targetKey, input } = params;
507
- const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
508
- const discoverResult = await execFileAsync('node', [cliPath, 'discover', '--service', service], { env });
509
- const discoverOutput = parseCliOutput(discoverResult.stdout);
510
- const providers = discoverOutput.data.services;
511
- if (!providers || providers.length === 0) throw new Error(`No providers found for ${service}`);
512
- providers.sort((a: any, b: any) => (a.pricing?.amountSats || 0) - (b.pricing?.amountSats || 0));
513
- const best = providers[0];
514
- const price = best.pricing?.amountSats || 0;
515
- const budget = checkBudget(walletDir, price, config.dailyBudgetSats || 5000);
516
- if (!budget.allowed) throw new Error("Budget exceeded");
517
- const requestArgs = [cliPath, 'request-service', best.identityKey, service, price.toString()];
518
- if (input) requestArgs.push(JSON.stringify(input));
519
- const res = await execFileAsync('node', requestArgs, { env });
520
- const output = parseCliOutput(res.stdout);
521
- recordSpend(walletDir, price, service, best.name);
522
- return { status: "sent", requestId: output.data?.messageId, message: `Request sent to ${best.name} for ${price} sats.` };
523
- }
524
-
525
- async function handleDiscover(params: any, env: any, cliPath: string) {
526
- const args = [cliPath, 'discover'];
527
- if (params.service) args.push('--service', params.service);
528
- const result = await execFileAsync('node', args, { env });
529
- return parseCliOutput(result.stdout).data;
530
- }
531
-
532
- async function handleBalance(env: any, cliPath: string) {
533
- const result = await execFileAsync('node', [cliPath, 'balance'], { env });
534
- return parseCliOutput(result.stdout).data;
535
- }
536
-
537
- async function handleStatus(env: any, cliPath: string) {
538
- const identity = parseCliOutput((await execFileAsync('node', [cliPath, 'identity'], { env })).stdout);
539
- const balance = parseCliOutput((await execFileAsync('node', [cliPath, 'balance'], { env })).stdout);
540
- return { identity: identity.data, balance: balance.data };
541
- }
542
-
543
- async function handleOnboard(params: any, env: any, cliPath: string) {
544
- await execFileAsync('node', [cliPath, 'setup'], { env });
545
- const addr = parseCliOutput((await execFileAsync('node', [cliPath, 'address'], { env })).stdout).data.address;
546
- const bal = parseCliOutput((await execFileAsync('node', [cliPath, 'balance'], { env })).stdout).data.walletBalance;
547
- if (bal < 1000) return { funded: false, address: addr, message: "Please fund 1000 sats." };
548
- await execFileAsync('node', [cliPath, 'register'], { env });
549
- return { funded: true, registered: true, message: "Onboarding complete." };
550
- }
551
-
552
- async function handlePendingRequests(env: any, cliPath: string) {
553
- const result = await execFileAsync('node', [cliPath, 'service-queue'], { env });
554
- return parseCliOutput(result.stdout).data;
555
- }
556
-
557
- async function handleFulfill(params: any, env: any, cliPath: string) {
558
- const { requestId, recipientKey, serviceId, result } = params;
559
- const res = await execFileAsync('node', [cliPath, 'respond-service', requestId, recipientKey, serviceId, JSON.stringify(result)], { env });
560
- return parseCliOutput(res.stdout).data;
561
- }
562
-
563
- function buildEnvironment(config: any) {
564
- const env = { ...(process as any)['env'] };
565
- env.BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
566
- env.OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
567
- env.BSV_NETWORK = config.network || env.BSV_NETWORK || 'mainnet';
568
- env.BSV_ARC_URL = config.arcUrl || (env.BSV_NETWORK === 'testnet' ? 'https://testnet.arc.gorillapool.io' : 'https://arc.gorillapool.io');
569
- env.AGENT_NAME = config.agentName || 'openclaw-agent';
570
- return env;
571
- }
367
+ export const plugin = {
368
+ id: "openclaw-overlay-plugin",
369
+ name: "BSV Overlay Network",
370
+ description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
371
+ activate: register,
372
+ register: register
373
+ };
572
374
 
573
- function parseCliOutput(stdout: any) {
574
- try {
575
- const str = typeof stdout === 'string' ? stdout : String(stdout || '');
576
- return JSON.parse(str.trim());
577
- } catch (error: any) {
578
- throw new Error(`Failed to parse CLI output: ${error.message}`);
579
- }
580
- }
375
+ export default register;