@aixyz/cli 0.8.0 → 0.10.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/bin.ts CHANGED
@@ -1,238 +1,23 @@
1
1
  #!/usr/bin/env bun
2
- import { program } from "commander";
3
- import { build } from "./build";
4
- import { dev } from "./dev";
5
- import { register } from "./register/register";
6
- import { setAgentUri } from "./register/set-agent-uri";
7
- import { CliError } from "./register/utils";
2
+ import { Command } from "commander";
3
+ import { devCommand } from "./dev";
4
+ import { buildCommand } from "./build";
5
+ import { erc8004Command } from "./register";
8
6
  import pkg from "./package.json";
9
7
 
10
- function handleAction(
11
- action: (options: Record<string, unknown>) => Promise<void>,
12
- ): (options: Record<string, unknown>) => Promise<void> {
13
- return async (options) => {
14
- try {
15
- await action(options);
16
- } catch (error) {
17
- if (error instanceof CliError) {
18
- console.error(`Error: ${error.message}`);
19
- process.exit(1);
20
- }
21
- if (error instanceof Error && error.name === "ExitPromptError") {
22
- process.exit(130);
23
- }
24
- console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
25
- process.exit(1);
26
- }
27
- };
8
+ const cli = new Command();
9
+ cli.name("aixyz").description("CLI for building and deploying aixyz agents").version(pkg.version);
10
+
11
+ cli.addCommand(devCommand);
12
+ cli.addCommand(buildCommand);
13
+ cli.addCommand(erc8004Command);
14
+
15
+ try {
16
+ await cli.parseAsync();
17
+ } catch (error) {
18
+ if (error instanceof Error && error.name === "ExitPromptError") {
19
+ process.exit(130);
20
+ }
21
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
22
+ process.exit(1);
28
23
  }
29
-
30
- program.name("aixyz").description("CLI for building and deploying aixyz agents").version(pkg.version);
31
-
32
- program
33
- .command("dev")
34
- .description("Start a local development server")
35
- .option("-p, --port <port>", "Port to listen on", "3000")
36
- .action(handleAction(dev));
37
-
38
- program
39
- .command("build")
40
- .description("Build the aixyz agent")
41
- .option("--output <type>", "Output format: 'standalone' or 'vercel'")
42
- .addHelpText(
43
- "after",
44
- `
45
- Details:
46
- Bundles your aixyz agent for deployment.
47
-
48
- Default behavior (auto-detected):
49
- Bundles into a single executable file for Standalone at ./.aixyz/output/server.js
50
-
51
- With --output vercel or VERCEL=1 env:
52
- Generates Vercel Build Output API v3 structure at .vercel/output/
53
- (Automatically detected when deploying to Vercel)
54
-
55
- The build process:
56
- 1. Loads aixyz.config.ts from the current directory
57
- 2. Detects entrypoint (app/server.ts or auto-generates from app/agent.ts + app/tools/)
58
- 3. Bundles the application
59
- 4. Copies static assets from public/ (if present)
60
-
61
- Prerequisites:
62
- - An aixyz.config.ts with a default export
63
- - An entrypoint at app/server.ts, or app/agent.ts + app/tools/ for auto-generation
64
-
65
- Examples:
66
- $ aixyz build # Build standalone (default)
67
- $ aixyz build --output standalone # Build standalone explicitly
68
- $ aixyz build --output vercel # Build for Vercel deployment
69
- $ VERCEL=1 aixyz build # Auto-detected Vercel build`,
70
- )
71
- .action(handleAction(build));
72
-
73
- const erc8004 = program.command("erc8004").description("ERC-8004 IdentityRegistry operations");
74
-
75
- erc8004
76
- .command("register")
77
- .description("Register a new agent to the ERC-8004 IdentityRegistry")
78
- .option("--uri <uri>", "Agent metadata URI or path to .json file (converts to base64 data URI)")
79
- .option("--chain <chain>", "Target chain (mainnet, sepolia, base-sepolia, localhost)")
80
- .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
81
- .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
82
- .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
83
- .option("--browser", "Use browser extension wallet (any extension)")
84
- .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
85
- .option("--out-dir <path>", "Write deployment result as JSON to the given directory")
86
- .addHelpText(
87
- "after",
88
- `
89
- Option Details:
90
- --uri <uri>
91
- Agent metadata as a URI or local file path. Accepts http://, https://,
92
- ipfs://, and data: URIs directly.
93
- If a .json file path is given, it is read and converted to a base64 data URI automatically.
94
- Otherwise, the URI is used as-is and the validity of the URI is not checked.
95
- If omitted, the agent is registered without metadata.
96
-
97
- --chain <chain>
98
- Target chain for registration. Supported values:
99
- mainnet Ethereum mainnet (chain ID 1)
100
- sepolia Ethereum Sepolia testnet (chain ID 11155111)
101
- base-sepolia Base Sepolia testnet (chain ID 84532)
102
- localhost Local Foundry/Anvil node (chain ID 31337)
103
- If omitted, you will be prompted to select a chain interactively.
104
- Each chain has a default RPC endpoint unless overridden with --rpc-url.
105
-
106
- --rpc-url <url>
107
- Custom RPC endpoint URL. Overrides the default RPC for the selected
108
- chain. Cannot be used with --browser since the browser wallet manages
109
- its own RPC connection.
110
-
111
- --registry <address>
112
- Contract address of the ERC-8004 IdentityRegistry. Only required for
113
- localhost, where there is no default deployment. For mainnet, sepolia,
114
- and base-sepolia the canonical registry address is used automatically.
115
-
116
- --keystore <path>
117
- Path to an Ethereum keystore (V3) JSON file. You will be prompted for
118
- the keystore password to decrypt the private key for signing.
119
-
120
- --browser
121
- Opens a local page in your default browser for signing with any
122
- EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
123
- The wallet handles both signing and broadcasting the transaction.
124
- Cannot be combined with --rpc-url.
125
-
126
- --broadcast
127
- Sign and broadcast the transaction on-chain. Without this flag the
128
- command performs a dry-run: it encodes the transaction and prints
129
- its details but does not interact with any wallet or send anything
130
- to the network.
131
-
132
- --out-dir <path>
133
- Directory to write the deployment result as a JSON file. The file
134
- is named registration-<chainId>-<timestamp>.json.
135
-
136
- Environment Variables:
137
- PRIVATE_KEY Private key (hex, with or without 0x prefix) used for
138
- signing. Detected automatically if set. Not recommended
139
- for interactive use as the key may appear in shell history.
140
-
141
- Examples:
142
- # Dry-run (default) — shows encoded transaction, no wallet needed
143
- $ aixyz erc8004 register --uri "./metadata.json" --chain sepolia
144
-
145
- # Sign and broadcast
146
- $ aixyz erc8004 register --uri "./metadata.json" --chain sepolia --keystore ~/.foundry/keystores/default --broadcast
147
- $ PRIVATE_KEY=0x... aixyz erc8004 register --chain sepolia --broadcast
148
- $ aixyz erc8004 register --chain localhost --registry 0x5FbDB2315678afecb367f032d93F642f64180aa3 --uri "./metadata.json" --broadcast
149
- $ aixyz erc8004 register --uri "./metadata.json" --chain sepolia --browser --broadcast`,
150
- )
151
- .action(handleAction(register));
152
-
153
- erc8004
154
- .command("set-agent-uri")
155
- .description("Update the metadata URI of a registered agent")
156
- .option("--agent-id <id>", "Agent ID (token ID) to update")
157
- .option("--uri <uri>", "New agent metadata URI or path to .json file")
158
- .option("--chain <chain>", "Target chain (mainnet, sepolia, base-sepolia, localhost)")
159
- .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
160
- .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
161
- .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
162
- .option("--browser", "Use browser extension wallet (any extension)")
163
- .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
164
- .option("--out-dir <path>", "Write result as JSON to the given directory")
165
- .addHelpText(
166
- "after",
167
- `
168
- Option Details:
169
- --agent-id <id>
170
- The token ID of the agent whose URI you want to update.
171
- Must be a non-negative integer. Only the agent owner, an approved
172
- address, or an operator can update the URI.
173
- If omitted, you will be prompted to enter the agent ID interactively.
174
-
175
- --uri <uri>
176
- New agent metadata as a URI or local file path. Accepts http://, https://,
177
- ipfs://, and data: URIs directly.
178
- If a .json file path is given, it is read and converted to a base64 data URI automatically.
179
- Otherwise, the URI is used as-is and the validity of the URI is not checked.
180
- If omitted, you will be prompted to enter the URI interactively.
181
-
182
- --chain <chain>
183
- Target chain. Supported values:
184
- mainnet Ethereum mainnet (chain ID 1)
185
- sepolia Ethereum Sepolia testnet (chain ID 11155111)
186
- base-sepolia Base Sepolia testnet (chain ID 84532)
187
- localhost Local Foundry/Anvil node (chain ID 31337)
188
- If omitted, you will be prompted to select a chain interactively.
189
- Each chain has a default RPC endpoint unless overridden with --rpc-url.
190
-
191
- --rpc-url <url>
192
- Custom RPC endpoint URL. Overrides the default RPC for the selected
193
- chain. Cannot be used with --browser since the browser wallet manages
194
- its own RPC connection.
195
-
196
- --registry <address>
197
- Contract address of the ERC-8004 IdentityRegistry. Only required for
198
- localhost, where there is no default deployment. For mainnet, sepolia,
199
- and base-sepolia the canonical registry address is used automatically.
200
-
201
- --keystore <path>
202
- Path to an Ethereum keystore (V3) JSON file. You will be prompted for
203
- the keystore password to decrypt the private key for signing.
204
-
205
- --browser
206
- Opens a local page in your default browser for signing with any
207
- EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
208
- The wallet handles both signing and broadcasting the transaction.
209
- Cannot be combined with --rpc-url.
210
-
211
- --broadcast
212
- Sign and broadcast the transaction on-chain. Without this flag the
213
- command performs a dry-run: it encodes the transaction and prints
214
- its details but does not interact with any wallet or send anything
215
- to the network.
216
-
217
- --out-dir <path>
218
- Directory to write the result as a JSON file. The file
219
- is named set-agent-uri-<chainId>-<timestamp>.json.
220
-
221
- Environment Variables:
222
- PRIVATE_KEY Private key (hex, with or without 0x prefix) used for
223
- signing. Detected automatically if set. Not recommended
224
- for interactive use as the key may appear in shell history.
225
-
226
- Examples:
227
- # Dry-run (default) — shows encoded transaction, no wallet needed
228
- $ aixyz erc8004 set-agent-uri --agent-id 1 --uri "./metadata.json" --chain sepolia
229
-
230
- # Sign and broadcast
231
- $ aixyz erc8004 set-agent-uri --agent-id 1 --uri "./metadata.json" --chain sepolia --keystore ~/.foundry/keystores/default --broadcast
232
- $ PRIVATE_KEY=0x... aixyz erc8004 set-agent-uri --agent-id 42 --uri "https://example.com/agent.json" --chain sepolia --broadcast
233
- $ aixyz erc8004 set-agent-uri --agent-id 1 --uri "./metadata.json" --chain localhost --registry 0x5FbDB2315678afecb367f032d93F642f64180aa3 --broadcast
234
- $ aixyz erc8004 set-agent-uri --agent-id 1 --uri "./metadata.json" --chain sepolia --browser --broadcast`,
235
- )
236
- .action(handleAction(setAgentUri));
237
-
238
- program.parse();
@@ -37,7 +37,7 @@ export function AixyzConfigPlugin(): BunPlugin {
37
37
  return {
38
38
  name: "aixyz-config",
39
39
  setup(build) {
40
- build.onLoad({ filter: /aixyz\/config\.ts$/ }, () => ({
40
+ build.onLoad({ filter: /aixyz[-/]config\/index\.ts$/ }, () => ({
41
41
  contents: `
42
42
  const config = ${JSON.stringify(materialized)};
43
43
  export function getAixyzConfig() {
@@ -127,6 +127,16 @@ function generateServer(appDir: string, entrypointDir: string): string {
127
127
  body.push("await mcp.connect();");
128
128
  }
129
129
 
130
+ // If app/erc-8004.ts exists, auto-register ERC-8004 endpoint
131
+ const hasErc8004 = existsSync(resolve(appDir, "erc-8004.ts"));
132
+ if (hasErc8004) {
133
+ imports.push('import { useERC8004 } from "aixyz/server/adapters/erc-8004";');
134
+ imports.push(`import * as erc8004 from "${importPrefix}/erc-8004";`);
135
+ body.push(
136
+ `useERC8004(server, { default: erc8004.default, options: { mcp: ${tools.length > 0}, a2a: ${hasAgent} } });`,
137
+ );
138
+ }
139
+
130
140
  body.push("export default server;");
131
141
 
132
142
  return [...imports, "", ...body].join("\n");
package/build/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { resolve } from "path";
2
2
  import { existsSync, mkdirSync, cpSync, rmSync } from "fs";
3
+ import { Command } from "commander";
3
4
  import { AixyzConfigPlugin } from "./AixyzConfigPlugin";
4
5
  import { AixyzServerPlugin, getEntrypointMayGenerate } from "./AixyzServerPlugin";
5
6
  import { findIconFile, copyAgentIcon, generateFavicon } from "./icons";
@@ -11,7 +12,41 @@ interface BuildOptions {
11
12
  output?: string;
12
13
  }
13
14
 
14
- export async function build(options: BuildOptions = {}): Promise<void> {
15
+ export const buildCommand = new Command("build")
16
+ .description("Build the aixyz agent")
17
+ .option("--output <type>", "Output format: 'standalone' or 'vercel'")
18
+ .addHelpText(
19
+ "after",
20
+ `
21
+ Details:
22
+ Bundles your aixyz agent for deployment.
23
+
24
+ Default behavior (auto-detected):
25
+ Bundles into a single executable file for Standalone at ./.aixyz/output/server.js
26
+
27
+ With --output vercel or VERCEL=1 env:
28
+ Generates Vercel Build Output API v3 structure at .vercel/output/
29
+ (Automatically detected when deploying to Vercel)
30
+
31
+ The build process:
32
+ 1. Loads aixyz.config.ts from the current directory
33
+ 2. Detects entrypoint (app/server.ts or auto-generates from app/agent.ts + app/tools/)
34
+ 3. Bundles the application
35
+ 4. Copies static assets from public/ (if present)
36
+
37
+ Prerequisites:
38
+ - An aixyz.config.ts with a default export
39
+ - An entrypoint at app/server.ts, or app/agent.ts + app/tools/ for auto-generation
40
+
41
+ Examples:
42
+ $ aixyz build # Build standalone (default)
43
+ $ aixyz build --output standalone # Build standalone explicitly
44
+ $ aixyz build --output vercel # Build for Vercel deployment
45
+ $ VERCEL=1 aixyz build # Auto-detected Vercel build`,
46
+ )
47
+ .action(action);
48
+
49
+ async function action(options: BuildOptions = {}): Promise<void> {
15
50
  const cwd = process.cwd();
16
51
  loadEnvConfig(cwd, false);
17
52
  const entrypoint = getEntrypointMayGenerate(cwd, "build");
package/dev/index.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import { resolve, relative } from "path";
2
2
  import { existsSync, watch } from "fs";
3
3
  import { loadEnvConfig } from "@next/env";
4
+ import { Command } from "commander";
4
5
  import { getEntrypointMayGenerate } from "../build/AixyzServerPlugin";
5
6
  import pkg from "../package.json";
6
7
 
7
- export async function dev(options: { port?: string }): Promise<void> {
8
+ export const devCommand = new Command("dev")
9
+ .description("Start a local development server")
10
+ .option("-p, --port <port>", "Port to listen on", "3000")
11
+ .action(action);
12
+
13
+ async function action(options: { port?: string }): Promise<void> {
8
14
  const cwd = process.cwd();
9
15
 
10
16
  // Load environment config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aixyz/cli",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Payment-native SDK for AI Agent",
5
5
  "keywords": [
6
6
  "ai",
@@ -27,8 +27,8 @@
27
27
  "bin.ts"
28
28
  ],
29
29
  "dependencies": {
30
- "@aixyz/config": "0.8.0",
31
- "@aixyz/erc-8004": "0.8.0",
30
+ "@aixyz/config": "0.10.0",
31
+ "@aixyz/erc-8004": "0.10.0",
32
32
  "@inquirer/prompts": "^8.3.0",
33
33
  "@next/env": "^16.1.6",
34
34
  "boxen": "^8.0.1",
package/register/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { Command } from "commander";
2
+ import { register } from "./register";
3
+ import { update } from "./update";
1
4
  import type { WalletOptions } from "./wallet";
2
5
 
3
6
  export interface BaseOptions extends WalletOptions {
@@ -6,3 +9,135 @@ export interface BaseOptions extends WalletOptions {
6
9
  registry?: string;
7
10
  outDir?: string;
8
11
  }
12
+
13
+ export const erc8004Command = new Command("erc-8004").description("ERC-8004 IdentityRegistry operations");
14
+
15
+ erc8004Command
16
+ .command("register")
17
+ .description("Register a new agent to the ERC-8004 IdentityRegistry")
18
+ .option("--url <url>", "Agent deployment URL (e.g., https://my-agent.example.com)")
19
+ .option("--chain <chain>", "Target chain (mainnet, sepolia, base-sepolia, localhost)")
20
+ .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
21
+ .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
22
+ .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
23
+ .option("--browser", "Use browser extension wallet (any extension)")
24
+ .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
25
+ .option("--out-dir <path>", "Write deployment result as JSON to the given directory")
26
+ .addHelpText(
27
+ "after",
28
+ `
29
+ Option Details:
30
+ --url <url>
31
+ Agent deployment URL (e.g., https://my-agent.example.com).
32
+ The registration URI will be derived as <url>/_aixyz/erc-8004.json.
33
+ If omitted, you will be prompted to enter the URL interactively.
34
+
35
+ --chain <chain>
36
+ Target chain for registration. Supported values:
37
+ mainnet Ethereum mainnet (chain ID 1)
38
+ sepolia Ethereum Sepolia testnet (chain ID 11155111)
39
+ base-sepolia Base Sepolia testnet (chain ID 84532)
40
+ localhost Local Foundry/Anvil node (chain ID 31337)
41
+ If omitted, you will be prompted to select a chain interactively.
42
+
43
+ --rpc-url <url>
44
+ Custom RPC endpoint URL. Overrides the default RPC for the selected
45
+ chain. Cannot be used with --browser since the browser wallet manages
46
+ its own RPC connection.
47
+
48
+ --registry <address>
49
+ Contract address of the ERC-8004 IdentityRegistry. Only required for
50
+ localhost, where there is no default deployment.
51
+
52
+ --keystore <path>
53
+ Path to an Ethereum keystore (V3) JSON file. You will be prompted for
54
+ the keystore password to decrypt the private key for signing.
55
+
56
+ --browser
57
+ Opens a local page in your default browser for signing with any
58
+ EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
59
+
60
+ --broadcast
61
+ Sign and broadcast the transaction on-chain. Without this flag the
62
+ command performs a dry-run.
63
+
64
+ --out-dir <path>
65
+ Directory to write the deployment result as a JSON file.
66
+
67
+ Behavior:
68
+ If app/erc-8004.ts does not exist, you will be prompted to create it
69
+ (selecting supported trust mechanisms). After a successful on-chain
70
+ registration, the new registration entry is written back to app/erc-8004.ts.
71
+
72
+ Environment Variables:
73
+ PRIVATE_KEY Private key (hex, with or without 0x prefix) used for signing.
74
+
75
+ Examples:
76
+ # Dry-run (default)
77
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia
78
+
79
+ # Sign and broadcast
80
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --keystore ~/.foundry/keystores/default --broadcast
81
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --browser --broadcast`,
82
+ )
83
+ .action(register);
84
+
85
+ erc8004Command
86
+ .command("update")
87
+ .description("Update the metadata URI of a registered agent")
88
+ .option("--url <url>", "New agent deployment URL (e.g., https://my-agent.example.com)")
89
+ .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
90
+ .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
91
+ .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
92
+ .option("--browser", "Use browser extension wallet (any extension)")
93
+ .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
94
+ .option("--out-dir <path>", "Write result as JSON to the given directory")
95
+ .addHelpText(
96
+ "after",
97
+ `
98
+ Option Details:
99
+ --url <url>
100
+ New agent deployment URL (e.g., https://my-agent.example.com).
101
+ The URI will be derived as <url>/_aixyz/erc-8004.json.
102
+ If omitted, you will be prompted to enter the URL interactively.
103
+
104
+ --rpc-url <url>
105
+ Custom RPC endpoint URL. Overrides the default RPC for the selected
106
+ chain. Cannot be used with --browser.
107
+
108
+ --registry <address>
109
+ Contract address of the ERC-8004 IdentityRegistry. Only required for
110
+ localhost, where there is no default deployment.
111
+
112
+ --keystore <path>
113
+ Path to an Ethereum keystore (V3) JSON file.
114
+
115
+ --browser
116
+ Opens a local page in your default browser for signing with any
117
+ EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
118
+
119
+ --broadcast
120
+ Sign and broadcast the transaction on-chain. Without this flag the
121
+ command performs a dry-run.
122
+
123
+ --out-dir <path>
124
+ Directory to write the result as a JSON file.
125
+
126
+ Behavior:
127
+ Reads existing registrations from app/erc-8004.ts. If there is one
128
+ registration, confirms it. If multiple, prompts you to select which
129
+ one to update. The chain and registry address are derived from the
130
+ selected registration's agentRegistry field.
131
+
132
+ Environment Variables:
133
+ PRIVATE_KEY Private key (hex, with or without 0x prefix) used for signing.
134
+
135
+ Examples:
136
+ # Dry-run (default)
137
+ $ aixyz erc-8004 update --url "https://new-domain.example.com"
138
+
139
+ # Sign and broadcast
140
+ $ aixyz erc-8004 update --url "https://new-domain.example.com" --keystore ~/.foundry/keystores/default --broadcast
141
+ $ aixyz erc-8004 update --url "https://new-domain.example.com" --browser --broadcast`,
142
+ )
143
+ .action(update);
@@ -1,6 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { CHAIN_ID, getIdentityRegistryAddress } from "@aixyz/erc-8004";
3
- import { register } from "./register";
4
3
 
5
4
  describe("register command chain configuration", () => {
6
5
  test("sepolia chain ID is correct", () => {
@@ -64,12 +63,4 @@ describe("register command validation", () => {
64
63
  };
65
64
  expect(CHAINS["mainnet"]).toBeUndefined();
66
65
  });
67
-
68
- test("localhost requires --registry flag", async () => {
69
- await expect(register({ chain: "localhost" })).rejects.toThrow("--registry is required for localhost");
70
- });
71
-
72
- test("dry-run completes without wallet interaction when --broadcast is not set", async () => {
73
- await expect(register({ chain: "sepolia", uri: "https://example.com/agent.json" })).resolves.toBeUndefined();
74
- });
75
66
  });
@@ -1,8 +1,7 @@
1
1
  import { encodeFunctionData, formatEther, parseEventLogs, type Chain, type Log } from "viem";
2
2
  import { IdentityRegistryAbi } from "@aixyz/erc-8004";
3
- import { selectWalletMethod, type WalletOptions } from "./wallet";
3
+ import { selectWalletMethod } from "./wallet";
4
4
  import { signTransaction } from "./wallet/sign";
5
- import { resolveUri } from "./utils";
6
5
  import {
7
6
  resolveChainConfig,
8
7
  selectChain,
@@ -12,30 +11,51 @@ import {
12
11
  } from "./utils/chain";
13
12
  import { writeResultJson } from "./utils/result";
14
13
  import { label, truncateUri, broadcastAndConfirm, logSignResult } from "./utils/transaction";
14
+ import { promptAgentUrl, promptSupportedTrust, deriveAgentUri } from "./utils/prompt";
15
+ import { hasErc8004File, createErc8004File, writeRegistrationEntry } from "./utils/erc8004-file";
16
+ import { confirm } from "@inquirer/prompts";
15
17
  import chalk from "chalk";
16
18
  import boxen from "boxen";
17
19
  import type { BaseOptions } from "./index";
18
20
 
19
21
  export interface RegisterOptions extends BaseOptions {
20
- uri?: string;
22
+ url?: string;
21
23
  chain?: string;
22
24
  }
23
25
 
24
26
  export async function register(options: RegisterOptions): Promise<void> {
25
- const chainName = options.chain ?? (await selectChain());
26
- const chainConfig = resolveChainConfig(chainName);
27
+ // Step 1: Ensure app/erc-8004.ts exists
28
+ if (!hasErc8004File()) {
29
+ console.log(chalk.yellow("No app/erc-8004.ts found. Let's create one."));
30
+ console.log("");
31
+ const supportedTrust = await promptSupportedTrust();
32
+ createErc8004File(supportedTrust);
33
+ console.log(chalk.green("Created app/erc-8004.ts"));
34
+ console.log("");
35
+ }
36
+
37
+ // Step 2: Get agent URL and derive URI
38
+ const agentUrl = options.url ?? (await promptAgentUrl());
39
+ const resolvedUri = deriveAgentUri(agentUrl);
27
40
 
28
- const resolvedUri = options.uri ? resolveUri(options.uri) : undefined;
29
- if (options.uri && resolvedUri !== options.uri) {
30
- console.log(`Resolved ${options.uri} to data URI (${resolvedUri!.length} chars)`);
41
+ const yes = await confirm({
42
+ message: `Will register URI as: ${chalk.cyan(resolvedUri)} — confirm?`,
43
+ default: true,
44
+ });
45
+ if (!yes) {
46
+ throw new Error("Aborted.");
31
47
  }
32
48
 
49
+ // Step 3: Select chain
50
+ const chainName = options.chain ?? (await selectChain());
51
+ const chainConfig = resolveChainConfig(chainName);
33
52
  const registryAddress = resolveRegistryAddress(chainName, chainConfig.chainId, options.registry);
34
53
 
54
+ // Step 4: Encode transaction
35
55
  const data = encodeFunctionData({
36
56
  abi: IdentityRegistryAbi,
37
57
  functionName: "register",
38
- args: resolvedUri ? [resolvedUri] : [],
58
+ args: [resolvedUri],
39
59
  });
40
60
 
41
61
  const printTxDetails = (header: string) => {
@@ -44,10 +64,8 @@ export async function register(options: RegisterOptions): Promise<void> {
44
64
  console.log(` ${label("To")}${registryAddress}`);
45
65
  console.log(` ${label("Data")}${data.slice(0, 10)}${chalk.dim("\u2026" + (data.length - 2) / 2 + " bytes")}`);
46
66
  console.log(` ${label("Chain")}${chainName}`);
47
- console.log(` ${label("Function")}${resolvedUri ? "register(string memory agentURI)" : "register()"}`);
48
- if (resolvedUri) {
49
- console.log(` ${label("URI")}${truncateUri(resolvedUri)}`);
50
- }
67
+ console.log(` ${label("Function")}register(string memory agentURI)`);
68
+ console.log(` ${label("URI")}${truncateUri(resolvedUri)}`);
51
69
  console.log("");
52
70
  };
53
71
 
@@ -73,7 +91,7 @@ export async function register(options: RegisterOptions): Promise<void> {
73
91
  chain: chainConfig.chain,
74
92
  rpcUrl: options.rpcUrl,
75
93
  options: {
76
- browser: { chainId: chainConfig.chainId, chainName, uri: resolvedUri },
94
+ browser: { chainId: chainConfig.chainId, chainName, uri: resolvedUri, mode: "register" },
77
95
  },
78
96
  });
79
97
  logSignResult(walletMethod.type, result);
@@ -86,6 +104,14 @@ export async function register(options: RegisterOptions): Promise<void> {
86
104
 
87
105
  const resultData = printResult(receipt, timestamp, chainConfig.chain, chainConfig.chainId, hash);
88
106
 
107
+ // Step 5: Write registration entry back to app/erc-8004.ts
108
+ if (resultData.agentId !== undefined) {
109
+ const agentRegistry = `eip155:${chainConfig.chainId}:${registryAddress}`;
110
+ writeRegistrationEntry({ agentId: Number(resultData.agentId), agentRegistry });
111
+ console.log("");
112
+ console.log(chalk.green(`Updated app/erc-8004.ts with registration (agentId: ${resultData.agentId})`));
113
+ }
114
+
89
115
  if (options.outDir) {
90
116
  writeResultJson(options.outDir, "registration", resultData);
91
117
  }