aavegotchi-cli 0.2.1 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.2 - 2026-02-27
6
+
7
+ ### Added
8
+
9
+ - Command-targeted help:
10
+ - `ag --help`
11
+ - `ag <command> --help`
12
+ - `ag help <command>`
13
+ - Mapped-write help now includes mapped function name, required flags, and dry-run pattern.
14
+ - ABI-derived signature/input introspection for mapped help when `--abi-file` is passed.
15
+ - Unknown command suggestion list in `UNKNOWN_COMMAND` error details.
16
+ - CLI UX audit report: `docs/ux/cli-ux-audit-2026-02-27.md`.
17
+ - Native `bankr` signer backend:
18
+ - signer spec: `bankr[:address|apiKeyEnv|apiUrl]`
19
+ - default auth env var: `BANKR_API_KEY`
20
+ - default API URL: `https://api.bankr.bot`
21
+ - address auto-resolution via `GET /agent/me` when address is not pinned
22
+ - transaction submit via `POST /agent/submit`
23
+ - Bootstrap overrides now support `bankr`:
24
+ - `--signer-address` to pin wallet address
25
+ - `--signer-auth-env-var` to customize Bankr API key env var
26
+
27
+ ### Changed
28
+
29
+ - Stub namespace errors now include mapped command options for the requested root.
30
+
3
31
  ## 0.2.1 - 2026-02-27
4
32
 
5
33
  ### Added
package/README.md CHANGED
@@ -55,6 +55,34 @@ Planned domain namespaces are stubbed for parity tracking:
55
55
  Many Base-era write flows are already executable as mapped aliases in those namespaces (internally routed through `onchain send`).
56
56
  Example: `ag lending create --abi-file ./abis/GotchiLendingFacet.json --address 0x... --args-json '[...]' --json`
57
57
 
58
+ ## Command help and discoverability
59
+
60
+ The CLI supports command-targeted help:
61
+
62
+ ```bash
63
+ ag --help
64
+ ag tx send --help
65
+ ag help baazaar buy-now
66
+ ```
67
+
68
+ Mapped write commands now expose their onchain function mapping and required flags:
69
+
70
+ ```bash
71
+ ag baazaar buy-now --help
72
+ ```
73
+
74
+ If you provide `--abi-file` with `--help`, the CLI prints ABI-derived function signature and input names for the mapped method:
75
+
76
+ ```bash
77
+ ag baazaar buy-now --help --abi-file ./abis/BaazaarFacet.json
78
+ ```
79
+
80
+ Unknown commands return suggestions:
81
+
82
+ ```bash
83
+ ag tx snd --json
84
+ ```
85
+
58
86
  ## Dry-run writes
59
87
 
60
88
  Use `--dry-run` on write commands to run full preflight without broadcasting:
@@ -147,12 +175,26 @@ npm run ag -- auction active --first 5 --raw --json
147
175
  - `keychain:ACCOUNT_ID` (encrypted local key store; requires `AGCLI_KEYCHAIN_PASSPHRASE`)
148
176
  - `remote:URL|ADDRESS|AUTH_ENV` (HTTP signer service)
149
177
  - `ledger:DERIVATION_PATH|ADDRESS|BRIDGE_ENV` (external bridge command signer)
178
+ - `bankr[:ADDRESS|API_KEY_ENV|API_URL]` (Bankr-native signer via `/agent/me` + `/agent/submit`; defaults: `BANKR_API_KEY`, `https://api.bankr.bot`)
150
179
 
151
180
  Remote signer contract:
152
181
 
153
182
  - `GET /address` -> `{ "address": "0x..." }` (optional if address configured)
154
183
  - `POST /sign-transaction` -> `{ "rawTransaction": "0x..." }` or `{ "txHash": "0x..." }`
155
184
 
185
+ Bankr signer contract:
186
+
187
+ - `GET /agent/me` -> resolves wallet address when signer address is not pinned
188
+ - `POST /agent/submit` -> submits transaction and returns transaction hash
189
+ - auth header: `x-api-key: <BANKR_API_KEY>`
190
+
191
+ Bankr bootstrap example:
192
+
193
+ ```bash
194
+ BANKR_API_KEY=... \
195
+ npm run ag -- bootstrap --mode agent --profile bankr --chain base --signer bankr --json
196
+ ```
197
+
156
198
  Ledger bridge contract:
157
199
 
158
200
  - Set `AGCLI_LEDGER_BRIDGE_CMD` (or custom env var in signer config) to a command that reads tx payload JSON from stdin and outputs JSON containing either `rawTransaction` or `txHash`.
