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.
- package/dist/index.d.ts +0 -1
- package/dist/index.js +137 -267
- package/dist/src/cli.js +1 -1
- package/dist/src/scripts/baemail/commands.d.ts +6 -6
- package/dist/src/scripts/messaging/connect.d.ts +2 -2
- package/dist/src/scripts/messaging/connect.js +63 -16
- package/dist/src/scripts/messaging/inbox.d.ts +2 -2
- package/dist/src/scripts/messaging/poll.d.ts +1 -1
- package/dist/src/scripts/messaging/send.d.ts +1 -1
- package/dist/src/scripts/output.d.ts +5 -4
- package/dist/src/scripts/output.js +11 -2
- package/dist/src/scripts/overlay/advertisement.d.ts +2 -2
- package/dist/src/scripts/overlay/discover.d.ts +1 -1
- package/dist/src/scripts/overlay/registration.d.ts +2 -2
- package/dist/src/scripts/overlay/services.d.ts +4 -4
- package/dist/src/scripts/payment/commands.d.ts +3 -3
- package/dist/src/scripts/services/queue.d.ts +2 -2
- package/dist/src/scripts/services/request.d.ts +1 -1
- package/dist/src/scripts/services/respond.d.ts +2 -2
- package/dist/src/scripts/wallet/balance.d.ts +2 -2
- package/dist/src/scripts/wallet/setup.d.ts +4 -4
- package/dist/src/scripts/x-verification/commands.d.ts +6 -6
- package/index.ts +136 -277
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/scripts/baemail/commands.ts +6 -6
- package/src/scripts/messaging/connect.ts +49 -16
- package/src/scripts/messaging/inbox.ts +2 -2
- package/src/scripts/messaging/poll.ts +1 -1
- package/src/scripts/messaging/send.ts +1 -1
- package/src/scripts/output.ts +13 -4
- package/src/scripts/overlay/advertisement.ts +2 -2
- package/src/scripts/overlay/discover.ts +1 -1
- package/src/scripts/overlay/registration.ts +2 -2
- package/src/scripts/overlay/services.ts +4 -4
- package/src/scripts/payment/commands.ts +3 -3
- package/src/scripts/services/queue.ts +2 -2
- package/src/scripts/services/request.ts +1 -1
- package/src/scripts/services/respond.ts +2 -2
- package/src/scripts/wallet/balance.ts +2 -2
- package/src/scripts/wallet/setup.ts +4 -4
- 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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
const addrOutput =
|
|
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;
|
|
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
|
-
|
|
126
|
-
const importOutput =
|
|
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((
|
|
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
|
-
|
|
148
|
-
const regOutput =
|
|
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(
|
|
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
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
199
|
-
|
|
200
|
-
|
|
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 =
|
|
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
|
|
222
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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 (
|
|
214
|
+
if (abortController) {
|
|
215
|
+
abortController.abort();
|
|
216
|
+
abortController = null;
|
|
217
|
+
}
|
|
296
218
|
if (requestCleanupInterval) { clearInterval(requestCleanupInterval); requestCleanupInterval = null; }
|
|
297
219
|
wokenRequests.clear();
|
|
298
|
-
|
|
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
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
388
|
-
const
|
|
389
|
-
console.log(JSON.stringify(
|
|
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("
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
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
|
-
|
|
413
|
-
const cliPath = getCliPath();
|
|
309
|
+
applyConfigToEnv(config);
|
|
414
310
|
|
|
415
311
|
switch (action) {
|
|
416
|
-
case "request":
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
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;
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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":
|
|
38
|
+
"additionalProperties": true,
|
|
39
39
|
"properties": {
|
|
40
40
|
"overlayUrl": {
|
|
41
41
|
"type": "string",
|
package/package.json
CHANGED
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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)) {
|