openclaw-overlay-plugin 0.7.66 → 0.7.68

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,172 +1,98 @@
1
- /**
2
- * Baemail commands - paid message forwarding service.
3
- */
4
-
1
+ import { fileURLToPath } from 'node:url';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
5
4
  import fs from 'node:fs';
6
- import { PATHS } from '../config.js';
5
+ import process from 'node:process';
6
+ import { Buffer } from 'node:buffer';
7
7
  import { ok, fail } from '../output.js';
8
8
  import { loadIdentity } from '../wallet/identity.js';
9
- import { ensureStateDir } from '../utils/storage.js';
10
- import { fetchWithTimeout } from '../utils/woc.js';
11
-
12
- // Types
13
- export interface BaemailConfig {
14
- deliveryChannel: string;
15
- tiers: {
16
- standard: number;
17
- priority: number;
18
- urgent: number;
19
- };
20
- maxMessageLength: number;
21
- blocklist: string[];
22
- createdAt: string;
23
- updatedAt: string;
24
- }
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ // Define paths relative to home directory
14
+ const PATHS = {
15
+ walletIdentity: path.join(os.homedir(), '.openclaw', 'bsv-wallet', 'wallet-identity.json'),
16
+ baemailLog: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-deliveries.jsonl'),
17
+ baemailConfig: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-config.json'),
18
+ baemailBlocklist: path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'baemail-blocklist.json'),
19
+ };
25
20
 
26
21
  export interface BaemailLogEntry {
27
22
  requestId: string;
28
23
  from: string;
29
- senderName: string;
30
- tier: string;
24
+ to?: string;
31
25
  paidSats: number;
32
- messageLength: number;
33
- deliveryChannel: string;
34
26
  deliverySuccess: boolean;
35
- deliveryError: string | null;
36
- paymentTxid: string;
37
- refundStatus: string | null;
27
+ refundStatus?: 'pending' | 'completed';
38
28
  refundTxid?: string;
39
- refundedAt?: string;
40
- timestamp: string;
41
29
  _lineIdx?: number;
30
+ ts?: number;
31
+ senderName?: string;
32
+ messageLength?: number;
33
+ tier?: string;
34
+ deliveryChannel?: string;
35
+ deliveryError?: string | null;
36
+ paymentTxid?: string;
37
+ timestamp?: string;
42
38
  }
43
39
 
44
40
  /**
45
- * Load baemail configuration.
41
+ * Log a baemail delivery event.
46
42
  */
47
- export function loadBaemailConfig(): BaemailConfig | null {
43
+ export function logBaemailDelivery(entry: BaemailLogEntry) {
48
44
  try {
49
- if (fs.existsSync(PATHS.baemailConfig)) {
50
- return JSON.parse(fs.readFileSync(PATHS.baemailConfig, 'utf-8'));
51
- }
52
- } catch (err) {
53
- console.warn(`[baemail] Warning: Could not read config: ${(err as Error).message}`);
54
- }
55
- return null;
45
+ const dir = path.dirname(PATHS.baemailLog);
46
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
47
+ fs.appendFileSync(PATHS.baemailLog, JSON.stringify({ ...entry, ts: Date.now() }) + '\n');
48
+ } catch {}
56
49
  }
57
50
 
