openclaw-overlay-plugin 0.7.66 → 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.
- package/dist/index.d.ts +12 -2
- package/dist/index.js +97 -86
- package/dist/src/compatibility.test.js +2 -2
- package/dist/src/scripts/baemail/commands.d.ts +18 -47
- package/dist/src/scripts/baemail/commands.js +112 -101
- package/dist/src/scripts/baemail/handler.js +2 -2
- package/dist/src/scripts/services/respond.js +1 -1
- package/dist/src/scripts/utils/storage.js +1 -1
- package/dist/src/test/request-response-flow.test.js +8 -7
- package/index.ts +106 -91
- package/openclaw.plugin.json +14 -50
- package/package.json +1 -1
- package/src/compatibility.test.ts +2 -2
- package/src/scripts/baemail/commands.ts +118 -146
- package/src/scripts/baemail/handler.ts +2 -2
- package/src/scripts/services/respond.ts +1 -1
- package/src/scripts/utils/storage.ts +1 -1
- package/src/test/request-response-flow.test.ts +8 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Overlay Plugin
|
|
3
|
+
* Decentralized agent marketplace with BSV micropayments.
|
|
4
|
+
*/
|
|
5
|
+
export declare const plugin: {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
activate(api: any): Promise<void>;
|
|
10
|
+
register(api: any): void;
|
|
11
|
+
};
|
|
12
|
+
export default plugin;
|
package/dist/index.js
CHANGED
|
@@ -7,9 +7,11 @@ import path from 'node:path';
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import fs from 'node:fs';
|
|
10
|
+
import process from 'node:process';
|
|
10
11
|
import { serviceManager } from './src/services/index.js';
|
|
11
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
const __dirname = path.dirname(__filename);
|
|
14
|
+
let isInitialized = false;
|
|
13
15
|
async function ensureCp() {
|
|
14
16
|
if (execFileAsync)
|
|
15
17
|
return;
|
|
@@ -57,7 +59,7 @@ function loadDailySpending(walletDir) {
|
|
|
57
59
|
return { date: today, totalSats: 0, transactions: [] };
|
|
58
60
|
}
|
|
59
61
|
function writeActivityEvent(event) {
|
|
60
|
-
const alertDir = path.join(process['
|
|
62
|
+
const alertDir = path.join(process['env'].HOME || '', '.openclaw', 'openclaw-overlay');
|
|
61
63
|
try {
|
|
62
64
|
fs.mkdirSync(alertDir, { recursive: true });
|
|
63
65
|
fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
|
|
@@ -115,7 +117,7 @@ async function startAutoImport(env, cliPath, logger) {
|
|
|
115
117
|
logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
|
|
116
118
|
// Clear onboarding flag since wallet is now funded
|
|
117
119
|
try {
|
|
118
|
-
const onboardingSentFile = path.join(process['
|
|
120
|
+
const onboardingSentFile = path.join(process['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
|
|
119
121
|
if (fs.existsSync(onboardingSentFile)) {
|
|
120
122
|
fs.unlinkSync(onboardingSentFile);
|
|
121
123
|
}
|
|
@@ -125,7 +127,7 @@ async function startAutoImport(env, cliPath, logger) {
|
|
|
125
127
|
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.`, logger, { sessionKey: 'hook:openclaw-overlay:import' });
|
|
126
128
|
// Check if registered, auto-register if not
|
|
127
129
|
try {
|
|
128
|
-
const regPath = path.join(process['
|
|
130
|
+
const regPath = path.join(process['env'].HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
129
131
|
if (!fs.existsSync(regPath)) {
|
|
130
132
|
logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
|
|
131
133
|
const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
|
|
@@ -196,7 +198,7 @@ async function autoAdvertiseServices(env, cliPath, logger) {
|
|
|
196
198
|
}
|
|
197
199
|
function wakeAgent(text, logger, options = {}) {
|
|
198
200
|
const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
|
|
199
|
-
const gatewayPort = process['
|
|
201
|
+
const gatewayPort = process['env'].OPENCLAW_GATEWAY_PORT || '18789';
|
|
200
202
|
const httpToken = getHooksToken();
|
|
201
203
|
if (!httpToken)
|
|
202
204
|
return;
|
|
@@ -207,7 +209,7 @@ function wakeAgent(text, logger, options = {}) {
|
|
|
207
209
|
}).catch(() => { });
|
|
208
210
|
}
|
|
209
211
|
function getHooksToken() {
|
|
210
|
-
let token = process['
|
|
212
|
+
let token = process['env'].OPENCLAW_HOOKS_TOKEN || null;
|
|
211
213
|
if (!token) {
|
|
212
214
|
try {
|
|
213
215
|
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
@@ -262,7 +264,7 @@ function startBackgroundService(env, cliPath, logger) {
|
|
|
262
264
|
}
|
|
263
265
|
const notif = categorizeEvent(event);
|
|
264
266
|
if (notif) {
|
|
265
|
-
const dir = path.join(process['
|
|
267
|
+
const dir = path.join(process['env'].HOME || '', '.openclaw', 'openclaw-overlay');
|
|
266
268
|
fs.mkdirSync(dir, { recursive: true });
|
|
267
269
|
fs.appendFileSync(path.join(dir, 'activity-feed.jsonl'), JSON.stringify(notif) + '\n');
|
|
268
270
|
}
|
|
@@ -291,95 +293,104 @@ function stopBackgroundService() {
|
|
|
291
293
|
wokenRequests.clear();
|
|
292
294
|
stopAutoImport();
|
|
293
295
|
}
|
|
294
|
-
export async function activate(api) {
|
|
295
|
-
return register(api);
|
|
296
|
-
}
|
|
297
|
-
let isInitialized = false;
|
|
298
296
|
function getCliPath() {
|
|
299
297
|
const base = __dirname.endsWith('dist') ? __dirname : path.join(__dirname, 'dist');
|
|
300
298
|
return path.join(base, 'src', 'cli.js');
|
|
301
299
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
api
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
300
|
+
/**
|
|
301
|
+
* OpenClaw Overlay Plugin
|
|
302
|
+
* Decentralized agent marketplace with BSV micropayments.
|
|
303
|
+
*/
|
|
304
|
+
export const plugin = {
|
|
305
|
+
id: "openclaw-overlay-plugin",
|
|
306
|
+
name: "BSV Overlay Network",
|
|
307
|
+
description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
|
|
308
|
+
async activate(api) {
|
|
309
|
+
return this.register(api);
|
|
310
|
+
},
|
|
311
|
+
register(api) {
|
|
312
|
+
if (isInitialized)
|
|
313
|
+
return;
|
|
314
|
+
isInitialized = true;
|
|
315
|
+
const entries = api.getConfig?.()?.plugins?.entries || {};
|
|
316
|
+
const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
|
|
317
|
+
const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
|
|
318
|
+
// 1. Tool
|
|
319
|
+
api.registerTool({
|
|
320
|
+
name: "overlay",
|
|
321
|
+
description: "Access the BSV agent marketplace",
|
|
322
|
+
parameters: {
|
|
323
|
+
type: "object",
|
|
324
|
+
properties: {
|
|
325
|
+
action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
|
|
326
|
+
service: { type: "string" },
|
|
327
|
+
input: { type: "object" },
|
|
328
|
+
identityKey: { type: "string" },
|
|
329
|
+
sats: { type: "number" },
|
|
330
|
+
requestId: { type: "string" },
|
|
331
|
+
recipientKey: { type: "string" },
|
|
332
|
+
serviceId: { type: "string" },
|
|
333
|
+
result: { type: "object" }
|
|
334
|
+
},
|
|
335
|
+
required: ["action"]
|
|
325
336
|
},
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
catch (error) {
|
|
333
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
// 2. Command
|
|
338
|
-
api.registerCommand({
|
|
339
|
-
name: "overlay",
|
|
340
|
-
description: "BSV Overlay Marketplace commands",
|
|
341
|
-
acceptsArgs: true,
|
|
342
|
-
requireAuth: true,
|
|
343
|
-
handler: async (ctx) => {
|
|
344
|
-
try {
|
|
345
|
-
const action = ctx.args?.[0] || 'status';
|
|
346
|
-
if (action === 'help') {
|
|
347
|
-
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)` };
|
|
337
|
+
async execute(_id, params) {
|
|
338
|
+
try {
|
|
339
|
+
return await executeOverlayAction(params, pluginConfig, api);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
348
343
|
}
|
|
349
|
-
const result = await executeOverlayAction({ action }, pluginConfig, api);
|
|
350
|
-
return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
|
|
351
344
|
}
|
|
352
|
-
|
|
353
|
-
|
|
345
|
+
});
|
|
346
|
+
// 2. Command
|
|
347
|
+
api.registerCommand({
|
|
348
|
+
name: "overlay",
|
|
349
|
+
description: "BSV Overlay Marketplace commands",
|
|
350
|
+
acceptsArgs: true,
|
|
351
|
+
requireAuth: true,
|
|
352
|
+
handler: async (ctx) => {
|
|
353
|
+
try {
|
|
354
|
+
const action = ctx.args?.[0] || 'status';
|
|
355
|
+
if (action === 'help') {
|
|
356
|
+
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)` };
|
|
357
|
+
}
|
|
358
|
+
const result = await executeOverlayAction({ action }, pluginConfig, api);
|
|
359
|
+
return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
return { text: `❌ Error: ${error.message}` };
|
|
363
|
+
}
|
|
354
364
|
}
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
// 3. Service
|
|
358
|
-
api.registerService({
|
|
359
|
-
id: "openclaw-overlay-relay",
|
|
360
|
-
start: async () => {
|
|
361
|
-
const env = buildEnvironment(pluginConfig);
|
|
362
|
-
const cliPath = getCliPath();
|
|
363
|
-
startBackgroundService(env, cliPath, api.logger);
|
|
364
|
-
startAutoImport(env, cliPath, api.logger);
|
|
365
|
-
},
|
|
366
|
-
stop: () => stopBackgroundService()
|
|
367
|
-
});
|
|
368
|
-
// 4. CLI
|
|
369
|
-
api.registerCli(({ program }) => {
|
|
370
|
-
const overlay = program.command("overlay").description("BSV Overlay Network management");
|
|
371
|
-
overlay.command("status").action(async () => {
|
|
372
|
-
await ensureCp();
|
|
373
|
-
const result = await handleStatus(buildEnvironment(pluginConfig), getCliPath());
|
|
374
|
-
console.log(JSON.stringify(result, null, 2));
|
|
375
365
|
});
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
366
|
+
// 3. Service
|
|
367
|
+
api.registerService({
|
|
368
|
+
id: "openclaw-overlay-relay",
|
|
369
|
+
start: async () => {
|
|
370
|
+
const env = buildEnvironment(pluginConfig);
|
|
371
|
+
const cliPath = getCliPath();
|
|
372
|
+
startBackgroundService(env, cliPath, api.logger);
|
|
373
|
+
startAutoImport(env, cliPath, api.logger);
|
|
374
|
+
},
|
|
375
|
+
stop: () => stopBackgroundService()
|
|
380
376
|
});
|
|
381
|
-
|
|
382
|
-
}
|
|
377
|
+
// 4. CLI
|
|
378
|
+
api.registerCli(({ program }) => {
|
|
379
|
+
const overlay = program.command("overlay").description("BSV Overlay Network management");
|
|
380
|
+
overlay.command("status").action(async () => {
|
|
381
|
+
await ensureCp();
|
|
382
|
+
const result = await handleStatus(buildEnvironment(pluginConfig), getCliPath());
|
|
383
|
+
console.log(JSON.stringify(result, null, 2));
|
|
384
|
+
});
|
|
385
|
+
overlay.command("balance").action(async () => {
|
|
386
|
+
await ensureCp();
|
|
387
|
+
const result = await handleBalance(buildEnvironment(pluginConfig), getCliPath());
|
|
388
|
+
console.log(JSON.stringify(result, null, 2));
|
|
389
|
+
});
|
|
390
|
+
}, { descriptors: [{ name: "overlay", description: "BSV Overlay Network management" }] });
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
export default plugin;
|
|
383
394
|
async function executeOverlayAction(params, config, api) {
|
|
384
395
|
await ensureCp();
|
|
385
396
|
const { action } = params;
|
|
@@ -453,7 +464,7 @@ async function handleFulfill(params, env, cliPath) {
|
|
|
453
464
|
return parseCliOutput(res.stdout).data;
|
|
454
465
|
}
|
|
455
466
|
function buildEnvironment(config) {
|
|
456
|
-
const env = { ...process['
|
|
467
|
+
const env = { ...process['env'] };
|
|
457
468
|
env.BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
|
|
458
469
|
env.OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
|
|
459
470
|
env.BSV_NETWORK = env.BSV_NETWORK || 'mainnet';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Compatibility Test for Node 24 and OpenClaw SDK
|
|
3
3
|
*/
|
|
4
4
|
import sqlite3 from 'sqlite3';
|
|
5
|
-
import
|
|
5
|
+
import { plugin } from '../index.js';
|
|
6
6
|
async function testCompatibility() {
|
|
7
7
|
console.log('--- Overlay Compatibility Test ---');
|
|
8
8
|
// 1. Verify SQLite3 Native Bindings
|
|
@@ -32,7 +32,7 @@ async function testCompatibility() {
|
|
|
32
32
|
getConfig: () => ({ plugins: { entries: {} } })
|
|
33
33
|
};
|
|
34
34
|
try {
|
|
35
|
-
register(mockApi);
|
|
35
|
+
plugin.register(mockApi);
|
|
36
36
|
if (registeredCli && hasDescriptors) {
|
|
37
37
|
console.log('✓ CLI descriptors registered correctly for Phase 1 discovery.');
|
|
38
38
|
}
|
|
@@ -1,63 +1,34 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Baemail commands - paid message forwarding service.
|
|
3
|
-
*/
|
|
4
|
-
export interface BaemailConfig {
|
|
5
|
-
deliveryChannel: string;
|
|
6
|
-
tiers: {
|
|
7
|
-
standard: number;
|
|
8
|
-
priority: number;
|
|
9
|
-
urgent: number;
|
|
10
|
-
};
|
|
11
|
-
maxMessageLength: number;
|
|
12
|
-
blocklist: string[];
|
|
13
|
-
createdAt: string;
|
|
14
|
-
updatedAt: string;
|
|
15
|
-
}
|
|
16
1
|
export interface BaemailLogEntry {
|
|
17
2
|
requestId: string;
|
|
18
3
|
from: string;
|
|
19
|
-
|
|
20
|
-
tier: string;
|
|
4
|
+
to?: string;
|
|
21
5
|
paidSats: number;
|
|
22
|
-
messageLength: number;
|
|
23
|
-
deliveryChannel: string;
|
|
24
6
|
deliverySuccess: boolean;
|
|
25
|
-
|
|
26
|
-
paymentTxid: string;
|
|
27
|
-
refundStatus: string | null;
|
|
7
|
+
refundStatus?: 'pending' | 'completed';
|
|
28
8
|
refundTxid?: string;
|
|
29
|
-
refundedAt?: string;
|
|
30
|
-
timestamp: string;
|
|
31
9
|
_lineIdx?: number;
|
|
10
|
+
ts?: number;
|
|
11
|
+
senderName?: string;
|
|
12
|
+
messageLength?: number;
|
|
13
|
+
tier?: string;
|
|
14
|
+
deliveryChannel?: string;
|
|
15
|
+
deliveryError?: string | null;
|
|
16
|
+
paymentTxid?: string;
|
|
17
|
+
timestamp?: string;
|
|
32
18
|
}
|
|
33
19
|
/**
|
|
34
|
-
*
|
|
35
|
-
*/
|
|
36
|
-
export declare function loadBaemailConfig(): BaemailConfig | null;
|
|
37
|
-
/**
|
|
38
|
-
* Save baemail configuration.
|
|
39
|
-
*/
|
|
40
|
-
export declare function saveBaemailConfig(config: BaemailConfig): void;
|
|
41
|
-
/**
|
|
42
|
-
* Setup baemail service with delivery channel and tier pricing.
|
|
20
|
+
* Log a baemail delivery event.
|
|
43
21
|
*/
|
|
44
|
-
export declare function
|
|
22
|
+
export declare function logBaemailDelivery(entry: BaemailLogEntry): void;
|
|
23
|
+
export declare function loadBaemailConfig(): Promise<any>;
|
|
45
24
|
/**
|
|
46
|
-
*
|
|
47
|
-
*/
|
|
48
|
-
export declare function cmdBaemailConfig(): Promise<never>;
|
|
49
|
-
/**
|
|
50
|
-
* Block a sender from using baemail.
|
|
51
|
-
*/
|
|
52
|
-
export declare function cmdBaemailBlock(identityKey: string | undefined): Promise<never>;
|
|
53
|
-
/**
|
|
54
|
-
* Unblock a sender.
|
|
55
|
-
*/
|
|
56
|
-
export declare function cmdBaemailUnblock(identityKey: string | undefined): Promise<never>;
|
|
57
|
-
/**
|
|
58
|
-
* View baemail delivery log.
|
|
25
|
+
* List recent baemail deliveries.
|
|
59
26
|
*/
|
|
60
27
|
export declare function cmdBaemailLog(limitStr?: string): Promise<never>;
|
|
28
|
+
export declare function cmdBaemailSetup(priceSatsStr: string, prioritySats?: string, urgentSats?: string, channel?: string): Promise<never>;
|
|
29
|
+
export declare function cmdBaemailConfig(): Promise<never>;
|
|
30
|
+
export declare function cmdBaemailBlock(pubkey: string): Promise<never>;
|
|
31
|
+
export declare function cmdBaemailUnblock(pubkey: string): Promise<never>;
|
|
61
32
|
/**
|
|
62
33
|
* Refund a failed baemail delivery.
|
|
63
34
|
*/
|
|
@@ -1,126 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
5
|
+
import process from 'node:process';
|
|
6
6
|
import { ok, fail } from '../output.js';
|
|
7
7
|
import { loadIdentity } from '../wallet/identity.js';
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
*
|
|
18
|
+
* Log a baemail delivery event.
|
|
12
19
|
*/
|
|
13
|
-
export function
|
|
20
|
+
export function logBaemailDelivery(entry) {
|
|
14
21
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
56
|
-
|
|
35
|
+
maxMessageLength: 4000,
|
|
36
|
+
deliveryChannel: 'agent-hook',
|
|
37
|
+
tiers: {
|
|
38
|
+
standard: 100,
|
|
39
|
+
priority: 500,
|
|
40
|
+
urgent: 1000
|
|
41
|
+
}
|
|
57
42
|
};
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
*
|
|
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['
|
|
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 ?
|
|
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
|
}
|