@tokenbuddy/tokenbuddy 1.0.5 → 1.0.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/src/buyer-store.d.ts +48 -1
- package/dist/src/buyer-store.d.ts.map +1 -1
- package/dist/src/buyer-store.js +144 -17
- package/dist/src/buyer-store.js.map +1 -1
- package/dist/src/cli.d.ts +17 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +560 -63
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +11 -5
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +574 -161
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/doctor-clawtip-wallet.d.ts +14 -0
- package/dist/src/doctor-clawtip-wallet.d.ts.map +1 -0
- package/dist/src/doctor-clawtip-wallet.js +54 -0
- package/dist/src/doctor-clawtip-wallet.js.map +1 -0
- package/dist/src/doctor-diagnostics.d.ts +99 -0
- package/dist/src/doctor-diagnostics.d.ts.map +1 -0
- package/dist/src/doctor-diagnostics.js +552 -0
- package/dist/src/doctor-diagnostics.js.map +1 -0
- package/dist/src/init-clawtip-activation.d.ts +48 -0
- package/dist/src/init-clawtip-activation.d.ts.map +1 -0
- package/dist/src/init-clawtip-activation.js +395 -0
- package/dist/src/init-clawtip-activation.js.map +1 -0
- package/dist/src/init-payment-options.d.ts +56 -0
- package/dist/src/init-payment-options.d.ts.map +1 -0
- package/dist/src/init-payment-options.js +165 -0
- package/dist/src/init-payment-options.js.map +1 -0
- package/dist/src/provider-install.d.ts +37 -2
- package/dist/src/provider-install.d.ts.map +1 -1
- package/dist/src/provider-install.js +317 -67
- package/dist/src/provider-install.js.map +1 -1
- package/dist/src/seller-catalog.d.ts +79 -0
- package/dist/src/seller-catalog.d.ts.map +1 -0
- package/dist/src/seller-catalog.js +126 -0
- package/dist/src/seller-catalog.js.map +1 -0
- package/dist/src/tb-proxyd.js +13 -2
- package/dist/src/tb-proxyd.js.map +1 -1
- package/dist/src/terminal-image.d.ts +22 -0
- package/dist/src/terminal-image.d.ts.map +1 -0
- package/dist/src/terminal-image.js +135 -0
- package/dist/src/terminal-image.js.map +1 -0
- package/package.json +1 -1
- package/src/buyer-store.ts +253 -18
- package/src/cli.ts +709 -68
- package/src/daemon.ts +651 -167
- package/src/doctor-clawtip-wallet.ts +70 -0
- package/src/doctor-diagnostics.ts +861 -0
- package/src/init-clawtip-activation.ts +487 -0
- package/src/init-payment-options.ts +249 -0
- package/src/provider-install.ts +426 -76
- package/src/seller-catalog.ts +222 -0
- package/src/tb-proxyd.ts +14 -2
- package/src/terminal-image.ts +187 -0
- package/tests/e2e.test.ts +88 -5
- package/tests/tokenbuddy.test.ts +1362 -27
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import {
|
|
7
|
+
inspectOpenClawWalletConfig,
|
|
8
|
+
type OpenClawWalletConfigState,
|
|
9
|
+
} from "./init-payment-options.js";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
12
|
+
const SLEEP_SLICE_MS = 200;
|
|
13
|
+
const CLAWTIP_MIN_CLI_VERSION = "1.0.4";
|
|
14
|
+
const CLAWTIP_MIN_SKILL_VERSION = "1.0.12";
|
|
15
|
+
|
|
16
|
+
export interface ClawtipBootstrapPayment {
|
|
17
|
+
orderNo: string;
|
|
18
|
+
amountFen: number;
|
|
19
|
+
payTo?: string;
|
|
20
|
+
encryptedData?: string;
|
|
21
|
+
indicator: string;
|
|
22
|
+
slug?: string;
|
|
23
|
+
skillId?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
resourceUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ParsedClawtipOutput {
|
|
29
|
+
authUrl?: string;
|
|
30
|
+
clawtipId?: string;
|
|
31
|
+
mediaPath?: string;
|
|
32
|
+
failureMessage?: string;
|
|
33
|
+
requiresWalletAuth: boolean;
|
|
34
|
+
walletReady: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WaitForClawtipActivationOptions {
|
|
38
|
+
inspectWalletConfig?: () => OpenClawWalletConfigState;
|
|
39
|
+
isCancelled?: () => boolean;
|
|
40
|
+
clawtipId?: string;
|
|
41
|
+
checkRegister?: (clawtipId: string) => Promise<void>;
|
|
42
|
+
cancel?: (message?: string) => void;
|
|
43
|
+
pollIntervalMs?: number;
|
|
44
|
+
sleep?: (ms: number) => Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StartClawtipWalletBootstrapOptions {
|
|
48
|
+
home?: string;
|
|
49
|
+
runClawtipCommand?: (args: string[]) => Promise<string>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CheckOpenClawRuntimeOptions {
|
|
53
|
+
runOpenClawCommand?: (args: string[]) => Promise<string>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function defaultSleep(ms: number): Promise<void> {
|
|
57
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function defaultHomeDir(): string {
|
|
61
|
+
return process.env.HOME || os.homedir();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function clawtipOrderFilePath(home: string, indicator: string, orderNo: string): string {
|
|
65
|
+
return path.join(home, ".openclaw", "skills", "orders", indicator, `${orderNo}.json`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sanitizeClawtipOutput(output: string): string {
|
|
69
|
+
return output
|
|
70
|
+
.split("\n")
|
|
71
|
+
.map((line) => {
|
|
72
|
+
if (line.includes("\"u\"") || line.includes("payCredential") || line.includes("access_token")) {
|
|
73
|
+
return "<redacted sensitive ClawTip output>";
|
|
74
|
+
}
|
|
75
|
+
return line;
|
|
76
|
+
})
|
|
77
|
+
.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isClawtipPayWalletAuthOutput(args: string[], output: string): boolean {
|
|
81
|
+
if (!args.includes("pay")) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const lower = output.toLowerCase();
|
|
85
|
+
return lower.includes("authurl")
|
|
86
|
+
|| output.includes("MEDIA:")
|
|
87
|
+
|| lower.includes("clawtipid")
|
|
88
|
+
|| lower.includes("qrcode")
|
|
89
|
+
|| lower.includes("扫码");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function runClawtipCli(args: string[]): Promise<string> {
|
|
93
|
+
return await new Promise((resolve, reject) => {
|
|
94
|
+
const child = spawn("npx", args, {
|
|
95
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
96
|
+
});
|
|
97
|
+
let stdout = "";
|
|
98
|
+
let stderr = "";
|
|
99
|
+
child.stdout.on("data", (chunk) => {
|
|
100
|
+
stdout += String(chunk);
|
|
101
|
+
});
|
|
102
|
+
child.stderr.on("data", (chunk) => {
|
|
103
|
+
stderr += String(chunk);
|
|
104
|
+
});
|
|
105
|
+
child.on("error", (error) => {
|
|
106
|
+
reject(error);
|
|
107
|
+
});
|
|
108
|
+
child.on("close", (code) => {
|
|
109
|
+
const combined = `${stdout}${stderr}`;
|
|
110
|
+
if (code === 0) {
|
|
111
|
+
resolve(combined);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (isClawtipPayWalletAuthOutput(args, combined)) {
|
|
115
|
+
resolve(combined);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
reject(new Error(`ClawTip command failed with exit ${code}: ${sanitizeClawtipOutput(combined)}`));
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function runOpenClawCli(args: string[]): Promise<string> {
|
|
124
|
+
return await new Promise((resolve, reject) => {
|
|
125
|
+
const child = spawn("openclaw", args, {
|
|
126
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
127
|
+
});
|
|
128
|
+
let stdout = "";
|
|
129
|
+
let stderr = "";
|
|
130
|
+
child.stdout.on("data", (chunk) => {
|
|
131
|
+
stdout += String(chunk);
|
|
132
|
+
});
|
|
133
|
+
child.stderr.on("data", (chunk) => {
|
|
134
|
+
stderr += String(chunk);
|
|
135
|
+
});
|
|
136
|
+
child.on("error", (error) => {
|
|
137
|
+
reject(error);
|
|
138
|
+
});
|
|
139
|
+
child.on("close", (code) => {
|
|
140
|
+
const combined = `${stdout}${stderr}`;
|
|
141
|
+
if (code === 0) {
|
|
142
|
+
resolve(combined);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
reject(new Error(`OpenClaw command failed with exit ${code}: ${combined.trim()}`));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function findValueAfterKeys(output: string, keys: string[]): string | undefined {
|
|
151
|
+
for (const line of output.split("\n")) {
|
|
152
|
+
for (const key of keys) {
|
|
153
|
+
const index = line.indexOf(key);
|
|
154
|
+
if (index < 0) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const raw = line.slice(index + key.length);
|
|
158
|
+
const value = raw
|
|
159
|
+
.replace(/^[:=\s]+/, "")
|
|
160
|
+
.replace(/^["']+|["',\s]+$/g, "")
|
|
161
|
+
.trim();
|
|
162
|
+
if (value) {
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function lineContainsWalletAuthCue(line: string): boolean {
|
|
171
|
+
const lower = line.toLowerCase();
|
|
172
|
+
return lower.includes("authurl")
|
|
173
|
+
|| lower.includes("scan")
|
|
174
|
+
|| lower.includes("扫码")
|
|
175
|
+
|| lower.includes("授权")
|
|
176
|
+
|| lower.includes("qrcode")
|
|
177
|
+
|| lower.includes("clawtipid")
|
|
178
|
+
|| lower.includes("safeMonitor".toLowerCase())
|
|
179
|
+
|| lower.includes("unifiedauthm");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function findUrlInOutput(output: string): string | undefined {
|
|
183
|
+
for (const line of output.split("\n")) {
|
|
184
|
+
if (line.includes("process.env.CLAWTIP_PAY")
|
|
185
|
+
|| line.includes("process.env.GET_PUBLIC_KEY")
|
|
186
|
+
|| line.includes("process.env.QUERY_TOKEN")) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const matches = line.match(/https?:\/\/\S+/g) || [];
|
|
190
|
+
for (const match of matches) {
|
|
191
|
+
const value = match.trim().replace(/^["'`([{\s]+|["'`)\]},.\s]+$/g, "");
|
|
192
|
+
if (!(value.startsWith("https://") || value.startsWith("http://"))) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const lower = value.toLowerCase();
|
|
196
|
+
if (value.includes("clawtipId")
|
|
197
|
+
|| lower.includes("/qrcode")
|
|
198
|
+
|| lower.includes("safemonitor")
|
|
199
|
+
|| lower.includes("unifiedauthm")
|
|
200
|
+
|| lineContainsWalletAuthCue(line)) {
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function findMediaPathInOutput(output: string): string | undefined {
|
|
209
|
+
const keyedPath = findValueAfterKeys(output, ["MEDIA", "media", "mediaPath", "media_path"]);
|
|
210
|
+
if (keyedPath) {
|
|
211
|
+
return keyedPath;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const line of output.split("\n").reverse()) {
|
|
215
|
+
const value = line.trim().replace(/^["'`]+|["'`,\s]+$/g, "");
|
|
216
|
+
if (path.isAbsolute(value) && /\.(png|jpe?g|webp)$/i.test(value)) {
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function findClawtipFailureMessage(output: string): string | undefined {
|
|
224
|
+
for (const line of output.split("\n")) {
|
|
225
|
+
const trimmed = line.trim();
|
|
226
|
+
if (!trimmed) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (trimmed.includes("商家信息有误")
|
|
230
|
+
|| trimmed.includes("商户信息有误")
|
|
231
|
+
|| trimmed.includes("支付失败")
|
|
232
|
+
|| trimmed.includes("下单失败")) {
|
|
233
|
+
return sanitizeClawtipOutput(trimmed);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function decodeClawtipValue(value: string): string {
|
|
240
|
+
try {
|
|
241
|
+
return decodeURIComponent(value);
|
|
242
|
+
} catch {
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function clawtipQrWorkspaceDir(home: string): string {
|
|
248
|
+
return path.join(home, ".openclaw", "workspace", "clawtip", "qrcode");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function findLatestGeneratedQrMediaPath(home: string, createdAfterMs: number): string | undefined {
|
|
252
|
+
const qrDir = clawtipQrWorkspaceDir(home);
|
|
253
|
+
if (!fs.existsSync(qrDir)) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const candidates = fs.readdirSync(qrDir)
|
|
258
|
+
.filter((fileName) => /^qrcode-.*\.(png|jpe?g|webp)$/i.test(fileName))
|
|
259
|
+
.map((fileName) => {
|
|
260
|
+
const filePath = path.join(qrDir, fileName);
|
|
261
|
+
return {
|
|
262
|
+
filePath,
|
|
263
|
+
mtimeMs: fs.statSync(filePath).mtimeMs,
|
|
264
|
+
};
|
|
265
|
+
})
|
|
266
|
+
.filter((entry) => entry.mtimeMs >= createdAfterMs - 1_000)
|
|
267
|
+
.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
268
|
+
|
|
269
|
+
return candidates[0]?.filePath;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function extractClawtipIdFromUrl(value: string): string | undefined {
|
|
273
|
+
const decoded = decodeClawtipValue(value);
|
|
274
|
+
for (const segment of decoded.split(/[?&]/)) {
|
|
275
|
+
const [key, rawValue] = segment.split("=", 2);
|
|
276
|
+
if (!key || !rawValue) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (["clawtipId", "clawtip_id", "deviceId", "device_id"].includes(key)) {
|
|
280
|
+
const trimmed = rawValue.trim();
|
|
281
|
+
if (trimmed) {
|
|
282
|
+
return trimmed;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function parseClawtipCliOutput(output: string): ParsedClawtipOutput {
|
|
290
|
+
const authUrl = findValueAfterKeys(output, ["authUrl", "auth_url", "auth url"]) || findUrlInOutput(output);
|
|
291
|
+
const mediaPath = findMediaPathInOutput(output);
|
|
292
|
+
const clawtipId = authUrl
|
|
293
|
+
? extractClawtipIdFromUrl(authUrl)
|
|
294
|
+
: findValueAfterKeys(output, ["clawtipId", "clawtip_id", "deviceId", "device_id"]);
|
|
295
|
+
const lower = output.toLowerCase();
|
|
296
|
+
const failureMessage = findClawtipFailureMessage(output);
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
authUrl,
|
|
300
|
+
clawtipId: clawtipId ? decodeClawtipValue(clawtipId) : undefined,
|
|
301
|
+
mediaPath,
|
|
302
|
+
failureMessage,
|
|
303
|
+
requiresWalletAuth: Boolean(authUrl || clawtipId || mediaPath
|
|
304
|
+
|| lower.includes("authurl")
|
|
305
|
+
|| lower.includes("qrcode")
|
|
306
|
+
|| lower.includes("scan")
|
|
307
|
+
|| lower.includes("扫码")),
|
|
308
|
+
walletReady: lower.includes("register success")
|
|
309
|
+
|| lower.includes("注册成功")
|
|
310
|
+
|| lower.includes("tokeninfo")
|
|
311
|
+
|| lower.includes("config.json")
|
|
312
|
+
|| lower.includes("status: successful")
|
|
313
|
+
|| lower.includes("saved u"),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function resolveClawtipQrMediaPath(parsedOutput: ParsedClawtipOutput, orderFile: string): string {
|
|
318
|
+
if (parsedOutput.mediaPath) {
|
|
319
|
+
return parsedOutput.mediaPath;
|
|
320
|
+
}
|
|
321
|
+
throw new Error(
|
|
322
|
+
[
|
|
323
|
+
"ClawTip pay did not return a QR media file.",
|
|
324
|
+
`Order file: ${orderFile}`,
|
|
325
|
+
"For first-time wallet binding, run the ClawTip wallet QR flow before paying.",
|
|
326
|
+
].join(" ")
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function readClawtipPayCredential(orderFile: string): string | undefined {
|
|
331
|
+
if (!fs.existsSync(orderFile)) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
const order = JSON.parse(fs.readFileSync(orderFile, "utf8")) as Record<string, unknown>;
|
|
335
|
+
const credential = order.payCredential || order.pay_credential;
|
|
336
|
+
return typeof credential === "string" && credential.trim() ? credential : undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function writeClawtipOrderFile(
|
|
340
|
+
payment: ClawtipBootstrapPayment,
|
|
341
|
+
home: string = defaultHomeDir(),
|
|
342
|
+
): string {
|
|
343
|
+
const orderFile = clawtipOrderFilePath(home, payment.indicator, payment.orderNo);
|
|
344
|
+
fs.mkdirSync(path.dirname(orderFile), { recursive: true });
|
|
345
|
+
const orderJson = {
|
|
346
|
+
"skill-id": payment.skillId,
|
|
347
|
+
order_no: payment.orderNo,
|
|
348
|
+
amount: payment.amountFen,
|
|
349
|
+
question: "LLM inference purchase",
|
|
350
|
+
encrypted_data: payment.encryptedData,
|
|
351
|
+
pay_to: payment.payTo,
|
|
352
|
+
description: payment.description,
|
|
353
|
+
slug: payment.slug,
|
|
354
|
+
resource_url: payment.resourceUrl,
|
|
355
|
+
};
|
|
356
|
+
fs.writeFileSync(orderFile, JSON.stringify(orderJson, null, 2), "utf8");
|
|
357
|
+
return orderFile;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export async function startClawtipWalletBootstrap(
|
|
361
|
+
payment: ClawtipBootstrapPayment,
|
|
362
|
+
options: StartClawtipWalletBootstrapOptions = {},
|
|
363
|
+
): Promise<{ orderFile: string; parsedOutput: ParsedClawtipOutput; payCredential?: string }> {
|
|
364
|
+
const orderFile = writeClawtipOrderFile(payment, options.home);
|
|
365
|
+
const home = options.home || defaultHomeDir();
|
|
366
|
+
const payStartedAt = Date.now();
|
|
367
|
+
const runCommand = options.runClawtipCommand || runClawtipCli;
|
|
368
|
+
const output = await runCommand([
|
|
369
|
+
"--yes",
|
|
370
|
+
`@clawtip/clawtip-cli@${CLAWTIP_MIN_CLI_VERSION}`,
|
|
371
|
+
"pay",
|
|
372
|
+
"-o",
|
|
373
|
+
payment.orderNo,
|
|
374
|
+
"-i",
|
|
375
|
+
payment.indicator,
|
|
376
|
+
"-v",
|
|
377
|
+
CLAWTIP_MIN_SKILL_VERSION,
|
|
378
|
+
]);
|
|
379
|
+
const parsedOutput = parseClawtipCliOutput(output);
|
|
380
|
+
if (parsedOutput.failureMessage) {
|
|
381
|
+
throw new Error(`ClawTip pay failed: ${parsedOutput.failureMessage}`);
|
|
382
|
+
}
|
|
383
|
+
if (!parsedOutput.mediaPath) {
|
|
384
|
+
parsedOutput.mediaPath = findLatestGeneratedQrMediaPath(home, payStartedAt);
|
|
385
|
+
if (parsedOutput.mediaPath) {
|
|
386
|
+
parsedOutput.requiresWalletAuth = true;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
orderFile,
|
|
392
|
+
parsedOutput,
|
|
393
|
+
payCredential: readClawtipPayCredential(orderFile),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function checkOpenClawRuntime(
|
|
398
|
+
options: CheckOpenClawRuntimeOptions = {},
|
|
399
|
+
): Promise<string> {
|
|
400
|
+
const runOpenClawCommand = options.runOpenClawCommand || runOpenClawCli;
|
|
401
|
+
try {
|
|
402
|
+
return (await runOpenClawCommand(["--version"])).trim();
|
|
403
|
+
} catch (error) {
|
|
404
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
405
|
+
throw new Error(
|
|
406
|
+
`OpenClaw CLI is required before ClawTip wallet registration. Ensure \`openclaw --version\` works. ${message}`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function defaultCheckRegister(clawtipId: string): Promise<void> {
|
|
412
|
+
try {
|
|
413
|
+
await runClawtipCli([
|
|
414
|
+
"--yes",
|
|
415
|
+
`@clawtip/clawtip-cli@${CLAWTIP_MIN_CLI_VERSION}`,
|
|
416
|
+
"check-register",
|
|
417
|
+
"-d",
|
|
418
|
+
clawtipId,
|
|
419
|
+
]);
|
|
420
|
+
} catch {
|
|
421
|
+
// The Rust version treats failed check-register polls as normal pending state.
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function sleepUntilNextPoll(
|
|
426
|
+
ms: number,
|
|
427
|
+
sleep: (ms: number) => Promise<void>,
|
|
428
|
+
isCancelled: () => boolean,
|
|
429
|
+
): Promise<boolean> {
|
|
430
|
+
let remaining = ms;
|
|
431
|
+
while (remaining > 0) {
|
|
432
|
+
if (isCancelled()) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
const slice = Math.min(remaining, SLEEP_SLICE_MS);
|
|
436
|
+
await sleep(slice);
|
|
437
|
+
remaining -= slice;
|
|
438
|
+
}
|
|
439
|
+
return !isCancelled();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export async function waitForClawtipActivationConfirmation(
|
|
443
|
+
options: WaitForClawtipActivationOptions = {},
|
|
444
|
+
): Promise<boolean> {
|
|
445
|
+
let cancelled = false;
|
|
446
|
+
const signalHandler = () => {
|
|
447
|
+
cancelled = true;
|
|
448
|
+
};
|
|
449
|
+
process.once("SIGINT", signalHandler);
|
|
450
|
+
|
|
451
|
+
const inspectWalletConfig = options.inspectWalletConfig || inspectOpenClawWalletConfig;
|
|
452
|
+
const externalCancelled = options.isCancelled || (() => false);
|
|
453
|
+
const isCancelled = () => cancelled || externalCancelled();
|
|
454
|
+
const checkRegister = options.checkRegister || defaultCheckRegister;
|
|
455
|
+
const cancel = options.cancel || p.cancel;
|
|
456
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
457
|
+
const sleep = options.sleep || defaultSleep;
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
for (;;) {
|
|
461
|
+
if (isCancelled()) {
|
|
462
|
+
cancel("ClawTip activation cancelled.");
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const walletConfig = inspectWalletConfig();
|
|
467
|
+
if (walletConfig.exists) {
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (options.clawtipId) {
|
|
472
|
+
await checkRegister(options.clawtipId);
|
|
473
|
+
if (inspectWalletConfig().exists) {
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const shouldContinue = await sleepUntilNextPoll(pollIntervalMs, sleep, isCancelled);
|
|
479
|
+
if (!shouldContinue) {
|
|
480
|
+
cancel("ClawTip activation cancelled.");
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
} finally {
|
|
485
|
+
process.removeListener("SIGINT", signalHandler);
|
|
486
|
+
}
|
|
487
|
+
}
|