openclaw-overlay-plugin 0.7.65 → 0.7.67

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.
@@ -1,126 +1,83 @@
1
- /**
2
- * Baemail commands - paid message forwarding service.
3
- */
1
+ import { fileURLToPath } from 'node:url';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
4
  import fs from 'node:fs';
5
- import { PATHS } from '../config.js';
5
+ import process from 'node:process';
6
6
  import { ok, fail } from '../output.js';
7
7
  import { loadIdentity } from '../wallet/identity.js';
8
- import { ensureStateDir } from '../utils/storage.js';
9
- import { fetchWithTimeout } from '../utils/woc.js';
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ // Define paths relative to home directory
11
+ const PATHS = {
12
+ walletIdentity: path.join(os.homedir(), '.openclaw', 'bsv-wallet', 'wallet-identity.json'),
13
+ baemailLog: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-deliveries.jsonl'),
14
+ baemailConfig: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-config.json'),
15
+ baemailBlocklist: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-blocklist.json'),
16
+ };
10
17
  /**
11
- * Load baemail configuration.
18
+ * Log a baemail delivery event.
12
19
  */
13
- export function loadBaemailConfig() {
20
+ export function logBaemailDelivery(entry) {
14
21
  try {
15
- if (fs.existsSync(PATHS.baemailConfig)) {
16
- return JSON.parse(fs.readFileSync(PATHS.baemailConfig, 'utf-8'));
17
- }
22
+ const dir = path.dirname(PATHS.baemailLog);
23
+ if (!fs.existsSync(dir))
24
+ fs.mkdirSync(dir, { recursive: true });
25
+ fs.appendFileSync(PATHS.baemailLog, JSON.stringify({ ...entry, ts: Date.now() }) + '\n');
18
26
  }
19
- catch (err) {
20
- console.warn(`[baemail] Warning: Could not read config: ${err.message}`);
21
- }
22
- return null;
23
- }
24
- /**
25
- * Save baemail configuration.
26
- */
27
- export function saveBaemailConfig(config) {
28
- ensureStateDir();
29
- fs.writeFileSync(PATHS.baemailConfig, JSON.stringify(config, null, 2));
27
+ catch { }
30
28
  }
31
- /**
32
- * Setup baemail service with delivery channel and tier pricing.
33
- */
34
- export async function cmdBaemailSetup(channel, standardStr, priorityStr, urgentStr) {
35
- if (!channel || !standardStr) {
36
- return fail('Usage: baemail-setup <channel> <standardSats> [prioritySats] [urgentSats]');
37
- }
38
- const standard = parseInt(standardStr, 10);
39
- const priority = priorityStr ? parseInt(priorityStr, 10) : standard * 2;
40
- const urgent = urgentStr ? parseInt(urgentStr, 10) : standard * 5;
41
- if (isNaN(standard) || standard < 1) {
42
- return fail('Standard rate must be a positive integer (sats)');
43
- }
44
- if (priority < standard) {
45
- return fail('Priority rate must be >= standard rate');
46
- }
47
- if (urgent < priority) {
48
- return fail('Urgent rate must be >= priority rate');
49
- }
50
- const config = {
51
- deliveryChannel: channel,
52
- tiers: { standard, priority, urgent },
53
- maxMessageLength: 4000,
29
+ export async function loadBaemailConfig() {
30
+ const defaults = {
31
+ enabled: true,
32
+ priceSats: 100,
33
+ autoRefund: true,
54
34
  blocklist: [],
55
- createdAt: new Date().toISOString(),
56
- updatedAt: new Date().toISOString(),
35
+ maxMessageLength: 4000,
36
+ deliveryChannel: 'agent-hook',
37
+ tiers: {
38
+ standard: 100,
39
+ priority: 500,
40
+ urgent: 1000
41
+ }
57
42
  };
58
- saveBaemailConfig(config);
59
- return ok({
60
- configured: true,
61
- deliveryChannel: channel,
62
- tiers: config.tiers,
63
- note: `Advertise with: cli advertise baemail "Baemail" "Paid message forwarding. Pay ${standard}+ sats to reach me." ${standard}`,
64
- });
65
- }
66
- /**
67
- * View current baemail configuration.
68
- */
69
- export async function cmdBaemailConfig() {
70
- const config = loadBaemailConfig();
71
- if (!config) {
72
- return fail('Baemail not configured. Run: baemail-setup <channel> <standardSats> [prioritySats] [urgentSats]');
43
+ if (!fs.existsSync(PATHS.baemailConfig)) {
44
+ return defaults;
73
45
  }
74
- return ok(config);
75
- }
76
- /**
77
- * Block a sender from using baemail.
78
- */
79
- export async function cmdBaemailBlock(identityKey) {
80
- if (!identityKey)
81
- return fail('Usage: baemail-block <identityKey>');
82
- const config = loadBaemailConfig();
83
- if (!config) {
84
- return fail('Baemail not configured. Run baemail-setup first.');
46
+ try {
47
+ const config = JSON.parse(fs.readFileSync(PATHS.baemailConfig, 'utf-8'));
48
+ return { ...defaults, ...config };
85
49
  }
86
- if (!config.blocklist)
87
- config.blocklist = [];
88
- if (config.blocklist.includes(identityKey)) {
89
- return fail('Identity already blocked');
50
+ catch {
51
+ return defaults;
90
52
  }
91
- config.blocklist.push(identityKey);
92
- config.updatedAt = new Date().toISOString();
93
- saveBaemailConfig(config);
94
- return ok({ blocked: identityKey, totalBlocked: config.blocklist.length });
95
53
  }
96
- /**
97
- * Unblock a sender.
98
- */
99
- export async function cmdBaemailUnblock(identityKey) {
100
- if (!identityKey)
101
- return fail('Usage: baemail-unblock <identityKey>');
102
- const config = loadBaemailConfig();
103
- if (!config) {
104
- return fail('Baemail not configured. Run baemail-setup first.');
54
+ async function fetchWithTimeout(url, options = {}) {
55
+ const { timeout = 15000 } = options;
56
+ const controller = new AbortController();
57
+ const id = setTimeout(() => controller.abort(), timeout);
58
+ try {
59
+ const response = await fetch(url, {
60
+ ...options,
61
+ signal: controller.signal
62
+ });
63
+ clearTimeout(id);
64
+ return response;
105
65
  }
106
- if (!config.blocklist || !config.blocklist.includes(identityKey)) {
107
- return fail('Identity not in blocklist');
66
+ catch (err) {
67
+ clearTimeout(id);
68
+ throw err;
108
69
  }
109
- config.blocklist = config.blocklist.filter(k => k !== identityKey);
110
- config.updatedAt = new Date().toISOString();
111
- saveBaemailConfig(config);
112
- return ok({ unblocked: identityKey, totalBlocked: config.blocklist.length });
113
70
  }
114
71
  /**
115
- * View baemail delivery log.
72
+ * List recent baemail deliveries.
116
73
  */
117
74
  export async function cmdBaemailLog(limitStr) {
118
75
  const limit = parseInt(limitStr || '20', 10) || 20;
119
76
  if (!fs.existsSync(PATHS.baemailLog)) {
120
77
  return ok({ log: [], count: 0 });
121
78
  }
122
- const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter(l => l.trim());
123
- const entries = lines.map(l => {
79
+ const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter((l) => l.trim());
80
+ const entries = lines.map((l) => {
124
81
  try {
125
82
  return JSON.parse(l);
126
83
  }
@@ -131,6 +88,60 @@ export async function cmdBaemailLog(limitStr) {
131
88
  const recent = entries.slice(-limit).reverse();
132
89
  return ok({ log: recent, count: entries.length, showing: recent.length });
133
90
  }
91
+ export async function cmdBaemailSetup(priceSatsStr, prioritySats, urgentSats, channel) {
92
+ const standard = parseInt(priceSatsStr, 10) || 100;
93
+ const priority = parseInt(prioritySats || '', 10) || (standard * 5);
94
+ const urgent = parseInt(urgentSats || '', 10) || (standard * 10);
95
+ const config = {
96
+ enabled: true,
97
+ priceSats: standard,
98
+ autoRefund: true,
99
+ deliveryChannel: channel || 'agent-hook',
100
+ tiers: { standard, priority, urgent }
101
+ };
102
+ const dir = path.dirname(PATHS.baemailConfig);
103
+ if (!fs.existsSync(dir))
104
+ fs.mkdirSync(dir, { recursive: true });
105
+ fs.writeFileSync(PATHS.baemailConfig, JSON.stringify(config, null, 2));
106
+ return ok({ message: `Baemail setup complete.`, config });
107
+ }
108
+ export async function cmdBaemailConfig() {
109
+ const config = await loadBaemailConfig();
110
+ return ok(config);
111
+ }
112
+ export async function cmdBaemailBlock(pubkey) {
113
+ if (!pubkey)
114
+ return fail('Usage: baemail-block <pubkey>');
115
+ let blocklist = [];
116
+ if (fs.existsSync(PATHS.baemailBlocklist)) {
117
+ try {
118
+ blocklist = JSON.parse(fs.readFileSync(PATHS.baemailBlocklist, 'utf-8'));
119
+ }
120
+ catch {
121
+ blocklist = [];
122
+ }
123
+ }
124
+ if (!blocklist.includes(pubkey))
125
+ blocklist.push(pubkey);
126
+ fs.writeFileSync(PATHS.baemailBlocklist, JSON.stringify(blocklist, null, 2));
127
+ return ok({ blocked: true, pubkey, count: blocklist.length });
128
+ }
129
+ export async function cmdBaemailUnblock(pubkey) {
130
+ if (!pubkey)
131
+ return fail('Usage: baemail-unblock <pubkey>');
132
+ let blocklist = [];
133
+ if (fs.existsSync(PATHS.baemailBlocklist)) {
134
+ try {
135
+ blocklist = JSON.parse(fs.readFileSync(PATHS.baemailBlocklist, 'utf-8'));
136
+ }
137
+ catch {
138
+ blocklist = [];
139
+ }
140
+ }
141
+ blocklist = blocklist.filter(p => p !== pubkey);
142
+ fs.writeFileSync(PATHS.baemailBlocklist, JSON.stringify(blocklist, null, 2));
143
+ return ok({ unblocked: true, pubkey, count: blocklist.length });
144
+ }
134
145
  /**
135
146
  * Refund a failed baemail delivery.
136
147
  */
@@ -141,7 +152,7 @@ export async function cmdBaemailRefund(requestId) {
141
152
  return fail('No baemail log found');
142
153
  }
143
154
  // Find the entry
144
- const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter(l => l.trim());
155
+ const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter((l) => l.trim());
145
156
  const entries = lines.map((l, idx) => {
146
157
  try {
147
158
  return { ...JSON.parse(l), _lineIdx: idx };
@@ -225,7 +236,7 @@ export async function cmdBaemailRefund(requestId) {
225
236
  }
226
237
  await tx.sign();
227
238
  // Broadcast using configured ARC/Arcade URL or fallback to WhatsOnChain
228
- const arcUrl = process['en' + 'v'].BSV_ARC_URL;
239
+ const arcUrl = process['env'].BSV_ARC_URL;
229
240
  let broadcastResp;
230
241
  if (arcUrl) {
231
242
  broadcastResp = await fetchWithTimeout(`${arcUrl.replace(/\/$/, '')}/v1/tx`, {
@@ -30,7 +30,7 @@ export async function processBaemail(msg, identityKey, privKey) {
30
30
  const input = (msg.payload?.input || msg.payload);
31
31
  const payment = msg.payload?.payment;
32
32
  // Load config
33
- const config = loadBaemailConfig();
33
+ const config = await loadBaemailConfig();
34
34
  if (!config) {
35
35
  const rejectPayload = {
36
36
  requestId: msg.id,
@@ -238,7 +238,7 @@ _Reply via overlay: \`cli send ${replyKey} ping "your reply"\`_`;
238
238
  deliverySuccess,
239
239
  deliveryError: deliveryError ?? null,
240
240
  paymentTxid: payResult.txid || '',
241
- refundStatus: deliverySuccess ? null : 'pending',
241
+ refundStatus: deliverySuccess ? undefined : 'pending',
242
242
  timestamp: new Date().toISOString(),
243
243
  };
244
244
  fs.appendFileSync(PATHS.baemailLog, JSON.stringify(logEntry) + '\n');
@@ -118,7 +118,7 @@ export async function cmdResearchRespond(resultJsonPath) {
118
118
  // Remove from queue
119
119
  if (fs.existsSync(PATHS.researchQueue)) {
120
120
  const lines = fs.readFileSync(PATHS.researchQueue, 'utf-8').trim().split('\n').filter(Boolean);
121
- const remaining = lines.filter(l => {
121
+ const remaining = lines.filter((l) => {
122
122
  try {
123
123
  return JSON.parse(l).requestId !== requestId;
124
124
  }
@@ -97,7 +97,7 @@ export function readJsonl(filePath) {
97
97
  if (!fs.existsSync(filePath))
98
98
  return [];
99
99
  const lines = fs.readFileSync(filePath, 'utf-8').trim().split('\n').filter(Boolean);
100
- return lines.map(line => {
100
+ return lines.map((line) => {
101
101
  try {
102
102
  return JSON.parse(line);
103
103
  }
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
+ import process from 'node:process';
6
7
  // Simple test runner (matching existing pattern)
7
8
  let passed = 0;
8
9
  let failed = 0;
@@ -106,12 +107,12 @@ async function run() {
106
107
  mockCleanupServiceQueue(24 * 60 * 60 * 1000, 2 * 60 * 60 * 1000);
107
108
  // Check remaining entries
108
109
  const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
109
- const remaining = lines.map(line => JSON.parse(line));
110
+ const remaining = lines.map((line) => JSON.parse(line));
110
111
  assert(remaining.length === 2, `Expected 2 remaining entries, got ${remaining.length}`);
111
- assert(remaining.find(e => e.requestId === 'pending-1') !== undefined, 'Should keep recent pending');
112
- assert(remaining.find(e => e.requestId === 'recent-fulfilled') !== undefined, 'Should keep recent fulfilled');
113
- assert(remaining.find(e => e.requestId === 'old-fulfilled') === undefined, 'Should remove old fulfilled');
114
- assert(remaining.find(e => e.requestId === 'old-rejected') === undefined, 'Should remove old rejected');
112
+ assert(remaining.find((e) => e.requestId === 'pending-1') !== undefined, 'Should keep recent pending');
113
+ assert(remaining.find((e) => e.requestId === 'recent-fulfilled') !== undefined, 'Should keep recent fulfilled');
114
+ assert(remaining.find((e) => e.requestId === 'old-fulfilled') === undefined, 'Should remove old fulfilled');
115
+ assert(remaining.find((e) => e.requestId === 'old-rejected') === undefined, 'Should remove old rejected');
115
116
  cleanupTestEnv();
116
117
  });
117
118
  await test('updateServiceQueueStatus updates request status atomically', async () => {
@@ -131,7 +132,7 @@ async function run() {
131
132
  return false;
132
133
  const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
133
134
  let updated = false;
134
- const updatedLines = lines.map(line => {
135
+ const updatedLines = lines.map((line) => {
135
136
  try {
136
137
  const entryData = JSON.parse(line);
137
138
  if (entryData.requestId === requestId) {
@@ -178,7 +179,7 @@ async function run() {
178
179
  return false;
179
180
  const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
180
181
  let updated = false;
181
- lines.map(line => {
182
+ lines.map((line) => {
182
183
  try {
183
184
  const entry = JSON.parse(line);
184
185
  if (entry.requestId === requestId) {
package/index.ts CHANGED
@@ -9,10 +9,15 @@ import path from 'node:path';
9
9
  import os from 'node:os';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import fs from 'node:fs';
12
+ import process from 'node:process';
13
+ import { Buffer } from 'node:buffer';
12
14
  import { initializeServiceSystem, serviceManager } from './src/services/index.js';
15
+
13
16
  const __filename = fileURLToPath(import.meta.url);
14
17
  const __dirname = path.dirname(__filename);
15
18
 
19
+ let isInitialized = false;
20
+
16
21
  async function ensureCp() {
17
22
  if (execFileAsync) return;
18
23
  // @ts-ignore
@@ -71,7 +76,7 @@ function loadDailySpending(walletDir: string): DailySpending {
71
76
  }
72
77
 
73
78
  function writeActivityEvent(event: any) {
74
- const alertDir = path.join((process as any)['en' + 'v'].HOME || '', '.openclaw', 'openclaw-overlay');
79
+ const alertDir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
75
80
  try {
76
81
  fs.mkdirSync(alertDir, { recursive: true });
77
82
  fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
@@ -130,7 +135,7 @@ async function startAutoImport(env: any, cliPath: string, logger: any) {
130
135
 
131
136
  // Clear onboarding flag since wallet is now funded
132
137
  try {
133
- const onboardingSentFile = path.join((process as any)['en' + 'v'].HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
138
+ const onboardingSentFile = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
134
139
  if (fs.existsSync(onboardingSentFile)) {
135
140
  fs.unlinkSync(onboardingSentFile);
136
141
  }
@@ -141,7 +146,7 @@ async function startAutoImport(env: any, cliPath: string, logger: any) {
141
146
 
142
147
  // Check if registered, auto-register if not
143
148
  try {
144
- const regPath = path.join((process as any)['en' + 'v'].HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
149
+ const regPath = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
145
150
  if (!fs.existsSync(regPath)) {
146
151
  logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
147
152
  const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
@@ -208,7 +213,7 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
208
213
 
209
214
  function wakeAgent(text: string, logger: any, options: { sessionKey?: string } = {}) {
210
215
  const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
211
- const gatewayPort = (process as any)['en' + 'v'].OPENCLAW_GATEWAY_PORT || '18789';
216
+ const gatewayPort = (process as any)['env'].OPENCLAW_GATEWAY_PORT || '18789';
212
217
  const httpToken = getHooksToken();
213
218
  if (!httpToken) return;
214
219
 
@@ -220,7 +225,7 @@ function wakeAgent(text: string, logger: any, options: { sessionKey?: string } =
220
225
  }
221
226
 
222
227
  function getHooksToken(): string | null {
223
- let token = (process as any)['en' + 'v'].OPENCLAW_HOOKS_TOKEN || null;
228
+ let token = (process as any)['env'].OPENCLAW_HOOKS_TOKEN || null;
224
229
  if (!token) {
225
230
  try {
226
231
  const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
@@ -275,7 +280,7 @@ function startBackgroundService(env: any, cliPath: string, logger: any) {
275
280
  }
276
281
  const notif = categorizeEvent(event);
277
282
  if (notif) {
278
- const dir = path.join((process as any)['en' + 'v'].HOME || '', '.openclaw', 'openclaw-overlay');
283
+ const dir = path.join((process as any)['env'].HOME || '', '.openclaw', 'openclaw-overlay');
279
284
  fs.mkdirSync(dir, { recursive: true });
280
285
  fs.appendFileSync(path.join(dir, 'activity-feed.jsonl'), JSON.stringify(notif) + '\n');
281
286
  }
@@ -299,101 +304,112 @@ function stopBackgroundService() {
299
304
  stopAutoImport();
300
305
  }
301
306
 
302
- export async function activate(api: any) {
303
- return register(api);
304
- }
305
-
306
- let isInitialized = false;
307
-
308
307
  function getCliPath() {
309
308
  const base = __dirname.endsWith('dist') ? __dirname : path.join(__dirname, 'dist');
310
309
  return path.join(base, 'src', 'cli.js');
311
310
  }
312
311
 
313
- export default function register(api: any) {
314
- if (isInitialized) return;
315
- isInitialized = true;
316
-
317
- const entries = api.getConfig?.()?.plugins?.entries || {};
318
- const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
319
- const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
320
-
321
- // 1. Tool
322
- api.registerTool({
323
- name: "overlay",
324
- description: "Access the BSV agent marketplace",
325
- parameters: {
326
- type: "object",
327
- properties: {
328
- action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
329
- service: { type: "string" },
330
- input: { type: "object" },
331
- identityKey: { type: "string" },
332
- sats: { type: "number" },
333
- requestId: { type: "string" },
334
- recipientKey: { type: "string" },
335
- serviceId: { type: "string" },
336
- result: { type: "object" }
312
+ /**
313
+ * OpenClaw Overlay Plugin
314
+ * Decentralized agent marketplace with BSV micropayments.
315
+ */
316
+ export const plugin = {
317
+ id: "openclaw-overlay-plugin",
318
+ name: "BSV Overlay Network",
319
+ description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
320
+
321
+ async activate(api: any) {
322
+ return this.register(api);
323
+ },
324
+
325
+ register(api: any) {
326
+ if (isInitialized) return;
327
+ isInitialized = true;
328
+
329
+ const entries = api.getConfig?.()?.plugins?.entries || {};
330
+ const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
331
+ const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
332
+
333
+ // 1. Tool
334
+ api.registerTool({
335
+ name: "overlay",
336
+ description: "Access the BSV agent marketplace",
337
+ parameters: {
338
+ type: "object",
339
+ properties: {
340
+ action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
341
+ service: { type: "string" },
342
+ input: { type: "object" },
343
+ identityKey: { type: "string" },
344
+ sats: { type: "number" },
345
+ requestId: { type: "string" },
346
+ recipientKey: { type: "string" },
347
+ serviceId: { type: "string" },
348
+ result: { type: "object" }
349
+ },
350
+ required: ["action"]
337
351
  },
338
- required: ["action"]
339
- },
340
- async execute(_id: string, params: any) {
341
- try {
342
- return await executeOverlayAction(params, pluginConfig, api);
343
- } catch (error: any) {
344
- return { content: [{ type: "text", text: `Error: ${error.message}` }] };
352
+ async execute(_id: string, params: any) {
353
+ try {
354
+ return await executeOverlayAction(params, pluginConfig, api);
355
+ } catch (error: any) {
356
+ return { content: [{ type: "text", text: `Error: ${error.message}` }] };
357
+ }
345
358
  }
346
- }
347
- });
348
-
349
- // 2. Command
350
- api.registerCommand({
351
- name: "overlay",
352
- description: "BSV Overlay Marketplace commands",
353
- acceptsArgs: true,
354
- handler: async (ctx: any) => {
355
- try {
356
- const action = ctx.args?.[0] || 'status';
359
+ });
357
360
 
358
- if (action === 'help') {
359
- 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- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
360
- }
361
+ // 2. Command
362
+ api.registerCommand({
363
+ name: "overlay",
364
+ description: "BSV Overlay Marketplace commands",
365
+ acceptsArgs: true,
366
+ requireAuth: true,
367
+ handler: async (ctx: any) => {
368
+ try {
369
+ const action = ctx.args?.[0] || 'status';
361
370
 
362
- const result = await executeOverlayAction({ action }, pluginConfig, api);
363
- return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
364
- } catch (error: any) {
365
- return { text: `❌ Error: ${error.message}` };
371
+ if (action === 'help') {
372
+ 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- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
373
+ }
374
+
375
+ const result = await executeOverlayAction({ action }, pluginConfig, api);
376
+ return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
377
+ } catch (error: any) {
378
+ return { text: `❌ Error: ${error.message}` };
379
+ }
366
380
  }
367
- }
368
- });
369
-
370
- // 3. Service
371
- api.registerService({
372
- id: "openclaw-overlay-relay",
373
- start: async () => {
374
- const env = buildEnvironment(pluginConfig);
375
- const cliPath = getCliPath();
376
- startBackgroundService(env, cliPath, api.logger);
377
- startAutoImport(env, cliPath, api.logger);
378
- },
379
- stop: () => stopBackgroundService()
380
- });
381
-
382
- // 4. CLI
383
- api.registerCli(({ program }: any) => {
384
- const overlay = program.command("overlay").description("BSV Overlay Network management");
385
- overlay.command("status").action(async () => {
386
- await ensureCp();
387
- const result = await handleStatus(buildEnvironment(pluginConfig), getCliPath());
388
- console.log(JSON.stringify(result, null, 2));
389
381
  });
390
- overlay.command("balance").action(async () => {
391
- await ensureCp();
392
- const result = await handleBalance(buildEnvironment(pluginConfig), getCliPath());
393
- console.log(JSON.stringify(result, null, 2));
382
+
383
+ // 3. Service
384
+ api.registerService({
385
+ id: "openclaw-overlay-relay",
386
+ start: async () => {
387
+ const env = buildEnvironment(pluginConfig);
388
+ const cliPath = getCliPath();
389
+ startBackgroundService(env, cliPath, api.logger);
390
+ startAutoImport(env, cliPath, api.logger);
391
+ },
392
+ stop: () => stopBackgroundService()
394
393
  });
395
- }, { commands: ["overlay"] });
396
- }
394
+
395
+ // 4. CLI
396
+ api.registerCli(({ program }: any) => {
397
+ const overlay = program.command("overlay").description("BSV Overlay Network management");
398
+ overlay.command("status").action(async () => {
399
+ await ensureCp();
400
+ const result = await handleStatus(buildEnvironment(pluginConfig), getCliPath());
401
+ console.log(JSON.stringify(result, null, 2));
402
+ });
403
+ overlay.command("balance").action(async () => {
404
+ await ensureCp();
405
+ const result = await handleBalance(buildEnvironment(pluginConfig), getCliPath());
406
+ console.log(JSON.stringify(result, null, 2));
407
+ });
408
+ }, { descriptors: [{ name: "overlay", description: "BSV Overlay Network management" }] });
409
+ }
410
+ };
411
+
412
+ export default plugin;
397
413
 
398
414
  async function executeOverlayAction(params: any, config: any, api: any) {
399
415
  await ensureCp();
@@ -472,7 +488,7 @@ async function handleFulfill(params: any, env: any, cliPath: string) {
472
488
  }
473
489
 
474
490
  function buildEnvironment(config: any) {
475
- const env = { ...(process as any)['en' + 'v'] };
491
+ const env = { ...(process as any)['env'] };
476
492
  env.BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
477
493
  env.OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
478
494
  env.BSV_NETWORK = env.BSV_NETWORK || 'mainnet';