package/dist/abi.js ADDED
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseAbiFile = parseAbiFile;
37
+ exports.getAbiFunctionEntries = getAbiFunctionEntries;
38
+ exports.formatAbiFunctionSignature = formatAbiFunctionSignature;
39
+ exports.formatAbiFunctionInputs = formatAbiFunctionInputs;
40
+ const fs = __importStar(require("fs"));
41
+ const errors_1 = require("./errors");
42
+ function normalizeTypeName(item) {
43
+ if (!item.type) {
44
+ return "unknown";
45
+ }
46
+ if (!item.type.includes("tuple")) {
47
+ return item.type;
48
+ }
49
+ const components = item.components || [];
50
+ const componentTypes = components.map((component) => component.type).join(",");
51
+ return item.type.replace("tuple", `tuple(${componentTypes})`);
52
+ }
53
+ function parseAbiFile(filePath) {
54
+ if (!fs.existsSync(filePath)) {
55
+ throw new errors_1.CliError("ABI_NOT_FOUND", `ABI file not found: ${filePath}`, 2);
56
+ }
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
60
+ }
61
+ catch {
62
+ throw new errors_1.CliError("INVALID_ABI", `ABI file is not valid JSON: ${filePath}`, 2);
63
+ }
64
+ if (Array.isArray(parsed)) {
65
+ return parsed;
66
+ }
67
+ if (typeof parsed === "object" && parsed !== null && "abi" in parsed && Array.isArray(parsed.abi)) {
68
+ return parsed.abi;
69
+ }
70
+ throw new errors_1.CliError("INVALID_ABI", "ABI file must be an array or object containing 'abi'.", 2);
71
+ }
72
+ function getAbiFunctionEntries(abi, functionName) {
73
+ return abi.filter((item) => item.type === "function" && typeof item.name === "string" && item.name === functionName);
74
+ }
75
+ function formatAbiFunctionSignature(item) {
76
+ const inputTypes = (item.inputs || []).map((input) => normalizeTypeName(input));
77
+ return `${item.name}(${inputTypes.join(",")})`;
78
+ }
79
+ function formatAbiFunctionInputs(item) {
80
+ const inputs = item.inputs || [];
81
+ if (inputs.length === 0) {
82
+ return ["(none)"];
83
+ }
84
+ return inputs.map((input, index) => {
85
+ const label = input.name ? input.name : `arg${index}`;
86
+ return `${index}: ${label} (${normalizeTypeName(input)})`;
87
+ });
88
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listKnownCommands = listKnownCommands;
4
+ exports.suggestCommands = suggestCommands;
5
+ const mapped_1 = require("./commands/mapped");
6
+ const stubs_1 = require("./commands/stubs");
7
+ const BUILTIN_COMMANDS = [
8
+ "help",
9
+ "bootstrap",
10
+ "profile list",
11
+ "profile show",
12
+ "profile use",
13
+ "profile export",
14
+ "signer check",
15
+ "signer keychain list",
16
+ "signer keychain import",
17
+ "signer keychain remove",
18
+ "policy list",
19
+ "policy show",
20
+ "policy upsert",
21
+ "rpc check",
22
+ "tx send",
23
+ "tx status",
24
+ "tx resume",
25
+ "tx watch",
26
+ "batch run",
27
+ "onchain call",
28
+ "onchain send",
29
+ "subgraph list",
30
+ "subgraph check",
31
+ "subgraph query",
32
+ "baazaar listing get",
33
+ "baazaar listing active",
34
+ "baazaar listing mine",
35
+ "auction get",
36
+ "auction active",
37
+ "auction mine",
38
+ "auction bids",
39
+ "auction bids-mine",
40
+ ];
41
+ function listDomainReadCommands() {
42
+ return (0, stubs_1.listDomainStubRoots)().map((root) => `${root} read`);
43
+ }
44
+ function levenshteinDistance(a, b) {
45
+ if (a === b) {
46
+ return 0;
47
+ }
48
+ if (a.length === 0) {
49
+ return b.length;
50
+ }
51
+ if (b.length === 0) {
52
+ return a.length;
53
+ }
54
+ const previous = new Array(b.length + 1).fill(0).map((_, index) => index);
55
+ const current = new Array(b.length + 1).fill(0);
56
+ for (let i = 1; i <= a.length; i++) {
57
+ current[0] = i;
58
+ for (let j = 1; j <= b.length; j++) {
59
+ const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1;
60
+ current[j] = Math.min(current[j - 1] + 1, previous[j] + 1, previous[j - 1] + substitutionCost);
61
+ }
62
+ for (let j = 0; j <= b.length; j++) {
63
+ previous[j] = current[j];
64
+ }
65
+ }
66
+ return previous[b.length];
67
+ }
68
+ function listKnownCommands() {
69
+ return [
70
+ ...BUILTIN_COMMANDS,
71
+ ...listDomainReadCommands(),
72
+ ...(0, mapped_1.listMappedCommands)(),
73
+ ];
74
+ }
75
+ function suggestCommands(input, max = 5) {
76
+ const query = input.trim().toLowerCase();
77
+ if (!query) {
78
+ return [];
79
+ }
80
+ const scored = listKnownCommands().map((command) => {
81
+ const normalized = command.toLowerCase();
82
+ const startsWith = normalized.startsWith(query);
83
+ const includes = normalized.includes(query);
84
+ const distance = levenshteinDistance(query, normalized);
85
+ let score = distance;
86
+ if (startsWith) {
87
+ score = 0;
88
+ }
89
+ else if (includes) {
90
+ score = Math.min(score, 1);
91
+ }
92
+ return { command, score };
93
+ });
94
+ scored.sort((a, b) => {
95
+ if (a.score !== b.score) {
96
+ return a.score - b.score;
97
+ }
98
+ return a.command.localeCompare(b.command);
99
+ });
100
+ const threshold = Math.max(2, Math.floor(query.length / 2) + 1);
101
+ const filtered = scored.filter((entry) => entry.score <= threshold);
102
+ const results = (filtered.length > 0 ? filtered : scored).slice(0, max).map((entry) => entry.command);
103
+ return [...new Set(results)];
104
+ }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeCommandPath = normalizeCommandPath;
4
4
  exports.executeCommand = executeCommand;
5
5
  const errors_1 = require("./errors");
6
+ const command_catalog_1 = require("./command-catalog");
6
7
  const batch_1 = require("./commands/batch");
7
8
  const bootstrap_1 = require("./commands/bootstrap");
8
9
  const mapped_1 = require("./commands/mapped");
@@ -17,9 +18,12 @@ const stubs_1 = require("./commands/stubs");
17
18
  const subgraph_1 = require("./commands/subgraph");
18
19
  const tx_1 = require("./commands/tx");
19
20
  function normalizeCommandPath(positionals) {
20
- if (positionals.length === 0 || positionals[0] === "help") {
21
+ if (positionals.length === 0) {
21
22
  return ["help"];
22
23
  }
24
+ if (positionals[0] === "help") {
25
+ return positionals;
26
+ }
23
27
  return positionals;
24
28
  }
25
29
  async function executeCommand(ctx) {
@@ -220,5 +224,11 @@ async function executeCommand(ctx) {
220
224
  data: await (0, stubs_1.runDomainStubCommand)(ctx),
221
225
  };
222
226
  }
223
- throw new errors_1.CliError("UNKNOWN_COMMAND", `Unknown command '${ctx.commandPath.join(" ")}'.`, 2);
227
+ const command = ctx.commandPath.join(" ");
228
+ const suggestions = (0, command_catalog_1.suggestCommands)(command);
229
+ throw new errors_1.CliError("UNKNOWN_COMMAND", `Unknown command '${command}'.`, 2, {
230
+ command,
231
+ suggestions,
232
+ hint: suggestions.length > 0 ? "Try one of the suggested commands with '--help'." : "Run 'ag help'.",
233
+ });
224
234
  }
@@ -26,6 +26,15 @@ function buildSignerConfig(ctx, signerInput) {
26
26
  ...(authEnvVar ? { authEnvVar } : {}),
27
27
  };
28
28
  }
