clawmoney 0.15.38 → 0.15.40
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/commands/wallet.js +41 -11
- package/dist/promote/auto-verify.js +4 -2
- package/dist/utils/awal.d.ts +26 -0
- package/dist/utils/awal.js +64 -0
- package/package.json +1 -1
package/dist/commands/wallet.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import { awalExec } from '../utils/awal.js';
|
|
3
|
+
import { awalExec, awalExecSafe } from '../utils/awal.js';
|
|
4
4
|
import { apiGet } from '../utils/api.js';
|
|
5
|
-
import { loadConfig } from '../utils/config.js';
|
|
5
|
+
import { loadConfig, saveConfig } from '../utils/config.js';
|
|
6
6
|
// Base mainnet USDC contract + balanceOf(address) ABI selector.
|
|
7
7
|
// Keeping on-chain reads as a first-class path lets `wallet balance`
|
|
8
8
|
// skip the awal Electron bridge entirely, which is notorious for
|
|
@@ -43,7 +43,8 @@ async function readBaseUsdcBalance(walletAddress, timeoutMs = 8000) {
|
|
|
43
43
|
export async function walletStatusCommand() {
|
|
44
44
|
const spinner = ora('Getting wallet status...').start();
|
|
45
45
|
try {
|
|
46
|
-
|
|
46
|
+
// Read-only, safe to auto-retry after killing a wedged awal.
|
|
47
|
+
const result = await awalExecSafe(['status'], { timeoutMs: 8_000 });
|
|
47
48
|
spinner.succeed('Wallet Status');
|
|
48
49
|
console.log('');
|
|
49
50
|
const data = result.data;
|
|
@@ -88,21 +89,48 @@ export async function walletBalanceCommand() {
|
|
|
88
89
|
// Either half is allowed to fail — we print "(unavailable)" for the
|
|
89
90
|
// broken section and keep going.
|
|
90
91
|
const config = loadConfig();
|
|
92
|
+
// Wallet address lookup order:
|
|
93
|
+
// 1. ~/.clawmoney/config.yaml cache (instant)
|
|
94
|
+
// 2. Backend /api/v1/claw-agents/me (authoritative, ~200ms)
|
|
95
|
+
// 3. awal address (Electron cold-start, last resort, 5s cap)
|
|
96
|
+
// After a successful #2 we write the result back to the config so
|
|
97
|
+
// every future `wallet balance` hits path #1.
|
|
91
98
|
let walletAddress = config?.wallet_address ?? null;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
let addressSource = 'config';
|
|
100
|
+
let addressError = null;
|
|
101
|
+
if (!walletAddress && config?.api_key) {
|
|
102
|
+
try {
|
|
103
|
+
const resp = await apiGet('/api/v1/claw-agents/me', config.api_key);
|
|
104
|
+
if (resp.ok && typeof resp.data?.wallet_address === 'string' && resp.data.wallet_address) {
|
|
105
|
+
walletAddress = resp.data.wallet_address;
|
|
106
|
+
addressSource = 'api';
|
|
107
|
+
// Cache it so the next run is instant.
|
|
108
|
+
try {
|
|
109
|
+
saveConfig({ wallet_address: walletAddress });
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Non-fatal — we still have the address for THIS run.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
addressError = err.message;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
96
120
|
if (!walletAddress) {
|
|
121
|
+
// Last resort: cold-start awal. Uses awalExecSafe so a wedged
|
|
122
|
+
// Electron is automatically killed + retried before surfacing
|
|
123
|
+
// the error. Capped at 6s per attempt.
|
|
97
124
|
try {
|
|
98
|
-
const awalResult = await
|
|
125
|
+
const awalResult = await awalExecSafe(['address'], { timeoutMs: 6_000 });
|
|
99
126
|
const data = awalResult.data;
|
|
100
127
|
if (typeof data?.address === 'string' && data.address) {
|
|
101
128
|
walletAddress = data.address;
|
|
129
|
+
addressSource = 'awal';
|
|
102
130
|
}
|
|
103
131
|
}
|
|
104
132
|
catch (err) {
|
|
105
|
-
|
|
133
|
+
addressError = err.message;
|
|
106
134
|
}
|
|
107
135
|
}
|
|
108
136
|
const relayPromise = config?.api_key
|
|
@@ -121,8 +149,9 @@ export async function walletBalanceCommand() {
|
|
|
121
149
|
}
|
|
122
150
|
}
|
|
123
151
|
else {
|
|
124
|
-
onchainError =
|
|
152
|
+
onchainError = addressError ?? 'no wallet address in config';
|
|
125
153
|
}
|
|
154
|
+
void addressSource; // reserved for future debug display
|
|
126
155
|
const relayRows = await relayPromise;
|
|
127
156
|
spinner.stop();
|
|
128
157
|
console.log('');
|
|
@@ -160,7 +189,8 @@ export async function walletBalanceCommand() {
|
|
|
160
189
|
export async function walletAddressCommand() {
|
|
161
190
|
const spinner = ora('Getting wallet address...').start();
|
|
162
191
|
try {
|
|
163
|
-
|
|
192
|
+
// Read-only, safe to auto-retry on awal wedge.
|
|
193
|
+
const result = await awalExecSafe(['address'], { timeoutMs: 8_000 });
|
|
164
194
|
spinner.succeed('Wallet Address');
|
|
165
195
|
console.log('');
|
|
166
196
|
const data = result.data;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiGet, apiPost } from "../utils/api.js";
|
|
2
|
-
import { awalExec } from "../utils/awal.js";
|
|
2
|
+
import { awalExec, awalExecSafe } from "../utils/awal.js";
|
|
3
3
|
import { requireConfig } from "../utils/config.js";
|
|
4
4
|
const POLL_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
|
|
5
5
|
const MAX_PER_CYCLE = 3;
|
|
@@ -11,7 +11,9 @@ function log(msg) {
|
|
|
11
11
|
}
|
|
12
12
|
async function getUsdcBalance() {
|
|
13
13
|
try {
|
|
14
|
-
|
|
14
|
+
// Read-only balance check inside a long-running daemon loop —
|
|
15
|
+
// must auto-recover if awal wedges during the day.
|
|
16
|
+
const result = await awalExecSafe(["balance"], { timeoutMs: 10_000 });
|
|
15
17
|
const base = result.data.base;
|
|
16
18
|
const balances = base?.balances;
|
|
17
19
|
const usdc = balances?.USDC;
|
package/dist/utils/awal.d.ts
CHANGED
|
@@ -17,3 +17,29 @@ export declare function awalExecInteractive(args: string[]): Promise<string>;
|
|
|
17
17
|
* Check if awal is installed and available.
|
|
18
18
|
*/
|
|
19
19
|
export declare function isAwalAvailable(): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Kill a wedged awal server process using the pattern documented in
|
|
22
|
+
* clawmoney-skill's SKILL.md. Reads the pid from `awal status --json`
|
|
23
|
+
* and SIGKILLs it. Safe to call even when awal isn't running (the
|
|
24
|
+
* inner command fails, kill -9 gets nothing, we swallow both).
|
|
25
|
+
*
|
|
26
|
+
* awal's Electron "Payments MCP" wrapper occasionally wedges on
|
|
27
|
+
* macOS (GPU render hang, stdin/stdout pipe full, or upstream
|
|
28
|
+
* Coinbase MCP endpoint unreachable). A hard kill lets the next
|
|
29
|
+
* `awal status` cold-start a fresh process.
|
|
30
|
+
*/
|
|
31
|
+
export declare function killAwal(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Execute an awal command with timeout + one automatic retry on
|
|
34
|
+
* failure. On the first failure we kill any wedged awal process
|
|
35
|
+
* (using the SKILL-documented kill pattern) and re-run.
|
|
36
|
+
*
|
|
37
|
+
* Safe ONLY for READ operations (status, address, balance, etc).
|
|
38
|
+
* Do NOT use for writes (send, x402 pay, auth verify) — those
|
|
39
|
+
* either cost money twice on retry, or consume a single-use OTP
|
|
40
|
+
* and fail the second time. For writes, use awalExec directly
|
|
41
|
+
* and let the failure surface to the user.
|
|
42
|
+
*/
|
|
43
|
+
export declare function awalExecSafe(args: string[], opts?: {
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
}): Promise<AwalResult>;
|
package/dist/utils/awal.js
CHANGED
|
@@ -98,3 +98,67 @@ export async function isAwalAvailable() {
|
|
|
98
98
|
return false;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Kill a wedged awal server process using the pattern documented in
|
|
103
|
+
* clawmoney-skill's SKILL.md. Reads the pid from `awal status --json`
|
|
104
|
+
* and SIGKILLs it. Safe to call even when awal isn't running (the
|
|
105
|
+
* inner command fails, kill -9 gets nothing, we swallow both).
|
|
106
|
+
*
|
|
107
|
+
* awal's Electron "Payments MCP" wrapper occasionally wedges on
|
|
108
|
+
* macOS (GPU render hang, stdin/stdout pipe full, or upstream
|
|
109
|
+
* Coinbase MCP endpoint unreachable). A hard kill lets the next
|
|
110
|
+
* `awal status` cold-start a fresh process.
|
|
111
|
+
*/
|
|
112
|
+
export async function killAwal() {
|
|
113
|
+
await new Promise((resolve) => {
|
|
114
|
+
const child = spawn('sh', [
|
|
115
|
+
'-c',
|
|
116
|
+
'kill -9 $(npx awal status --json 2>/dev/null | grep -o \'"pid":[0-9]*\' | grep -o \'[0-9]*\') 2>/dev/null',
|
|
117
|
+
], { stdio: 'ignore', shell: false });
|
|
118
|
+
child.on('exit', () => resolve());
|
|
119
|
+
child.on('error', () => resolve());
|
|
120
|
+
});
|
|
121
|
+
// Give the OS a moment to reclaim the process + named pipes.
|
|
122
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Execute an awal command with timeout + one automatic retry on
|
|
126
|
+
* failure. On the first failure we kill any wedged awal process
|
|
127
|
+
* (using the SKILL-documented kill pattern) and re-run.
|
|
128
|
+
*
|
|
129
|
+
* Safe ONLY for READ operations (status, address, balance, etc).
|
|
130
|
+
* Do NOT use for writes (send, x402 pay, auth verify) — those
|
|
131
|
+
* either cost money twice on retry, or consume a single-use OTP
|
|
132
|
+
* and fail the second time. For writes, use awalExec directly
|
|
133
|
+
* and let the failure surface to the user.
|
|
134
|
+
*/
|
|
135
|
+
export async function awalExecSafe(args, opts = {}) {
|
|
136
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
137
|
+
const runWithTimeout = () => new Promise((resolve, reject) => {
|
|
138
|
+
const timer = setTimeout(() => {
|
|
139
|
+
reject(new Error(`awal ${args.join(' ')} timed out after ${timeoutMs}ms`));
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
awalExec(args).then((v) => {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
resolve(v);
|
|
144
|
+
}, (e) => {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
reject(e);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
try {
|
|
150
|
+
return await runWithTimeout();
|
|
151
|
+
}
|
|
152
|
+
catch (firstErr) {
|
|
153
|
+
// First attempt failed — kill any wedged server and retry once.
|
|
154
|
+
await killAwal();
|
|
155
|
+
try {
|
|
156
|
+
return await runWithTimeout();
|
|
157
|
+
}
|
|
158
|
+
catch (secondErr) {
|
|
159
|
+
// Preserve the first error too — it's usually the more
|
|
160
|
+
// informative one (the retry typically just times out again).
|
|
161
|
+
throw new Error(`awal ${args.join(' ')} failed after retry: ${secondErr.message} (initial: ${firstErr.message})`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|