@bananalink-test/agent-wallet 0.1.0
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.mjs +478 -0
- package/package.json +36 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { createConnection, createServer } from "node:net";
|
|
6
|
+
import { BananalinkClient, ConnectionRejectedError, ConnectionTimeoutError, RequestRejectedError, RequestTimeoutError, SessionClosedError } from "@bananalink-test/client";
|
|
7
|
+
import { parseUnits } from "viem";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
//#region src/chain/resolveChain.ts
|
|
13
|
+
const CHAINS = {
|
|
14
|
+
ethereum: 1,
|
|
15
|
+
eth: 1,
|
|
16
|
+
polygon: 137,
|
|
17
|
+
matic: 137,
|
|
18
|
+
base: 8453,
|
|
19
|
+
optimism: 10,
|
|
20
|
+
op: 10,
|
|
21
|
+
arbitrum: 42161,
|
|
22
|
+
arb: 42161,
|
|
23
|
+
sepolia: 11155111
|
|
24
|
+
};
|
|
25
|
+
function resolveChain(input) {
|
|
26
|
+
const n = Number(input);
|
|
27
|
+
if (!Number.isNaN(n) && n > 0) return n;
|
|
28
|
+
const id = CHAINS[input.toLowerCase()];
|
|
29
|
+
if (!id) throw new Error(`Unknown chain "${input}". Use a name (ethereum, base, polygon…) or a numeric chain ID.`);
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/config/CONFIG_DIR.ts
|
|
35
|
+
const CONFIG_DIR = join(homedir(), ".config", "bananalink-agent");
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/config/PID_FILE.ts
|
|
39
|
+
const PID_FILE = join(CONFIG_DIR, "daemon.pid");
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/config/SOCKET_PATH.ts
|
|
43
|
+
const SOCKET_PATH = join(CONFIG_DIR, "daemon.sock");
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/errors/toErrorPayload.ts
|
|
47
|
+
function toErrorPayload(e) {
|
|
48
|
+
if (e instanceof ConnectionRejectedError) return {
|
|
49
|
+
error: "ConnectionRejected",
|
|
50
|
+
message: "Connection was declined."
|
|
51
|
+
};
|
|
52
|
+
if (e instanceof ConnectionTimeoutError) return {
|
|
53
|
+
error: "ConnectionTimeout",
|
|
54
|
+
message: "Timed out waiting for wallet approval."
|
|
55
|
+
};
|
|
56
|
+
if (e instanceof RequestRejectedError) return {
|
|
57
|
+
error: "RequestRejected",
|
|
58
|
+
message: "Transaction was declined."
|
|
59
|
+
};
|
|
60
|
+
if (e instanceof RequestTimeoutError) return {
|
|
61
|
+
error: "RequestTimeout",
|
|
62
|
+
message: "Timed out waiting for transaction approval."
|
|
63
|
+
};
|
|
64
|
+
if (e instanceof SessionClosedError) return {
|
|
65
|
+
error: "SessionClosed",
|
|
66
|
+
message: "Session expired. Run connect again."
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
error: "Error",
|
|
70
|
+
message: e instanceof Error ? e.message : String(e)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/output/emit.ts
|
|
76
|
+
function emit(data) {
|
|
77
|
+
console.log(JSON.stringify(data));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/output/info.ts
|
|
82
|
+
function info(msg) {
|
|
83
|
+
console.error(msg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/schemas/DisconnectCmdSchema.ts
|
|
88
|
+
const DisconnectCmdSchema = z.object({ cmd: z.literal("disconnect") });
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/schemas/SendCmdSchema.ts
|
|
92
|
+
const SendCmdSchema = z.object({
|
|
93
|
+
cmd: z.literal("send"),
|
|
94
|
+
to: z.string(),
|
|
95
|
+
chainId: z.string().transform(resolveChain),
|
|
96
|
+
value: z.string().optional(),
|
|
97
|
+
data: z.string().optional(),
|
|
98
|
+
notificationTitle: z.string().optional(),
|
|
99
|
+
notificationBody: z.string().optional()
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/schemas/StatusCmdSchema.ts
|
|
104
|
+
const StatusCmdSchema = z.object({ cmd: z.literal("status") });
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/schemas/DaemonCmdSchema.ts
|
|
108
|
+
const DaemonCmdSchema = z.discriminatedUnion("cmd", [
|
|
109
|
+
StatusCmdSchema,
|
|
110
|
+
SendCmdSchema,
|
|
111
|
+
DisconnectCmdSchema
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/config/SESSION_FILE.ts
|
|
116
|
+
const SESSION_FILE = join(CONFIG_DIR, "session.json");
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/schemas/StoredSessionSchema.ts
|
|
120
|
+
const StoredSessionSchema = z.object({
|
|
121
|
+
dappId: z.string(),
|
|
122
|
+
accessToken: z.string().optional()
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/session/loadSession.ts
|
|
127
|
+
function loadSession() {
|
|
128
|
+
try {
|
|
129
|
+
if (existsSync(SESSION_FILE)) {
|
|
130
|
+
const data = readFileSync(SESSION_FILE, "utf8");
|
|
131
|
+
const result = StoredSessionSchema.safeParse(JSON.parse(data));
|
|
132
|
+
if (result.success) return result.data;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/session/saveSession.ts
|
|
142
|
+
function saveSession(s) {
|
|
143
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
144
|
+
writeFileSync(SESSION_FILE, JSON.stringify(s, null, 2), { mode: 384 });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/daemon/runDaemon.ts
|
|
149
|
+
function makeDapp(dappId) {
|
|
150
|
+
return {
|
|
151
|
+
dappId,
|
|
152
|
+
dappName: "AI Agent",
|
|
153
|
+
dappStatement: "An AI agent wants to send transactions on your behalf.",
|
|
154
|
+
domain: "ai.agent",
|
|
155
|
+
uri: "ai://agent",
|
|
156
|
+
icons: [],
|
|
157
|
+
chainId: 1
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function serveSession(session) {
|
|
161
|
+
const server = createServer((socket) => {
|
|
162
|
+
let buf = "";
|
|
163
|
+
socket.on("data", async (chunk) => {
|
|
164
|
+
buf += chunk.toString();
|
|
165
|
+
const newline = buf.indexOf("\n");
|
|
166
|
+
if (newline === -1) return;
|
|
167
|
+
const line = buf.slice(0, newline);
|
|
168
|
+
buf = buf.slice(newline + 1);
|
|
169
|
+
let response;
|
|
170
|
+
try {
|
|
171
|
+
const parseResult = DaemonCmdSchema.safeParse(JSON.parse(line));
|
|
172
|
+
if (!parseResult.success) {
|
|
173
|
+
response = {
|
|
174
|
+
ok: false,
|
|
175
|
+
error: "InvalidCommand",
|
|
176
|
+
message: parseResult.error.message
|
|
177
|
+
};
|
|
178
|
+
socket.write(`${JSON.stringify(response)}\n`);
|
|
179
|
+
socket.end();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const cmd = parseResult.data;
|
|
183
|
+
switch (cmd.cmd) {
|
|
184
|
+
case "status":
|
|
185
|
+
response = {
|
|
186
|
+
ok: true,
|
|
187
|
+
connected: true,
|
|
188
|
+
address: session.sessionClaims.address
|
|
189
|
+
};
|
|
190
|
+
break;
|
|
191
|
+
case "send": {
|
|
192
|
+
const valueHex = cmd.value ? `0x${parseUnits(cmd.value, 18).toString(16)}` : void 0;
|
|
193
|
+
const notification = cmd.notificationTitle && cmd.notificationBody ? {
|
|
194
|
+
title: cmd.notificationTitle,
|
|
195
|
+
body: cmd.notificationBody
|
|
196
|
+
} : void 0;
|
|
197
|
+
response = {
|
|
198
|
+
ok: true,
|
|
199
|
+
txHash: await session.request({
|
|
200
|
+
method: "eth_sendTransaction",
|
|
201
|
+
params: [{
|
|
202
|
+
to: cmd.to,
|
|
203
|
+
value: valueHex,
|
|
204
|
+
data: cmd.data ?? "0x",
|
|
205
|
+
chainId: cmd.chainId,
|
|
206
|
+
metadata: notification ? { notification } : void 0
|
|
207
|
+
}]
|
|
208
|
+
}),
|
|
209
|
+
to: cmd.to
|
|
210
|
+
};
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case "disconnect": {
|
|
214
|
+
const stored = loadSession();
|
|
215
|
+
if (stored) saveSession({ dappId: stored.dappId });
|
|
216
|
+
response = { ok: true };
|
|
217
|
+
socket.write(`${JSON.stringify(response)}\n`);
|
|
218
|
+
socket.end();
|
|
219
|
+
server.close();
|
|
220
|
+
if (existsSync(SOCKET_PATH)) unlinkSync(SOCKET_PATH);
|
|
221
|
+
if (existsSync(PID_FILE)) unlinkSync(PID_FILE);
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
response = {
|
|
227
|
+
ok: false,
|
|
228
|
+
...toErrorPayload(e)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
socket.write(`${JSON.stringify(response)}\n`);
|
|
232
|
+
socket.end();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
server.listen(SOCKET_PATH, () => {
|
|
236
|
+
chmodSync(SOCKET_PATH, 384);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async function runDaemon() {
|
|
240
|
+
process.stdout.on("error", (err) => {
|
|
241
|
+
if (err.code !== "EPIPE") throw err;
|
|
242
|
+
});
|
|
243
|
+
const stored = loadSession();
|
|
244
|
+
const dappId = stored?.dappId ?? crypto.randomUUID();
|
|
245
|
+
const connection = await new BananalinkClient({ dapp: makeDapp(dappId) }).connect(stored?.accessToken ? { accessToken: stored.accessToken } : void 0);
|
|
246
|
+
if (!stored?.accessToken) {
|
|
247
|
+
emit({
|
|
248
|
+
type: "pending",
|
|
249
|
+
url: connection.connectionUrl
|
|
250
|
+
});
|
|
251
|
+
info("Waiting for wallet approval…");
|
|
252
|
+
}
|
|
253
|
+
const session = await connection.getSession();
|
|
254
|
+
saveSession({
|
|
255
|
+
dappId,
|
|
256
|
+
accessToken: session.sessionClaims.accessToken
|
|
257
|
+
});
|
|
258
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
259
|
+
if (existsSync(SOCKET_PATH)) unlinkSync(SOCKET_PATH);
|
|
260
|
+
serveSession(session);
|
|
261
|
+
writeFileSync(PID_FILE, String(process.pid), { mode: 384 });
|
|
262
|
+
emit({
|
|
263
|
+
type: "connected",
|
|
264
|
+
ok: true,
|
|
265
|
+
address: session.sessionClaims.address
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/schemas/DisconnectResponseSchema.ts
|
|
271
|
+
const DisconnectResponseSchema = z.object({ ok: z.literal(true) });
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/schemas/ErrorResponseSchema.ts
|
|
275
|
+
const ErrorResponseSchema = z.object({
|
|
276
|
+
ok: z.literal(false),
|
|
277
|
+
error: z.string(),
|
|
278
|
+
message: z.string()
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/schemas/NotConnectedResponseSchema.ts
|
|
283
|
+
const NotConnectedResponseSchema = z.object({
|
|
284
|
+
ok: z.literal(true),
|
|
285
|
+
connected: z.literal(false)
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/schemas/SendResponseSchema.ts
|
|
290
|
+
const SendResponseSchema = z.object({
|
|
291
|
+
ok: z.literal(true),
|
|
292
|
+
txHash: z.string(),
|
|
293
|
+
to: z.string()
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/schemas/StatusResponseSchema.ts
|
|
298
|
+
const StatusResponseSchema = z.object({
|
|
299
|
+
ok: z.literal(true),
|
|
300
|
+
connected: z.literal(true),
|
|
301
|
+
address: z.string()
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/schemas/DaemonResponseSchema.ts
|
|
306
|
+
const DaemonResponseSchema = z.union([
|
|
307
|
+
StatusResponseSchema,
|
|
308
|
+
NotConnectedResponseSchema,
|
|
309
|
+
SendResponseSchema,
|
|
310
|
+
DisconnectResponseSchema,
|
|
311
|
+
ErrorResponseSchema
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/ipc/callDaemon.ts
|
|
316
|
+
function callDaemon(command) {
|
|
317
|
+
return new Promise((resolve, reject) => {
|
|
318
|
+
const socket = createConnection(SOCKET_PATH);
|
|
319
|
+
let buf = "";
|
|
320
|
+
socket.on("connect", () => socket.write(`${JSON.stringify(command)}\n`));
|
|
321
|
+
socket.on("data", (chunk) => {
|
|
322
|
+
buf += chunk.toString();
|
|
323
|
+
const newline = buf.indexOf("\n");
|
|
324
|
+
if (newline !== -1) {
|
|
325
|
+
socket.destroy();
|
|
326
|
+
const data = JSON.parse(buf.slice(0, newline));
|
|
327
|
+
const result = DaemonResponseSchema.safeParse(data);
|
|
328
|
+
if (result.success) resolve(result.data);
|
|
329
|
+
else reject(/* @__PURE__ */ new Error("Unexpected daemon response"));
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
socket.on("error", reject);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region src/ipc/isDaemonRunning.ts
|
|
338
|
+
function isDaemonRunning() {
|
|
339
|
+
if (!existsSync(PID_FILE) || !existsSync(SOCKET_PATH)) return false;
|
|
340
|
+
try {
|
|
341
|
+
const pid = parseInt(readFileSync(PID_FILE, "utf8"), 10);
|
|
342
|
+
process.kill(pid, 0);
|
|
343
|
+
return true;
|
|
344
|
+
} catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/index.ts
|
|
351
|
+
/**
|
|
352
|
+
* Bananalink Wallet CLI — for use by AI agents
|
|
353
|
+
*
|
|
354
|
+
* Commands:
|
|
355
|
+
* bananalink-wallet status
|
|
356
|
+
* bananalink-wallet connect
|
|
357
|
+
* bananalink-wallet send --to <address> --chain <name|id> --value <eth>
|
|
358
|
+
* bananalink-wallet disconnect
|
|
359
|
+
*
|
|
360
|
+
* Output: newline-delimited JSON on stdout.
|
|
361
|
+
* Informational messages go to stderr.
|
|
362
|
+
*/
|
|
363
|
+
async function cmdStatus() {
|
|
364
|
+
if (!isDaemonRunning()) {
|
|
365
|
+
emit({
|
|
366
|
+
ok: true,
|
|
367
|
+
connected: false
|
|
368
|
+
});
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
emit(await callDaemon({ cmd: "status" }));
|
|
372
|
+
}
|
|
373
|
+
async function cmdConnect() {
|
|
374
|
+
if (isDaemonRunning()) {
|
|
375
|
+
emit(await callDaemon({ cmd: "status" }));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const [daemonBin, daemonScriptArgs] = process.argv[1].endsWith(".ts") ? ["tsx", [
|
|
379
|
+
process.argv[1],
|
|
380
|
+
"connect",
|
|
381
|
+
"--daemon"
|
|
382
|
+
]] : [process.execPath, [
|
|
383
|
+
process.argv[1],
|
|
384
|
+
"connect",
|
|
385
|
+
"--daemon"
|
|
386
|
+
]];
|
|
387
|
+
const daemon = spawn(daemonBin, daemonScriptArgs, {
|
|
388
|
+
detached: true,
|
|
389
|
+
stdio: [
|
|
390
|
+
"ignore",
|
|
391
|
+
"pipe",
|
|
392
|
+
"inherit"
|
|
393
|
+
]
|
|
394
|
+
});
|
|
395
|
+
daemon.unref();
|
|
396
|
+
let buf = "";
|
|
397
|
+
daemon.stdout?.on("data", (chunk) => {
|
|
398
|
+
buf += chunk.toString();
|
|
399
|
+
const lines = buf.split("\n");
|
|
400
|
+
buf = lines.pop() ?? "";
|
|
401
|
+
for (const line of lines) {
|
|
402
|
+
if (!line.trim()) continue;
|
|
403
|
+
console.log(line);
|
|
404
|
+
try {
|
|
405
|
+
const msg = JSON.parse(line);
|
|
406
|
+
if (msg.type === "pending" || msg.type === "connected" || msg.ok === false) process.exit(msg.ok === false ? 1 : 0);
|
|
407
|
+
} catch {}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
daemon.on("error", (err) => {
|
|
411
|
+
emit({
|
|
412
|
+
ok: false,
|
|
413
|
+
error: "DaemonError",
|
|
414
|
+
message: err.message
|
|
415
|
+
});
|
|
416
|
+
process.exit(1);
|
|
417
|
+
});
|
|
418
|
+
daemon.on("close", (code) => {
|
|
419
|
+
emit({
|
|
420
|
+
ok: false,
|
|
421
|
+
error: "DaemonError",
|
|
422
|
+
message: `Daemon exited with code ${code}`
|
|
423
|
+
});
|
|
424
|
+
process.exit(1);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
async function cmdSend(opts) {
|
|
428
|
+
if (!isDaemonRunning()) throw new Error("No wallet connected. Run: bananalink-wallet connect");
|
|
429
|
+
emit(await callDaemon({
|
|
430
|
+
cmd: "send",
|
|
431
|
+
to: opts.to,
|
|
432
|
+
chainId: opts.chain,
|
|
433
|
+
value: opts.value,
|
|
434
|
+
data: opts.data,
|
|
435
|
+
notificationTitle: opts.notificationTitle,
|
|
436
|
+
notificationBody: opts.notificationBody
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
439
|
+
async function cmdDisconnect() {
|
|
440
|
+
if (isDaemonRunning()) await callDaemon({ cmd: "disconnect" });
|
|
441
|
+
else {
|
|
442
|
+
const stored = loadSession();
|
|
443
|
+
if (stored) saveSession({ dappId: stored.dappId });
|
|
444
|
+
}
|
|
445
|
+
emit({ ok: true });
|
|
446
|
+
}
|
|
447
|
+
const program = new Command("bananalink-wallet").description("Manage wallet sessions and send onchain transactions").addHelpText("after", "\nOutput: newline-delimited JSON on stdout. Errors go to stderr.").exitOverride();
|
|
448
|
+
program.command("status").description("Show the current wallet connection status").action(async () => {
|
|
449
|
+
await cmdStatus();
|
|
450
|
+
});
|
|
451
|
+
program.command("connect").description("Connect a wallet (spawns background daemon, exits after QR is ready)").option("--daemon", "Run as background daemon (internal use)").action(async (opts) => {
|
|
452
|
+
if (opts.daemon) await runDaemon();
|
|
453
|
+
else await cmdConnect();
|
|
454
|
+
});
|
|
455
|
+
program.command("send").description("Send a transaction via the connected wallet").requiredOption("--to <address>", "Recipient address").requiredOption("--chain <name|id>", "Chain to send on (e.g. base, ethereum, 137)").requiredOption("--value <eth>", "Amount in ETH (e.g. 0.01)").option("--data <hex>", "Optional calldata (hex)").option("--notification-title <text>", "Push notification title shown to the wallet user").option("--notification-body <text>", "Push notification body shown to the wallet user").action(async (opts) => {
|
|
456
|
+
resolveChain(opts.chain);
|
|
457
|
+
await cmdSend(opts);
|
|
458
|
+
});
|
|
459
|
+
program.command("disconnect").description("Disconnect the wallet and clear the session token").action(async () => {
|
|
460
|
+
await cmdDisconnect();
|
|
461
|
+
});
|
|
462
|
+
try {
|
|
463
|
+
await program.parseAsync(process.argv);
|
|
464
|
+
} catch (e) {
|
|
465
|
+
if (e && typeof e === "object" && "code" in e && typeof e.code === "string") {
|
|
466
|
+
const code = e.code;
|
|
467
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") process.exit(0);
|
|
468
|
+
if (code?.startsWith("commander.")) process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
emit({
|
|
471
|
+
ok: false,
|
|
472
|
+
...toErrorPayload(e)
|
|
473
|
+
});
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bananalink-test/agent-wallet",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool that gives AI agents onchain transaction capabilities via a Bananalink wallet",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"bananalink-wallet": "./dist/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@biomejs/biome": "2.3.15",
|
|
20
|
+
"tsdown": "^0.20.3",
|
|
21
|
+
"tsx": "^4.21.0",
|
|
22
|
+
"typescript": "^5.9.3"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"commander": "^14.0.3",
|
|
26
|
+
"viem": "^2.47.2",
|
|
27
|
+
"zod": "^4.3.6",
|
|
28
|
+
"@bananalink-test/client": "0.9.5"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"start": "tsx src/index.ts",
|
|
32
|
+
"build": "tsdown",
|
|
33
|
+
"type-check": "tsc --noEmit",
|
|
34
|
+
"lint": "biome check ."
|
|
35
|
+
}
|
|
36
|
+
}
|