29
+ if (signer.type === "bankr") {
30
+ const address = (0, args_1.getFlagString)(ctx.args.flags, "signer-address");
31
+ const apiKeyEnvVar = (0, args_1.getFlagString)(ctx.args.flags, "signer-auth-env-var");
32
+ return {
33
+ ...signer,
34
+ ...(address ? { address: address } : {}),
35
+ ...(apiKeyEnvVar ? { apiKeyEnvVar } : {}),
36
+ };
37
+ }
29
38
  if (signer.type === "ledger") {
30
39
  const address = (0, args_1.getFlagString)(ctx.args.flags, "signer-address");
31
40
  const bridgeCommandEnvVar = (0, args_1.getFlagString)(ctx.args.flags, "signer-bridge-env-var");
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.findMappedFunction = findMappedFunction;
4
+ exports.listMappedCommands = listMappedCommands;
4
5
  exports.listMappedCommandsForRoot = listMappedCommandsForRoot;
6
+ exports.getMappedCommandEntries = getMappedCommandEntries;
5
7
  exports.runMappedDomainCommand = runMappedDomainCommand;
6
8
  const errors_1 = require("../errors");
7
9
  const onchain_1 = require("./onchain");
@@ -50,8 +52,14 @@ function findMappedFunction(commandPath) {
50
52
  const key = commandPath.join(" ");
51
53
  return MAPPED_WRITE_COMMANDS[key];
52
54
  }
55
+ function listMappedCommands() {
56
+ return Object.keys(MAPPED_WRITE_COMMANDS).sort((a, b) => a.localeCompare(b));
57
+ }
53
58
  function listMappedCommandsForRoot(root) {
54
- return Object.keys(MAPPED_WRITE_COMMANDS).filter((entry) => entry.startsWith(`${root} `));
59
+ return listMappedCommands().filter((entry) => entry.startsWith(`${root} `));
60
+ }
61
+ function getMappedCommandEntries() {
62
+ return { ...MAPPED_WRITE_COMMANDS };
55
63
  }
56
64
  async function runMappedDomainCommand(ctx) {
57
65
  const key = ctx.commandPath.join(" ");
@@ -1,44 +1,11 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.runOnchainCallCommand = runOnchainCallCommand;
37
4
  exports.runOnchainSendWithFunction = runOnchainSendWithFunction;
38
5
  exports.runOnchainSendCommand = runOnchainSendCommand;
39
- const fs = __importStar(require("fs"));
40
6
  const viem_1 = require("viem");
41
7
  const args_1 = require("../args");
8
+ const abi_1 = require("../abi");
42
9
  const chains_1 = require("../chains");
43
10
  const config_1 = require("../config");
44
11
  const errors_1 = require("../errors");
@@ -66,25 +33,6 @@ function parseArgsJson(value) {
66
33
  }
67
34
  return parsed;
68
35
  }
69
- function parseAbiFile(filePath) {
70
- if (!fs.existsSync(filePath)) {
71
- throw new errors_1.CliError("ABI_NOT_FOUND", `ABI file not found: ${filePath}`, 2);
72
- }
73
- let parsed;
74
- try {
75
- parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
76
- }
77
- catch {
78
- throw new errors_1.CliError("INVALID_ABI", `ABI file is not valid JSON: ${filePath}`, 2);
79
- }
80
- if (Array.isArray(parsed)) {
81
- return parsed;
82
- }
83
- if (typeof parsed === "object" && parsed !== null && "abi" in parsed && Array.isArray(parsed.abi)) {
84
- return parsed.abi;
85
- }
86
- throw new errors_1.CliError("INVALID_ABI", `ABI file must be an array or object containing 'abi'.`, 2);
87
- }
88
36
  function parseValueWei(value) {
89
37
  if (!value) {
90
38
  return undefined;
@@ -123,7 +71,7 @@ async function runOnchainCallCommand(ctx) {
123
71
  const chain = (0, chains_1.resolveChain)(profile.chain);
124
72
  const rpcUrl = (0, chains_1.resolveRpcUrl)(chain, (0, args_1.getFlagString)(ctx.args.flags, "rpc-url") || profile.rpcUrl);
125
73
  const abiFile = requireFlag((0, args_1.getFlagString)(ctx.args.flags, "abi-file"), "--abi-file");
126
- const abi = parseAbiFile(abiFile);
74
+ const abi = (0, abi_1.parseAbiFile)(abiFile);
127
75
  const address = parseAddress((0, args_1.getFlagString)(ctx.args.flags, "address"), "--address");
128
76
  const functionName = requireFlag((0, args_1.getFlagString)(ctx.args.flags, "function"), "--function");
129
77
  const args = parseArgsJson((0, args_1.getFlagString)(ctx.args.flags, "args-json"));
@@ -162,7 +110,7 @@ async function runOnchainSendWithFunction(ctx, forcedFunctionName, commandOverri
162
110
  const chain = (0, chains_1.resolveChain)(profile.chain);
163
111
  const rpcUrl = (0, chains_1.resolveRpcUrl)(chain, (0, args_1.getFlagString)(ctx.args.flags, "rpc-url") || profile.rpcUrl);
164
112
  const abiFile = requireFlag((0, args_1.getFlagString)(ctx.args.flags, "abi-file"), "--abi-file");
165
- const abi = parseAbiFile(abiFile);
113
+ const abi = (0, abi_1.parseAbiFile)(abiFile);
166
114
  const address = parseAddress((0, args_1.getFlagString)(ctx.args.flags, "address"), "--address");
167
115
  const functionName = forcedFunctionName || requireFlag((0, args_1.getFlagString)(ctx.args.flags, "function"), "--function");
168
116
  const args = parseArgsJson((0, args_1.getFlagString)(ctx.args.flags, "args-json"));
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isDomainStubRoot = isDomainStubRoot;
4
+ exports.listDomainStubRoots = listDomainStubRoots;
4
5
  exports.runDomainStubCommand = runDomainStubCommand;
5
6
  const errors_1 = require("../errors");
7
+ const mapped_1 = require("./mapped");
6
8
  const SUPPORTED_STUB_ROOTS = [
7
9
  "gotchi",
8
10
  "portal",
@@ -22,11 +24,17 @@ const SUPPORTED_STUB_ROOTS = [
22
24
  function isDomainStubRoot(root) {
23
25
  return SUPPORTED_STUB_ROOTS.includes(root);
24
26
  }
27
+ function listDomainStubRoots() {
28
+ return SUPPORTED_STUB_ROOTS;
29
+ }
25
30
  async function runDomainStubCommand(ctx) {
26
31
  const command = ctx.commandPath.join(" ");
32
+ const root = ctx.commandPath[0];
33
+ const availableMapped = (0, mapped_1.listMappedCommandsForRoot)(root);
27
34
  throw new errors_1.CliError("COMMAND_NOT_IMPLEMENTED", `Command '${command}' is planned but not implemented yet.`, 2, {
28
35
  command,
29
- hint: "If this is a mapped onchain write, pass full subcommand plus --abi-file/--address/--args-json.",
36
+ hint: "Run 'ag help <command>' for usage. Mapped writes require --abi-file/--address/--args-json.",
37
+ availableMapped,
30
38
  plannedRoots: SUPPORTED_STUB_ROOTS,
31
39
  });
32
40
  }
package/dist/index.js CHANGED
@@ -11,8 +11,13 @@ async function run() {
11
11
  const globals = (0, args_1.normalizeGlobals)(args);
12
12
  (0, logger_1.initializeLogger)(globals);
13
13
  const commandPath = (0, command_runner_1.normalizeCommandPath)(args.positionals);
14
+ const helpRequested = (0, args_1.getFlagBoolean)(args.flags, "help", "h");
14
15
  if (commandPath[0] === "help") {
15
- (0, output_1.outputHelp)();
16
+ (0, output_1.outputHelp)(commandPath.slice(1), args.flags);
17
+ return;
18
+ }
19
+ if (helpRequested) {
20
+ (0, output_1.outputHelp)(commandPath, args.flags);
16
21
  return;
17
22
  }
18
23
  const ctx = {
package/dist/output.js CHANGED
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildHelpText = buildHelpText;
3
4
  exports.outputSuccess = outputSuccess;
4
5
  exports.outputError = outputError;
5
6
  exports.outputHelp = outputHelp;
7
+ const args_1 = require("./args");
8
+ const abi_1 = require("./abi");
9
+ const command_catalog_1 = require("./command-catalog");
10
+ const mapped_1 = require("./commands/mapped");
11
+ const stubs_1 = require("./commands/stubs");
6
12
  function stringifyWithBigInt(input) {
7
13
  return JSON.stringify(input, (_, value) => {
8
14
  if (typeof value === "bigint") {
@@ -17,6 +23,397 @@ function buildMeta(mode) {
17
23
  mode,
18
24
  };
19
25
  }
26
+ function toLines(items, prefix = " ") {
27
+ if (items.length === 0) {
28
+ return `${prefix}(none)`;
29
+ }
30
+ return items.map((item) => `${prefix}${item}`).join("\n");
31
+ }
32
+ function buildGlobalHelpText() {
33
+ return `
34
+ Aavegotchi CLI (agent-first foundation)
35
+
36
+ Usage:
37
+ ag <command> [options]
38
+ ag <command> --help
39
+ ag help <command>
40
+
41
+ Core commands:
42
+ bootstrap
43
+ profile list|show|use|export
44
+ signer check
45
+ signer keychain list|import|remove
46
+ policy list|show|upsert
47
+ rpc check
48
+
49
+ Tx commands:
50
+ tx send|status|resume|watch
51
+
52
+ Automation commands:
53
+ batch run --file plan.yaml
54
+
55
+ Power-user commands:
56
+ onchain call|send
57
+ subgraph list|check|query
58
+
59
+ Subgraph wrappers:
60
+ baazaar listing get|active|mine
61
+ auction get|active|mine|bids|bids-mine
62
+
63
+ Domain namespaces:
64
+ gotchi, portal, wearables, items, inventory, baazaar, auction, lending, staking, gotchi-points, realm, alchemica, forge, token
65
+ (many write flows are mapped aliases that route through onchain send)
66
+
67
+ Global flags:
68
+ --mode <agent|human> Agent mode implies --json --yes
69
+ --json, -j Emit JSON envelope output
70
+ --yes, -y Skip prompts
71
+ --profile NAME Select profile globally
72
+ --help, -h Show command-specific help
73
+
74
+ Examples:
75
+ ag bootstrap --profile prod --chain base --signer readonly --json
76
+ ag tx send --profile prod --to 0xabc... --value-wei 0 --dry-run --json
77
+ ag onchain send --profile prod --abi-file ./abi.json --address 0xabc... --function approve --args-json '["0xdef...", "1"]' --dry-run --json
78
+ BANKR_API_KEY=... ag bootstrap --profile bankr --chain base --signer bankr --json
79
+ ag baazaar buy-now --help
80
+ ag help tx send
81
+ `;
82
+ }
83
+ const STATIC_HELP = {
84
+ bootstrap: `
85
+ Usage:
86
+ ag bootstrap --profile <name> [--chain <base|base-sepolia|id>] [--rpc-url <url>] [--signer <config>] [--policy <name>] [--skip-signer-check] [--json]
87
+
88
+ Required:
89
+ --profile <name>
90
+
91
+ Signer formats:
92
+ readonly
93
+ env:ENV_VAR
94
+ keychain:ACCOUNT_ID
95
+ ledger[:DERIVATION_PATH|ADDRESS|BRIDGE_ENV_VAR]
96
+ remote:URL|ADDRESS|AUTH_ENV_VAR
97
+ bankr[:ADDRESS|API_KEY_ENV|API_URL]
98
+ `,
99
+ profile: `
100
+ Usage:
101
+ ag profile list [--json]
102
+ ag profile show [--profile <name>] [--json]
103
+ ag profile use --profile <name> [--json]
104
+ ag profile export [--profile <name>] [--json]
105
+ `,
106
+ "profile list": `
107
+ Usage:
108
+ ag profile list [--json]
109
+ `,
110
+ "profile show": `
111
+ Usage:
112
+ ag profile show [--profile <name>] [--json]
113
+ `,
114
+ "profile use": `
115
+ Usage:
116
+ ag profile use --profile <name> [--json]
117
+ `,
118
+ "profile export": `
119
+ Usage:
120
+ ag profile export [--profile <name>] [--json]
121
+ `,
122
+ signer: `
123
+ Usage:
124
+ ag signer check [--profile <name>] [--json]
125
+ ag signer keychain list [--json]
126
+ ag signer keychain import --account-id <id> --private-key-env <ENV> [--json]
127
+ ag signer keychain remove --account-id <id> [--json]
128
+ `,
129
+ "signer check": `
130
+ Usage:
131
+ ag signer check [--profile <name>] [--json]
132
+ `,
133
+ "signer keychain": `
134
+ Usage:
135
+ ag signer keychain list [--json]
136
+ ag signer keychain import --account-id <id> --private-key-env <ENV> [--json]
137
+ ag signer keychain remove --account-id <id> [--json]
138
+ `,
139
+ "signer keychain list": `
140
+ Usage:
141
+ ag signer keychain list [--json]
142
+ `,
143
+ "signer keychain import": `
144
+ Usage:
145
+ ag signer keychain import --account-id <id> --private-key-env <ENV> [--json]
146
+ `,
147
+ "signer keychain remove": `
148
+ Usage:
149
+ ag signer keychain remove --account-id <id> [--json]
150
+ `,
151
+ policy: `
152
+ Usage:
153
+ ag policy list [--json]
154
+ ag policy show --policy <name> [--json]
155
+ ag policy upsert --policy <name> [--max-value-wei <wei>] [--max-gas-limit <gas>] [--max-fee-per-gas-wei <wei>] [--max-priority-fee-per-gas-wei <wei>] [--allowed-to <0x...,0x...>] [--json]
156
+ `,
157
+ "policy list": `
158
+ Usage:
159
+ ag policy list [--json]
160
+ `,
161
+ "policy show": `
162
+ Usage:
163
+ ag policy show --policy <name> [--json]
164
+ `,
165
+ "policy upsert": `
166
+ Usage:
167
+ ag policy upsert --policy <name> [policy flags...] [--json]
168
+ `,
169
+ rpc: `
170
+ Usage:
171
+ ag rpc check [--profile <name>] [--rpc-url <url>] [--json]
172
+ `,
173
+ "rpc check": `
174
+ Usage:
175
+ ag rpc check [--profile <name>] [--rpc-url <url>] [--json]
176
+ `,
177
+ tx: `
178
+ Usage:
179
+ ag tx send --to <0x...> [--value-wei <wei>] [--data <0x...>] [--profile <name>] [--nonce-policy <safe|replace|manual>] [--nonce <n>] [--dry-run] [--wait|--confirm] [--timeout-ms <ms>] [--json]
180
+ ag tx status [--idempotency-key <key> | --tx-hash <0x...> | --limit <n>] [--json]
181
+ ag tx resume --idempotency-key <key> [--profile <name>] [--timeout-ms <ms>] [--json]
182
+ ag tx watch --idempotency-key <key> [--interval-ms <ms>] [--timeout-ms <ms>] [--json]
183
+
184
+ Notes:
185
+ --dry-run cannot be combined with --wait/--confirm.
186
+ `,
187
+ "tx send": `
188
+ Usage:
189
+ ag tx send --to <0x...> [--value-wei <wei>] [--data <0x...>] [--profile <name>] [--dry-run] [--wait|--confirm] [--json]
190
+ `,
191
+ "tx status": `
192
+ Usage:
193
+ ag tx status [--idempotency-key <key> | --tx-hash <0x...> | --limit <n>] [--json]
194
+ `,
195
+ "tx resume": `
196
+ Usage:
197
+ ag tx resume --idempotency-key <key> [--profile <name>] [--timeout-ms <ms>] [--json]
198
+ `,
199
+ "tx watch": `
200
+ Usage:
201
+ ag tx watch --idempotency-key <key> [--interval-ms <ms>] [--timeout-ms <ms>] [--json]
202
+ `,
203
+ onchain: `
204
+ Usage:
205
+ ag onchain call --profile <name> --abi-file <path> --address <0x...> --function <name> [--args-json '[...]'] [--json]
206
+ ag onchain send --profile <name> --abi-file <path> --address <0x...> --function <name> [--args-json '[...]'] [--value-wei <wei>] [--nonce-policy <safe|replace|manual>] [--nonce <n>] [--dry-run] [--wait] [--json]
207
+ `,
208
+ "onchain call": `
209
+ Usage:
210
+ ag onchain call --profile <name> --abi-file <path> --address <0x...> --function <name> [--args-json '[...]'] [--json]
211
+ `,
212
+ "onchain send": `
213
+ Usage:
214
+ ag onchain send --profile <name> --abi-file <path> --address <0x...> --function <name> [--args-json '[...]'] [--value-wei <wei>] [--nonce-policy <safe|replace|manual>] [--nonce <n>] [--dry-run] [--wait] [--json]
215
+
216
+ Required:
217
+ --abi-file
218
+ --address
219
+ --function
220
+ --profile (or active profile)
221
+
222
+ Notes:
223
+ --dry-run cannot be combined with --wait.
224
+ `,
225
+ subgraph: `
226
+ Usage:
227
+ ag subgraph list [--json]
228
+ ag subgraph check --source <core-base|gbm-base> [--timeout-ms <ms>] [--raw] [--json]
229
+ ag subgraph query --source <core-base|gbm-base> (--query <graphql> | --query-file <path>) [--variables-json '{...}'] [--timeout-ms <ms>] [--raw] [--json]
230
+ `,
231
+ "subgraph list": `
232
+ Usage:
233
+ ag subgraph list [--json]
234
+ `,
235
+ "subgraph check": `
236
+ Usage:
237
+ ag subgraph check --source <core-base|gbm-base> [--timeout-ms <ms>] [--raw] [--json]
238
+ `,
239
+ "subgraph query": `
240
+ Usage:
241
+ ag subgraph query --source <core-base|gbm-base> (--query <graphql> | --query-file <path>) [--variables-json '{...}'] [--timeout-ms <ms>] [--raw] [--json]
242
+ `,
243
+ batch: `
244
+ Usage:
245
+ ag batch run --file <plan.yaml> [--json]
246
+ `,
247
+ "batch run": `
248
+ Usage:
249
+ ag batch run --file <plan.yaml> [--json]
250
+ `,
251
+ baazaar: `
252
+ Usage:
253
+ ag baazaar listing get --kind <erc721|erc1155> --id <listingId> [--verify-onchain] [--json]
254
+ ag baazaar listing active --kind <erc721|erc1155> [--first <n>] [--skip <n>] [--json]
255
+ ag baazaar listing mine --kind <erc721|erc1155> --seller <0x...> [--first <n>] [--skip <n>] [--json]
256
+ ag baazaar <mapped-write> --help
257
+ `,
258
+ "baazaar listing": `
259
+ Usage:
260
+ ag baazaar listing get --kind <erc721|erc1155> --id <listingId> [--verify-onchain] [--json]
261
+ ag baazaar listing active --kind <erc721|erc1155> [--first <n>] [--skip <n>] [--json]
262
+ ag baazaar listing mine --kind <erc721|erc1155> --seller <0x...> [--first <n>] [--skip <n>] [--json]
263
+ `,
264
+ "baazaar listing get": `
265
+ Usage:
266
+ ag baazaar listing get --kind <erc721|erc1155> --id <listingId> [--verify-onchain] [--json]
267
+ `,
268
+ "baazaar listing active": `
269
+ Usage:
270
+ ag baazaar listing active --kind <erc721|erc1155> [--first <n>] [--skip <n>] [--json]
271
+ `,
272
+ "baazaar listing mine": `
273
+ Usage:
274
+ ag baazaar listing mine --kind <erc721|erc1155> --seller <0x...> [--first <n>] [--skip <n>] [--json]
275
+ `,
276
+ auction: `
277
+ Usage:
278
+ ag auction get --id <auctionId> [--verify-onchain] [--json]
279
+ ag auction active [--first <n>] [--skip <n>] [--at-time <unixSec>] [--json]
280
+ ag auction mine --seller <0x...> [--first <n>] [--skip <n>] [--at-time <unixSec>] [--json]
281
+ ag auction bids --auction-id <auctionId> [--first <n>] [--skip <n>] [--json]
282
+ ag auction bids-mine --bidder <0x...> [--first <n>] [--skip <n>] [--json]
283
+ ag auction <mapped-write> --help
284
+ `,
285
+ "auction get": `
286
+ Usage:
287
+ ag auction get --id <auctionId> [--verify-onchain] [--json]
288
+ `,
289
+ "auction active": `
290
+ Usage:
291
+ ag auction active [--first <n>] [--skip <n>] [--at-time <unixSec>] [--json]
292
+ `,
293
+ "auction mine": `
294
+ Usage:
295
+ ag auction mine --seller <0x...> [--first <n>] [--skip <n>] [--at-time <unixSec>] [--json]
296
+ `,
297
+ "auction bids": `
298
+ Usage:
299
+ ag auction bids --auction-id <auctionId> [--first <n>] [--skip <n>] [--json]
300
+ `,
301
+ "auction bids-mine": `
302
+ Usage:
303
+ ag auction bids-mine --bidder <0x...> [--first <n>] [--skip <n>] [--json]
304
+ `,
305
+ };
306
+ function buildDomainRootHelp(root) {
307
+ const mapped = (0, mapped_1.listMappedCommandsForRoot)(root);
308
+ return `
309
+ Usage:
310
+ ag ${root} read --profile <name> --abi-file <path> --address <0x...> --function <name> [--args-json '[...]'] [--json]
311
+ ag ${root} <mapped-write> --help
312
+
313
+ Mapped writes for '${root}':
314
+ ${toLines(mapped)}
315
+ `;
316
+ }
317
+ function buildMappedCommandHelp(commandPath, flags) {
318
+ const command = commandPath.join(" ");
319
+ const method = (0, mapped_1.findMappedFunction)(commandPath);
320
+ if (!method) {
321
+ return "";
322
+ }
323
+ const abiFile = (0, args_1.getFlagString)(flags, "abi-file");
324
+ const signatureLines = [];
325
+ const inputLines = [];
326
+ if (abiFile) {
327
+ try {
328
+ const abi = (0, abi_1.parseAbiFile)(abiFile);
329
+ const entries = (0, abi_1.getAbiFunctionEntries)(abi, method);
330
+ if (entries.length === 0) {
331
+ signatureLines.push(`No function named '${method}' found in '${abiFile}'.`);
332
+ }
333
+ else {
334
+ for (const entry of entries) {
335
+ signatureLines.push((0, abi_1.formatAbiFunctionSignature)(entry));
336
+ inputLines.push(...(0, abi_1.formatAbiFunctionInputs)(entry));
337
+ }
338
+ }
339
+ }
340
+ catch (error) {
341
+ const message = error instanceof Error ? error.message : "Unable to parse ABI file.";
342
+ signatureLines.push(`Could not inspect ABI file '${abiFile}': ${message}`);
343
+ }
344
+ }
345
+ else {
346
+ signatureLines.push("Pass --abi-file <path> with --help to print exact ABI-derived signature and input names.");
347
+ }
348
+ return `
349
+ Usage:
350
+ ag ${command} --profile <name> --abi-file <path> --address <0x...> --args-json '[...]' [--value-wei <wei>] [--nonce-policy <safe|replace|manual>] [--nonce <n>] [--dry-run] [--wait] [--json]
351
+
352
+ Mapped to onchain function:
353
+ ${method}
354
+
355
+ Required flags:
356
+ --abi-file
357
+ --address
358
+ --args-json
359
+ --profile (or active profile)
360
+
361
+ Dry-run example:
362
+ ag ${command} --profile prod --abi-file ./abi.json --address 0xabc... --args-json '[<arg0>,<arg1>]' --dry-run --json
363
+
364
+ ABI signature info:
365
+ ${toLines(signatureLines)}
366
+
367
+ ABI inputs:
368
+ ${toLines(inputLines)}
369
+ `;
370
+ }
371
+ function buildUnknownHelpText(commandPath) {
372
+ const command = commandPath.join(" ");
373
+ const suggestions = (0, command_catalog_1.suggestCommands)(command);
374
+ const suggestionBlock = suggestions.length
375
+ ? `\nSuggested commands:\n${toLines(suggestions)}`
376
+ : "";
377
+ return `No command-specific help found for '${command}'.${suggestionBlock}
378
+
379
+ Use 'ag help' for the full command surface.
380
+ `;
381
+ }
382
+ function normalizeHelpPath(commandPath) {
383
+ return commandPath.map((part) => part.trim()).filter(Boolean);
384
+ }
385
+ function buildHelpText(commandPath = [], flags = {}) {
386
+ const target = normalizeHelpPath(commandPath);
387
+ if (target.length === 0) {
388
+ return buildGlobalHelpText().trim();
389
+ }
390
+ const mappedHelp = buildMappedCommandHelp(target, flags);
391
+ if (mappedHelp) {
392
+ return mappedHelp.trim();
393
+ }
394
+ const key = target.join(" ");
395
+ if (STATIC_HELP[key]) {
396
+ return STATIC_HELP[key].trim();
397
+ }
398
+ if (target.length === 1 && (0, stubs_1.isDomainStubRoot)(target[0])) {
399
+ return buildDomainRootHelp(target[0]).trim();
400
+ }
401
+ if (target.length > 1 && (0, stubs_1.isDomainStubRoot)(target[0])) {
402
+ const root = target[0];
403
+ const mapped = (0, mapped_1.listMappedCommandsForRoot)(root);
404
+ return `
405
+ No direct help entry for '${key}'.
406
+
407
+ Mapped writes under '${root}':
408
+ ${toLines(mapped)}
409
+
410
+ Try:
411
+ ag ${root} --help
412
+ ag ${root} read --help
413
+ `.trim();
414
+ }
415
+ return buildUnknownHelpText(target).trim();
416
+ }
20
417
  function outputSuccess(command, data, globals) {
21
418
  if (globals.json) {
22
419
  const envelope = {
@@ -53,70 +450,6 @@ function outputError(command, error, globals) {
53
450
  console.error(stringifyWithBigInt(error.details));
54
451
  }
55
452
  }
56
- function outputHelp() {
57
- console.log(`
58
- Aavegotchi CLI (agent-first foundation)
59
-
60
- Usage:
61
- ag <command> [options]
62
-
63
- Core commands:
64
- bootstrap Create/update and activate a profile with RPC/signer preflight
65
- profile list|show|use|export Manage profiles
66
- signer check Verify active profile signer backend and account readiness
67
- signer keychain list|import|remove
68
- policy list|show|upsert Manage transaction policies
69
- rpc check Verify RPC connectivity + signer backend health
70
-
71
- Tx commands:
72
- tx send Send a raw EVM transaction (or simulate only with --dry-run)
73
- tx status Read tx status by idempotency key/hash or list recent
74
- tx resume Resume waiting for a previously submitted tx
75
- tx watch Poll journal until tx is confirmed
76
-
77
- Automation commands:
78
- batch run --file plan.yaml Run a YAML execution plan (dependency-aware)
79
-
80
- Power-user commands:
81
- onchain call Call any ABI function from --abi-file
82
- onchain send Send any ABI function as a transaction (or simulate with --dry-run)
83
- subgraph list|check|query List/check/query canonical Goldsky subgraphs
84
-
85
- Domain namespaces:
86
- gotchi, portal, wearables, items, inventory, baazaar, auction, lending, staking, gotchi-points, realm, alchemica, forge, token
87
- (many write flows are mapped to onchain send aliases; unmatched commands return typed not-implemented)
88
-
89
- Subgraph wrappers:
90
- baazaar listing get|active|mine Read Baazaar listing data from core-base subgraph
91
- auction get|active|mine|bids|bids-mine
92
- Read GBM auction/bid data from gbm-base subgraph
93
-
94
- Global flags:
95
- --mode <agent|human> Agent mode implies --json --yes
96
- --json, -j Emit JSON envelope output
97
- --yes, -y Skip prompts (write commands assume explicit flags)
98
- --profile NAME Select profile globally
99
-
100
- Bootstrap flags:
101
- --profile NAME Profile to create or update (required)
102
- --chain base|base-sepolia|<id> Chain key or numeric chain id (default: base)
103
- --rpc-url URL RPC endpoint (optional when chain preset exists)
104
- --signer readonly|env:VAR|keychain:<id>|ledger[:path|address|bridgeEnv]|remote:<url|address|authEnv>
105
- --signer-address 0x... Optional override for remote/ledger signer address
106
- --signer-auth-env-var ENV_VAR Optional remote signer bearer token env var
107
- --signer-bridge-env-var ENV_VAR Optional ledger bridge command env var name
108
- --policy NAME Policy label (default: default)
109
- --skip-signer-check Persist signer config without backend validation
110
-
111
- Examples:
112
- ag bootstrap --mode agent --profile prod --chain base --signer env:AGCLI_PRIVATE_KEY --json
113
- AGCLI_KEYCHAIN_PASSPHRASE=... AGCLI_PRIVATE_KEY=0x... ag signer keychain import --account-id bot --private-key-env AGCLI_PRIVATE_KEY --json
114
- ag tx send --profile prod --to 0xabc... --value-wei 1000000000000000 --wait --json
115
- ag tx send --profile prod --to 0xabc... --value-wei 0 --dry-run --json
116
- ag subgraph check --source core-base --json
117
- ag baazaar listing active --kind erc721 --first 20 --json
118
- ag auction active --first 20 --json
119
- ag lending create --profile prod --abi-file ./abis/GotchiLendingFacet.json --address 0xabc... --args-json '[...]' --json
120
- ag batch run --file ./plan.yaml --json
121
- `);
453
+ function outputHelp(commandPath = [], flags = {}) {
454
+ console.log(buildHelpText(commandPath, flags));
122
455
  }
package/dist/schemas.js CHANGED
@@ -22,6 +22,12 @@ exports.signerSchema = zod_1.z.discriminatedUnion("type", [
22
22
  address: addressSchema.optional(),
23
23
  authEnvVar: zod_1.z.string().regex(/^[A-Z_][A-Z0-9_]*$/).optional(),
24
24
  }),
25
+ zod_1.z.object({
26
+ type: zod_1.z.literal("bankr"),
27
+ address: addressSchema.optional(),
28
+ apiKeyEnvVar: zod_1.z.string().regex(/^[A-Z_][A-Z0-9_]*$/).optional(),
29
+ apiUrl: zod_1.z.string().url().optional(),
30
+ }),
25
31
  ]);
26
32
  exports.policySchema = zod_1.z.object({
27
33
  name: zod_1.z.string().min(1),
package/dist/signer.js CHANGED
@@ -9,6 +9,9 @@ const errors_1 = require("./errors");
9
9
  const keychain_1 = require("./keychain");
10
10
  const REMOTE_SIGN_ADDRESS_PATH = "/address";
11
11
  const REMOTE_SIGN_TX_PATH = "/sign-transaction";
12
+ const BANKR_DEFAULT_API_URL = "https://api.bankr.bot";
13
+ const BANKR_AGENT_ME_PATH = "/agent/me";
14
+ const BANKR_AGENT_SUBMIT_PATH = "/agent/submit";
12
15
  function parsePrivateKey(value, hint) {
13
16
  if (!/^0x[0-9a-fA-F]{64}$/.test(value)) {
14
17
  throw new errors_1.CliError("INVALID_PRIVATE_KEY", `${hint} is not a valid private key.`, 2);
@@ -77,6 +80,111 @@ function buildRemoteHeaders(signer) {
77
80
  }
78
81
  return headers;
79
82
  }
83
+ function requireEnvVarName(value, context) {
84
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(value)) {
85
+ throw new errors_1.CliError("INVALID_SIGNER_SPEC", `Invalid ${context} env var '${value}'.`, 2);
86
+ }
87
+ return value;
88
+ }
89
+ function resolveBankrApiUrl(signer) {
90
+ return (signer.apiUrl || BANKR_DEFAULT_API_URL).replace(/\/+$/, "");
91
+ }
92
+ function buildBankrHeaders(signer) {
93
+ const envVar = signer.apiKeyEnvVar || "BANKR_API_KEY";
94
+ const token = process.env[envVar];
95
+ if (!token) {
96
+ throw new errors_1.CliError("MISSING_SIGNER_SECRET", `Missing environment variable '${envVar}'.`, 2);
97
+ }
98
+ return {
99
+ "content-type": "application/json",
100
+ "x-api-key": token,
101
+ };
102
+ }
103
+ async function fetchBankrJson(url, init) {
104
+ let response;
105
+ try {
106
+ response = await fetch(url, init);
107
+ }
108
+ catch (error) {
109
+ throw new errors_1.CliError("BANKR_API_UNREACHABLE", "Failed to connect to Bankr API.", 2, {
110
+ url,
111
+ message: error instanceof Error ? error.message : String(error),
112
+ });
113
+ }
114
+ const text = await response.text();
115
+ let parsed = {};
116
+ if (text) {
117
+ try {
118
+ parsed = JSON.parse(text);
119
+ }
120
+ catch {
121
+ parsed = text;
122
+ }
123
+ }
124
+ if (!response.ok) {
125
+ throw new errors_1.CliError("BANKR_API_HTTP_ERROR", `Bankr API responded with HTTP ${response.status}.`, 2, {
126
+ url,
127
+ status: response.status,
128
+ body: parsed,
129
+ });
130
+ }
131
+ return parsed;
132
+ }
133
+ function parseBankrAddressCandidate(candidate) {
134
+ if (typeof candidate !== "string") {
135
+ return undefined;
136
+ }
137
+ if (!/^0x[a-fA-F0-9]{40}$/.test(candidate)) {
138
+ return undefined;
139
+ }
140
+ return candidate.toLowerCase();
141
+ }
142
+ async function resolveBankrAddress(signer, headers) {
143
+ if (signer.address) {
144
+ return ensureAddress(signer.address, "bankr signer address");
145
+ }
146
+ const apiUrl = resolveBankrApiUrl(signer);
147
+ const response = (await fetchBankrJson(addDefaultPaths(apiUrl, BANKR_AGENT_ME_PATH), {
148
+ method: "GET",
149
+ headers,
150
+ }));
151
+ const direct = parseBankrAddressCandidate(response.walletAddress) ||
152
+ parseBankrAddressCandidate(response.address) ||
153
+ parseBankrAddressCandidate(response.agent?.walletAddress) ||
154
+ parseBankrAddressCandidate(response.agent?.address);
155
+ if (direct) {
156
+ return direct;
157
+ }
158
+ if (Array.isArray(response.wallets)) {
159
+ const evmWallet = response.wallets.find((wallet) => wallet.chain === "evm");
160
+ const evmAddress = parseBankrAddressCandidate(evmWallet?.address) || parseBankrAddressCandidate(evmWallet?.walletAddress);
161
+ if (evmAddress) {
162
+ return evmAddress;
163
+ }
164
+ for (const wallet of response.wallets) {
165
+ const anyAddress = parseBankrAddressCandidate(wallet.address) || parseBankrAddressCandidate(wallet.walletAddress);
166
+ if (anyAddress) {
167
+ return anyAddress;
168
+ }
169
+ }
170
+ }
171
+ throw new errors_1.CliError("BANKR_API_PROTOCOL_ERROR", "Bankr /agent/me response missing wallet address.", 2, {
172
+ keys: Object.keys(response),
173
+ });
174
+ }
175
+ function parseBankrSubmitHash(response) {
176
+ if (typeof response !== "object" || response === null) {
177
+ return undefined;
178
+ }
179
+ const root = response;
180
+ const nested = typeof root.result === "object" && root.result !== null ? root.result : undefined;
181
+ return (parseTxHash(root.transactionHash) ||
182
+ parseTxHash(root.txHash) ||
183
+ parseTxHash(root.hash) ||
184
+ parseTxHash(nested?.transactionHash) ||
185
+ parseTxHash(nested?.txHash) ||
186
+ parseTxHash(nested?.hash));
187
+ }
80
188
  async function resolveRemoteAddress(signer, headers) {
81
189
  if (signer.address) {
82
190
  return ensureAddress(signer.address, "remote signer address");
@@ -175,6 +283,47 @@ function parseSignerLedgerSpec(value) {
175
283
  }
176
284
  return signer;
177
285
  }
286
+ function parseSignerBankrSpec(value) {
287
+ if (value === "bankr") {
288
+ return { type: "bankr" };
289
+ }
290
+ const body = value.slice("bankr:".length).trim();
291
+ const signer = {
292
+ type: "bankr",
293
+ };
294
+ if (!body) {
295
+ return signer;
296
+ }
297
+ if (!body.includes("|")) {
298
+ if (/^0x[a-fA-F0-9]{40}$/.test(body)) {
299
+ signer.address = ensureAddress(body, "bankr signer address");
300
+ return signer;
301
+ }
302
+ if (/^[A-Z_][A-Z0-9_]*$/.test(body)) {
303
+ signer.apiKeyEnvVar = requireEnvVarName(body, "bankr api key");
304
+ return signer;
305
+ }
306
+ if (/^https?:\/\//i.test(body)) {
307
+ signer.apiUrl = body;
308
+ return signer;
309
+ }
310
+ throw new errors_1.CliError("INVALID_SIGNER_SPEC", `Invalid bankr signer format '${value}'.`, 2);
311
+ }
312
+ const [addressPart, apiKeyEnvVarPart, apiUrlPart] = body.split("|").map((entry) => entry.trim());
313
+ if (addressPart) {
314
+ signer.address = ensureAddress(addressPart, "bankr signer address");
315
+ }
316
+ if (apiKeyEnvVarPart) {
317
+ signer.apiKeyEnvVar = requireEnvVarName(apiKeyEnvVarPart, "bankr api key");
318
+ }
319
+ if (apiUrlPart) {
320
+ if (!/^https?:\/\//i.test(apiUrlPart)) {
321
+ throw new errors_1.CliError("INVALID_SIGNER_SPEC", `Invalid bankr API URL in '${value}'.`, 2);
322
+ }
323
+ signer.apiUrl = apiUrlPart;
324
+ }
325
+ return signer;
326
+ }
178
327
  function parseSigner(value) {
179
328
  if (!value || value === "readonly") {
180
329
  return { type: "readonly" };
@@ -205,7 +354,10 @@ function parseSigner(value) {
205
354
  if (value.startsWith("remote:")) {
206
355
  return parseSignerRemoteSpec(value);
207
356
  }
208
- throw new errors_1.CliError("INVALID_SIGNER_SPEC", `Unsupported signer '${value}'. Use readonly, env:<ENV_VAR>, keychain:<id>, ledger[:path|address|bridgeEnv], or remote:<url|address|authEnv>.`, 2);
357
+ if (value === "bankr" || value.startsWith("bankr:")) {
358
+ return parseSignerBankrSpec(value);
359
+ }
360
+ throw new errors_1.CliError("INVALID_SIGNER_SPEC", `Unsupported signer '${value}'. Use readonly, env:<ENV_VAR>, keychain:<id>, ledger[:path|address|bridgeEnv], remote:<url|address|authEnv>, or bankr[:address|apiKeyEnv|apiUrl].`, 2);
209
361
  }
210
362
  async function resolveSignerRuntime(signer, publicClient, rpcUrl, chain, customHome) {
211
363
  if (signer.type === "readonly") {
@@ -307,6 +459,44 @@ async function resolveSignerRuntime(signer, publicClient, rpcUrl, chain, customH
307
459
  },
308
460
  };
309
461
  }
462
+ if (signer.type === "bankr") {
463
+ const apiUrl = resolveBankrApiUrl(signer);
464
+ const headers = buildBankrHeaders(signer);
465
+ const address = await resolveBankrAddress(signer, headers);
466
+ const summary = await resolveBalanceSummary("bankr", publicClient, address);
467
+ return {
468
+ summary,
469
+ sendTransaction: async (request) => {
470
+ const payload = {
471
+ transaction: {
472
+ chainId: request.chain.id,
473
+ from: address,
474
+ to: request.to,
475
+ data: request.data,
476
+ value: request.value?.toString() || "0",
477
+ gas: request.gas.toString(),
478
+ nonce: request.nonce,
479
+ maxFeePerGas: request.maxFeePerGas?.toString(),
480
+ maxPriorityFeePerGas: request.maxPriorityFeePerGas?.toString(),
481
+ },
482
+ waitForConfirmation: false,
483
+ description: "Submitted via aavegotchi-cli",
484
+ };
485
+ const response = await fetchBankrJson(addDefaultPaths(apiUrl, BANKR_AGENT_SUBMIT_PATH), {
486
+ method: "POST",
487
+ headers,
488
+ body: JSON.stringify(payload),
489
+ });
490
+ const txHash = parseBankrSubmitHash(response);
491
+ if (txHash) {
492
+ return txHash;
493
+ }
494
+ throw new errors_1.CliError("BANKR_API_PROTOCOL_ERROR", "Bankr submit response missing transaction hash.", 2, {
495
+ response,
496
+ });
497
+ },
498
+ };
499
+ }
310
500
  const bridgeEnvVar = signer.bridgeCommandEnvVar || "AGCLI_LEDGER_BRIDGE_CMD";
311
501
  const bridgeCommand = process.env[bridgeEnvVar];
312
502
  if (!bridgeCommand) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aavegotchi-cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Agent-first CLI for automating Aavegotchi app and onchain workflows",
5
5
  "license": "MIT",
6
6
  "repository": {