58
- /**
59
- * Save baemail configuration.
60
- */
61
- export function saveBaemailConfig(config: BaemailConfig): void {
62
- ensureStateDir();
63
- fs.writeFileSync(PATHS.baemailConfig, JSON.stringify(config, null, 2));
64
- }
65
-
66
- /**
67
- * Setup baemail service with delivery channel and tier pricing.
68
- */
69
- export async function cmdBaemailSetup(
70
- channel: string | undefined,
71
- standardStr: string | undefined,
72
- priorityStr?: string,
73
- urgentStr?: string
74
- ): Promise<never> {
75
- if (!channel || !standardStr) {
76
- return fail('Usage: baemail-setup <channel> <standardSats> [prioritySats] [urgentSats]');
77
- }
78
-
79
- const standard = parseInt(standardStr, 10);
80
- const priority = priorityStr ? parseInt(priorityStr, 10) : standard * 2;
81
- const urgent = urgentStr ? parseInt(urgentStr, 10) : standard * 5;
82
-
83
- if (isNaN(standard) || standard < 1) {
84
- return fail('Standard rate must be a positive integer (sats)');
85
- }
86
- if (priority < standard) {
87
- return fail('Priority rate must be >= standard rate');
88
- }
89
- if (urgent < priority) {
90
- return fail('Urgent rate must be >= priority rate');
91
- }
92
-
93
- const config: BaemailConfig = {
94
- deliveryChannel: channel,
95
- tiers: { standard, priority, urgent },
51
+ export async function loadBaemailConfig() {
52
+ const defaults = {
53
+ enabled: true,
54
+ priceSats: 100,
55
+ autoRefund: true,
56
+ blocklist: [] as string[],
96
57
  maxMessageLength: 4000,
97
- blocklist: [],
98
- createdAt: new Date().toISOString(),
99
- updatedAt: new Date().toISOString(),
58
+ deliveryChannel: 'agent-hook',
59
+ tiers: {
60
+ standard: 100,
61
+ priority: 500,
62
+ urgent: 1000
63
+ }
100
64
  };
101
65
 
102
- saveBaemailConfig(config);
103
-
104
- return ok({
105
- configured: true,
106
- deliveryChannel: channel,
107
- tiers: config.tiers,
108
- note: `Advertise with: cli advertise baemail "Baemail" "Paid message forwarding. Pay ${standard}+ sats to reach me." ${standard}`,
109
- });
110
- }
111
-
112
- /**
113
- * View current baemail configuration.
114
- */
115
- export async function cmdBaemailConfig(): Promise<never> {
116
- const config = loadBaemailConfig();
117
- if (!config) {
118
- return fail('Baemail not configured. Run: baemail-setup <channel> <standardSats> [prioritySats] [urgentSats]');
66
+ if (!fs.existsSync(PATHS.baemailConfig)) {
67
+ return defaults;
119
68
  }
120
- return ok(config);
121
- }
122
-
123
- /**
124
- * Block a sender from using baemail.
125
- */
126
- export async function cmdBaemailBlock(identityKey: string | undefined): Promise<never> {
127
- if (!identityKey) return fail('Usage: baemail-block <identityKey>');
128
-
129
- const config = loadBaemailConfig();
130
- if (!config) {
131
- return fail('Baemail not configured. Run baemail-setup first.');
132
- }
133
-
134
- if (!config.blocklist) config.blocklist = [];
135
- if (config.blocklist.includes(identityKey)) {
136
- return fail('Identity already blocked');
69
+ try {
70
+ const config = JSON.parse(fs.readFileSync(PATHS.baemailConfig, 'utf-8'));
71
+ return { ...defaults, ...config };
72
+ } catch {
73
+ return defaults;
137
74
  }
138
-
139
- config.blocklist.push(identityKey);
140
- config.updatedAt = new Date().toISOString();
141
- saveBaemailConfig(config);
142
-
143
- return ok({ blocked: identityKey, totalBlocked: config.blocklist.length });
144
75
  }
145
76
 
146
- /**
147
- * Unblock a sender.
148
- */
149
- export async function cmdBaemailUnblock(identityKey: string | undefined): Promise<never> {
150
- if (!identityKey) return fail('Usage: baemail-unblock <identityKey>');
151
-
152
- const config = loadBaemailConfig();
153
- if (!config) {
154
- return fail('Baemail not configured. Run baemail-setup first.');
155
- }
156
-
157
- if (!config.blocklist || !config.blocklist.includes(identityKey)) {
158
- return fail('Identity not in blocklist');
77
+ async function fetchWithTimeout(url: string, options: any = {}) {
78
+ const { timeout = 15000 } = options;
79
+ const controller = new AbortController();
80
+ const id = setTimeout(() => controller.abort(), timeout);
81
+ try {
82
+ const response = await fetch(url, {
83
+ ...options,
84
+ signal: controller.signal
85
+ });
86
+ clearTimeout(id);
87
+ return response;
88
+ } catch (err) {
89
+ clearTimeout(id);
90
+ throw err;
159
91
  }
160
-
161
- config.blocklist = config.blocklist.filter(k => k !== identityKey);
162
- config.updatedAt = new Date().toISOString();
163
- saveBaemailConfig(config);
164
-
165
- return ok({ unblocked: identityKey, totalBlocked: config.blocklist.length });
166
92
  }
167
93
 
168
94
  /**
169
- * View baemail delivery log.
95
+ * List recent baemail deliveries.
170
96
  */
171
97
  export async function cmdBaemailLog(limitStr?: string): Promise<never> {
172
98
  const limit = parseInt(limitStr || '20', 10) || 20;
@@ -175,8 +101,8 @@ export async function cmdBaemailLog(limitStr?: string): Promise<never> {
175
101
  return ok({ log: [], count: 0 });
176
102
  }
177
103
 
178
- const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter(l => l.trim());
179
- const entries: BaemailLogEntry[] = lines.map(l => {
104
+ const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter((l: string) => l.trim());
105
+ const entries: BaemailLogEntry[] = lines.map((l: string) => {
180
106
  try { return JSON.parse(l); } catch { return null; }
181
107
  }).filter(Boolean) as BaemailLogEntry[];
182
108
 
@@ -184,6 +110,52 @@ export async function cmdBaemailLog(limitStr?: string): Promise<never> {
184
110
  return ok({ log: recent, count: entries.length, showing: recent.length });
185
111
  }
186
112
 
113
+ export async function cmdBaemailSetup(priceSatsStr: string, prioritySats?: string, urgentSats?: string, channel?: string): Promise<never> {
114
+ const standard = parseInt(priceSatsStr, 10) || 100;
115
+ const priority = parseInt(prioritySats || '', 10) || (standard * 5);
116
+ const urgent = parseInt(urgentSats || '', 10) || (standard * 10);
117
+
118
+ const config = {
119
+ enabled: true,
120
+ priceSats: standard,
121
+ autoRefund: true,
122
+ deliveryChannel: channel || 'agent-hook',
123
+ tiers: { standard, priority, urgent }
124
+ };
125
+
126
+ const dir = path.dirname(PATHS.baemailConfig);
127
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
128
+ fs.writeFileSync(PATHS.baemailConfig, JSON.stringify(config, null, 2));
129
+ return ok({ message: `Baemail setup complete.`, config });
130
+ }
131
+
132
+ export async function cmdBaemailConfig(): Promise<never> {
133
+ const config = await loadBaemailConfig();
134
+ return ok(config);
135
+ }
136
+
137
+ export async function cmdBaemailBlock(pubkey: string): Promise<never> {
138
+ if (!pubkey) return fail('Usage: baemail-block <pubkey>');
139
+ let blocklist: string[] = [];
140
+ if (fs.existsSync(PATHS.baemailBlocklist)) {
141
+ try { blocklist = JSON.parse(fs.readFileSync(PATHS.baemailBlocklist, 'utf-8')); } catch { blocklist = []; }
142
+ }
143
+ if (!blocklist.includes(pubkey)) blocklist.push(pubkey);
144
+ fs.writeFileSync(PATHS.baemailBlocklist, JSON.stringify(blocklist, null, 2));
145
+ return ok({ blocked: true, pubkey, count: blocklist.length });
146
+ }
147
+
148
+ export async function cmdBaemailUnblock(pubkey: string): Promise<never> {
149
+ if (!pubkey) return fail('Usage: baemail-unblock <pubkey>');
150
+ let blocklist: string[] = [];
151
+ if (fs.existsSync(PATHS.baemailBlocklist)) {
152
+ try { blocklist = JSON.parse(fs.readFileSync(PATHS.baemailBlocklist, 'utf-8')); } catch { blocklist = []; }
153
+ }
154
+ blocklist = blocklist.filter(p => p !== pubkey);
155
+ fs.writeFileSync(PATHS.baemailBlocklist, JSON.stringify(blocklist, null, 2));
156
+ return ok({ unblocked: true, pubkey, count: blocklist.length });
157
+ }
158
+
187
159
  /**
188
160
  * Refund a failed baemail delivery.
189
161
  */
@@ -195,8 +167,8 @@ export async function cmdBaemailRefund(requestId: string | undefined): Promise<n
195
167
  }
196
168
 
197
169
  // Find the entry
198
- const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter(l => l.trim());
199
- const entries: BaemailLogEntry[] = lines.map((l, idx) => {
170
+ const lines = fs.readFileSync(PATHS.baemailLog, 'utf-8').split('\n').filter((l: string) => l.trim());
171
+ const entries: BaemailLogEntry[] = lines.map((l: string, idx: number) => {
200
172
  try { return { ...JSON.parse(l), _lineIdx: idx }; } catch { return null; }
201
173
  }).filter(Boolean) as BaemailLogEntry[];
202
174
 
@@ -289,7 +261,7 @@ export async function cmdBaemailRefund(requestId: string | undefined): Promise<n
289
261
  await tx.sign();
290
262
 
291
263
  // Broadcast using configured ARC/Arcade URL or fallback to WhatsOnChain
292
- const arcUrl = (process as any)['en' + 'v'].BSV_ARC_URL;
264
+ const arcUrl = (process as any)['env'].BSV_ARC_URL;
293
265
  let broadcastResp;
294
266
 
295
267
  if (arcUrl) {
@@ -314,7 +286,7 @@ export async function cmdBaemailRefund(requestId: string | undefined): Promise<n
314
286
  const txid = tx.id('hex');
315
287
 
316
288
  // Update log entry
317
- const updatedLines = lines.map((l, idx) => {
289
+ const updatedLines = lines.map((l: string, idx: number) => {
318
290
  if (idx === entry._lineIdx) {
319
291
  const updated = { ...JSON.parse(l), refundStatus: 'completed', refundTxid: txid, refundedAt: new Date().toISOString() };
320
292
  return JSON.stringify(updated);
@@ -68,7 +68,7 @@ export async function processBaemail(
68
68
  const payment = msg.payload?.payment;
69
69
 
70
70
  // Load config
71
- const config = loadBaemailConfig();
71
+ const config = await loadBaemailConfig();
72
72
  if (!config) {
73
73
  const rejectPayload = {
74
74
  requestId: msg.id,
@@ -289,7 +289,7 @@ _Reply via overlay: \`cli send ${replyKey} ping "your reply"\`_`;
289
289
  deliverySuccess,
290
290
  deliveryError: deliveryError ?? null,
291
291
  paymentTxid: payResult.txid || '',
292
- refundStatus: deliverySuccess ? null : 'pending',
292
+ refundStatus: deliverySuccess ? undefined : 'pending',
293
293
  timestamp: new Date().toISOString(),
294
294
  };
295
295
  fs.appendFileSync(PATHS.baemailLog, JSON.stringify(logEntry) + '\n');
@@ -139,7 +139,7 @@ export async function cmdResearchRespond(resultJsonPath: string | undefined): Pr
139
139
  // Remove from queue
140
140
  if (fs.existsSync(PATHS.researchQueue)) {
141
141
  const lines = fs.readFileSync(PATHS.researchQueue, 'utf-8').trim().split('\n').filter(Boolean);
142
- const remaining = lines.filter(l => {
142
+ const remaining = lines.filter((l: string) => {
143
143
  try { return JSON.parse(l).requestId !== requestId; } catch { return true; }
144
144
  });
145
145
  fs.writeFileSync(PATHS.researchQueue, remaining.length ? remaining.join('\n') + '\n' : '');
@@ -104,7 +104,7 @@ export function appendToJsonl(filePath: string, entry: Record<string, unknown>):
104
104
  export function readJsonl<T>(filePath: string): T[] {
105
105
  if (!fs.existsSync(filePath)) return [];
106
106
  const lines = fs.readFileSync(filePath, 'utf-8').trim().split('\n').filter(Boolean);
107
- return lines.map(line => {
107
+ return lines.map((line: string) => {
108
108
  try {
109
109
  return JSON.parse(line);
110
110
  } catch {
@@ -4,6 +4,7 @@
4
4
 
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
+ import process from 'node:process';
7
8
 
8
9
  // Simple test runner (matching existing pattern)
9
10
  let passed = 0;
@@ -127,13 +128,13 @@ async function run() {
127
128
 
128
129
  // Check remaining entries
129
130
  const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
130
- const remaining = lines.map(line => JSON.parse(line));
131
+ const remaining = lines.map((line: string) => JSON.parse(line));
131
132
 
132
133
  assert(remaining.length === 2, `Expected 2 remaining entries, got ${remaining.length}`);
133
- assert(remaining.find(e => e.requestId === 'pending-1') !== undefined, 'Should keep recent pending');
134
- assert(remaining.find(e => e.requestId === 'recent-fulfilled') !== undefined, 'Should keep recent fulfilled');
135
- assert(remaining.find(e => e.requestId === 'old-fulfilled') === undefined, 'Should remove old fulfilled');
136
- assert(remaining.find(e => e.requestId === 'old-rejected') === undefined, 'Should remove old rejected');
134
+ assert(remaining.find((e: any) => e.requestId === 'pending-1') !== undefined, 'Should keep recent pending');
135
+ assert(remaining.find((e: any) => e.requestId === 'recent-fulfilled') !== undefined, 'Should keep recent fulfilled');
136
+ assert(remaining.find((e: any) => e.requestId === 'old-fulfilled') === undefined, 'Should remove old fulfilled');
137
+ assert(remaining.find((e: any) => e.requestId === 'old-rejected') === undefined, 'Should remove old rejected');
137
138
 
138
139
  cleanupTestEnv();
139
140
  });
@@ -159,7 +160,7 @@ async function run() {
159
160
  const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
160
161
  let updated = false;
161
162
 
162
- const updatedLines = lines.map(line => {
163
+ const updatedLines = lines.map((line: string) => {
163
164
  try {
164
165
  const entryData = JSON.parse(line);
165
166
  if (entryData.requestId === requestId) {
@@ -215,7 +216,7 @@ async function run() {
215
216
  const lines = fs.readFileSync(TEST_PATHS.serviceQueue, 'utf-8').trim().split('\n').filter(Boolean);
216
217
  let updated = false;
217
218
 
218
- lines.map(line => {
219
+ lines.map((line: string) => {
219
220
  try {
220
221
  const entry = JSON.parse(line);
221
222
  if (entry.requestId === requestId) {