8004agent-cli 1.0.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/cli.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ import dotenv from 'dotenv';
4
+ dotenv.config();
5
+
6
+ import { program } from 'commander';
7
+ import { init } from '../src/commands/init.js';
8
+ import { mainMenu } from '../src/commands/menu.js';
9
+
10
+ program
11
+ .name('8004agent')
12
+ .version('1.0.0')
13
+ .description('8004agent.network CLI - Create and register AI agents on-chain');
14
+
15
+ program
16
+ .command('init')
17
+ .description('Initialize the CLI with your owner address')
18
+ .action(init);
19
+
20
+ program
21
+ .action(mainMenu);
22
+
23
+ program.parse(process.argv);
package/config.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "ownerAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
3
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "8004agent-cli",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "bin": {
6
+ "8004agent": "./bin/cli.js"
7
+ },
8
+ "type": "module",
9
+ "scripts": {
10
+ "test": "vitest run",
11
+ "test:watch": "vitest"
12
+ },
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "description": "8004agent.network CLI - Create and register AI agents on-chain",
17
+ "dependencies": {
18
+ "@google/generative-ai": "^0.24.1",
19
+ "@privy-io/server-auth": "^1.32.5",
20
+ "axios": "^1.13.4",
21
+ "chalk": "^5.6.2",
22
+ "commander": "^14.0.3",
23
+ "dotenv": "^17.2.4",
24
+ "ethers": "^6.16.0",
25
+ "facinet": "^2.3.0",
26
+ "figlet": "^1.10.0",
27
+ "gradient-string": "^3.0.0",
28
+ "inquirer": "^12.11.1",
29
+ "ora": "^9.3.0"
30
+ },
31
+ "devDependencies": {
32
+ "vitest": "^4.0.18"
33
+ }
34
+ }
@@ -0,0 +1,424 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import { ethers } from 'ethers';
4
+ import { CONTRACTS, PRICING, RELAYER_PRIVATE_KEY } from '../lib/constants.js';
5
+ import { getConfig, saveConfig } from '../lib/config.js';
6
+ import { payWithFacinet, buildGaslessPaymentData, submitGaslessPayment } from '../lib/payment.js';
7
+ import { createPrivyWalletForAgent } from '../lib/privy.js';
8
+ import { mineSaltFor8004, deployAgentWallet } from '../lib/agentWallet.js';
9
+ import { useBackend, apiRegister, apiDeployAgentWallet, apiSetAgentWallet } from '../lib/api.js';
10
+ import { IdentityRegistryABI } from '../lib/abi.js';
11
+ import { getProvider, getSigner } from '../lib/web3.js';
12
+ import { spinner, success, error, info } from '../lib/ui.js';
13
+
14
+ export const createAgent = async () => {
15
+ const config = getConfig();
16
+
17
+ // ---- 1. Collect Agent Details ----
18
+ const answers = await inquirer.prompt([
19
+ {
20
+ type: 'input',
21
+ name: 'name',
22
+ message: 'Agent Name:',
23
+ validate: (input) => (input ? true : 'Name is required'),
24
+ },
25
+ {
26
+ type: 'input',
27
+ name: 'personality',
28
+ message: 'Personality/System Prompt:',
29
+ },
30
+ {
31
+ type: 'input',
32
+ name: 'keyword',
33
+ message: 'Primary Capability (e.g., defi yield, image gen):',
34
+ default: 'general assistance',
35
+ },
36
+ {
37
+ type: 'input',
38
+ name: 'imageUrl',
39
+ message: 'Agent Image URL (optional):',
40
+ default: '',
41
+ },
42
+ {
43
+ type: 'input',
44
+ name: 'details',
45
+ message: 'Additional Details/Requirements (optional):',
46
+ default: '',
47
+ },
48
+ ]);
49
+
50
+ // ---- 2. Select Networks ----
51
+ const { networks } = await inquirer.prompt([
52
+ {
53
+ type: 'checkbox',
54
+ name: 'networks',
55
+ message: 'Which networks would you like to register on?',
56
+ choices: Object.keys(CONTRACTS).map((key) => ({
57
+ name: CONTRACTS[key].name,
58
+ value: key,
59
+ })),
60
+ validate: (input) =>
61
+ input.length > 0 ? true : 'Select at least one network.',
62
+ },
63
+ ]);
64
+
65
+ // ---- 3. Signing Method ----
66
+ const { signingMethod } = await inquirer.prompt([
67
+ {
68
+ type: 'list',
69
+ name: 'signingMethod',
70
+ message: 'How would you like to handle payment?',
71
+ choices: [
72
+ {
73
+ name: 'Enter Private Key (fully automated payment + registration)',
74
+ value: 'privateKey',
75
+ },
76
+ {
77
+ name: 'Sign Message (gasless - sign in your wallet, no private key needed)',
78
+ value: 'signMessage',
79
+ },
80
+ ],
81
+ },
82
+ ]);
83
+
84
+ let signerKey = null;
85
+ let payerAddress = null;
86
+ if (signingMethod === 'privateKey') {
87
+ const { key } = await inquirer.prompt([
88
+ {
89
+ type: 'password',
90
+ name: 'key',
91
+ message: 'Enter your Private Key:',
92
+ mask: '*',
93
+ },
94
+ ]);
95
+ signerKey = key.trim();
96
+ } else {
97
+ const { walletAddr } = await inquirer.prompt([
98
+ {
99
+ type: 'input',
100
+ name: 'walletAddr',
101
+ message: 'Enter your wallet address (the address holding USDC):',
102
+ validate: (input) =>
103
+ ethers.isAddress(input) ? true : 'Please enter a valid Ethereum address.',
104
+ },
105
+ ]);
106
+ payerAddress = walletAddr.trim();
107
+ }
108
+
109
+ // ---- 4. AI Code Generation + Deployment ----
110
+ const genSpinner = spinner('Generating Agent Code using Gemini...').start();
111
+ const { generateAgentCode } = await import('../lib/ai.js');
112
+ const { deployToVercel } = await import('../lib/vercel.js');
113
+
114
+ const code = await generateAgentCode(
115
+ answers.name,
116
+ answers.personality,
117
+ answers.keyword,
118
+ answers.imageUrl,
119
+ answers.details
120
+ );
121
+ genSpinner.succeed('Agent Code Generated!');
122
+
123
+ const deploySpinner = spinner('Deploying agent to Vercel...').start();
124
+ const agentUrl = await deployToVercel(answers.name, code);
125
+ deploySpinner.succeed(`Agent deployed at: ${chalk.underline(agentUrl)}`);
126
+
127
+ // ---- 5. Payment ----
128
+ const primaryNetwork = networks[0];
129
+ const totalCost = PRICING.create * networks.length;
130
+ let paymentTxHash = null;
131
+
132
+ if (signingMethod === 'privateKey') {
133
+ const paySpinner = spinner(
134
+ `Processing gasless payment of $${totalCost.toFixed(2)} USDC via Facinet x402 on ${CONTRACTS[primaryNetwork].name}...`
135
+ ).start();
136
+
137
+ try {
138
+ const result = await payWithFacinet({
139
+ amount: totalCost.toString(),
140
+ privateKey: signerKey,
141
+ networkKey: primaryNetwork,
142
+ });
143
+ paymentTxHash = result.txHash;
144
+ paySpinner.succeed(`Payment confirmed! Tx: ${chalk.underline(paymentTxHash)}`);
145
+ } catch (e) {
146
+ paySpinner.fail(`Payment failed: ${e.message}`);
147
+ return;
148
+ }
149
+ } else {
150
+ // Gasless signature flow: build EIP-712 message, user signs, relayer submits
151
+ const buildSpinner = spinner(
152
+ `Building gasless payment authorization for $${totalCost.toFixed(2)} USDC on ${CONTRACTS[primaryNetwork].name}...`
153
+ ).start();
154
+
155
+ let paymentData;
156
+ try {
157
+ paymentData = await buildGaslessPaymentData(payerAddress, totalCost, primaryNetwork);
158
+ buildSpinner.succeed('Payment authorization ready!');
159
+ } catch (e) {
160
+ buildSpinner.fail(`Failed to build payment data: ${e.message}`);
161
+ return;
162
+ }
163
+
164
+ // Display the EIP-712 typed data for the user to sign
165
+ console.log('');
166
+ info('=== GASLESS PAYMENT - SIGN THIS MESSAGE ===');
167
+ console.log('');
168
+ console.log(chalk.yellow(' Sign the following EIP-712 typed data in your wallet.'));
169
+ console.log(chalk.yellow(' This authorizes a gasless USDC transfer (you pay NO gas).'));
170
+ console.log('');
171
+ console.log(chalk.white(JSON.stringify({
172
+ domain: paymentData.domain,
173
+ primaryType: paymentData.primaryType,
174
+ types: paymentData.types,
175
+ message: paymentData.message,
176
+ }, null, 2)));
177
+ console.log('');
178
+ info(' How to sign:');
179
+ info(' - MetaMask: Use eth_signTypedData_v4 via a dApp or browser console');
180
+ info(' - Foundry: cast wallet sign-typed-data ...');
181
+ info(' - Any EIP-712 compatible wallet or signing tool');
182
+ console.log('');
183
+
184
+ const { signature } = await inquirer.prompt([
185
+ {
186
+ type: 'input',
187
+ name: 'signature',
188
+ message: 'Paste your signature (0x...):',
189
+ validate: (input) =>
190
+ /^0x[0-9a-fA-F]{130}$/.test(input)
191
+ ? true
192
+ : 'Invalid signature (expected 0x + 130 hex chars = 65 bytes)',
193
+ },
194
+ ]);
195
+
196
+ const submitSpinner = spinner('Submitting gasless payment via relayer...').start();
197
+ try {
198
+ const result = await submitGaslessPayment(paymentData, signature, primaryNetwork);
199
+ paymentTxHash = result.txHash;
200
+ submitSpinner.succeed(`Payment confirmed! Tx: ${chalk.underline(paymentTxHash)}`);
201
+ } catch (e) {
202
+ submitSpinner.fail(`Gasless payment failed: ${e.message}`);
203
+ return;
204
+ }
205
+ }
206
+
207
+ // ---- 6. Registration Loop ----
208
+ const results = [];
209
+
210
+ for (const netKey of networks) {
211
+ const netConfig = CONTRACTS[netKey];
212
+ info(`\n--- Registering on ${netConfig.name} ---`);
213
+
214
+ const netResult = {
215
+ network: netConfig.name,
216
+ agentId: null,
217
+ registrationTx: null,
218
+ smartWallet: null,
219
+ agentWallet: null,
220
+ blockExplorer: netConfig.blockExplorer,
221
+ };
222
+
223
+ try {
224
+ // 6a. Register on-chain (backend relayer or local)
225
+ const registerSpinner = spinner('Registering agent on-chain...').start();
226
+ const hasRelayer = useBackend() || !!RELAYER_PRIVATE_KEY;
227
+ let agentId;
228
+ let receiptHash;
229
+
230
+ if (useBackend() && !signerKey) {
231
+ try {
232
+ registerSpinner.text = 'Registering via backend relayer...';
233
+ const result = await apiRegister(agentUrl, netKey, config?.ownerAddress);
234
+ agentId = result.agentId;
235
+ receiptHash = result.txHash;
236
+ registerSpinner.succeed(`Agent ID: ${agentId} | Tx: ${chalk.underline(receiptHash)}`);
237
+ } catch (e) {
238
+ registerSpinner.fail(`Registration failed: ${e.message}`);
239
+ results.push(netResult);
240
+ continue;
241
+ }
242
+ } else if (hasRelayer || signerKey) {
243
+ const provider = getProvider(netConfig.rpc);
244
+ const registrantKey = hasRelayer && !signerKey ? RELAYER_PRIVATE_KEY : signerKey;
245
+ const registrantAddress = new ethers.Wallet(registrantKey).address;
246
+ if (hasRelayer && !signerKey) registerSpinner.text = 'Registering via gasless relayer...';
247
+ else registerSpinner.text = 'Registering (you pay gas)...';
248
+
249
+ const signer = getSigner(registrantKey, provider);
250
+ const registry = new ethers.Contract(netConfig.identityRegistry, IdentityRegistryABI, signer);
251
+
252
+ const tx = await registry['register(string)'](agentUrl);
253
+ registerSpinner.text = `Tx sent: ${tx.hash}. Waiting for confirmation...`;
254
+ const receipt = await tx.wait();
255
+
256
+ for (const evLog of receipt.logs) {
257
+ try {
258
+ const parsed = registry.interface.parseLog(evLog);
259
+ if (parsed?.name === 'Registered') {
260
+ agentId = parsed.args.agentId.toString();
261
+ break;
262
+ }
263
+ } catch (e) { /* skip */ }
264
+ }
265
+
266
+ if (!agentId) {
267
+ registerSpinner.fail('Registration tx confirmed but could not parse Agent ID.');
268
+ results.push(netResult);
269
+ continue;
270
+ }
271
+
272
+ receiptHash = receipt.hash;
273
+ netResult.registrationTx = receiptHash;
274
+
275
+ if (hasRelayer && config?.ownerAddress && registrantAddress.toLowerCase() !== config.ownerAddress.toLowerCase()) {
276
+ registerSpinner.text = 'Transferring agent NFT to your wallet...';
277
+ const transferTx = await registry.transferFrom(registrantAddress, config.ownerAddress, agentId);
278
+ await transferTx.wait();
279
+ }
280
+
281
+ registerSpinner.succeed(`Agent ID: ${agentId} | Tx: ${chalk.underline(receiptHash)}`);
282
+ } else {
283
+ registerSpinner.fail('No relayer configured and no private key provided. Cannot register.');
284
+ results.push(netResult);
285
+ continue;
286
+ }
287
+
288
+ netResult.agentId = agentId;
289
+ netResult.registrationTx = netResult.registrationTx || receiptHash;
290
+
291
+ // 6b. Create Privy smart wallet
292
+ const privySpinner = spinner('Creating Privy smart wallet...').start();
293
+ try {
294
+ const privyResult = await createPrivyWalletForAgent(
295
+ agentId.toString(),
296
+ netKey
297
+ );
298
+ netResult.smartWallet =
299
+ privyResult.smartWallet || privyResult.embeddedWallet;
300
+ privySpinner.succeed(
301
+ `Smart Wallet: ${netResult.smartWallet || 'N/A'}`
302
+ );
303
+ } catch (e) {
304
+ privySpinner.warn(`Privy wallet skipped: ${e.message}`);
305
+ }
306
+
307
+ // 6c. Mine 0x8004 agent wallet + deploy
308
+ const mineSpinner = spinner(
309
+ 'Mining 0x8004-prefix agent wallet address...'
310
+ ).start();
311
+ const provider = getProvider(netConfig.rpc);
312
+ const registrantAddress = signerKey ? new ethers.Wallet(signerKey).address : (useBackend() ? config?.ownerAddress : new ethers.Wallet(RELAYER_PRIVATE_KEY).address);
313
+ try {
314
+ const ownerForWallet = config?.ownerAddress || registrantAddress;
315
+ const { salt, predictedAddress } = await mineSaltFor8004(
316
+ ownerForWallet,
317
+ netKey,
318
+ 50,
319
+ 2000,
320
+ (attempts) => {
321
+ mineSpinner.text = `Mining 0x8004 address... (${attempts} salts tried)`;
322
+ }
323
+ );
324
+ mineSpinner.succeed(`Found 0x8004 address: ${predictedAddress}`);
325
+
326
+ const deployWalletSpinner = spinner('Deploying AgentWallet contract...').start();
327
+ let walletAddress;
328
+ if (useBackend() && !signerKey) {
329
+ try {
330
+ const result = await apiDeployAgentWallet(ownerForWallet, salt, netKey);
331
+ walletAddress = result.walletAddress;
332
+ } catch (e) {
333
+ deployWalletSpinner.fail(`Deploy failed: ${e.message}`);
334
+ throw e;
335
+ }
336
+ } else {
337
+ const deployerKey = hasRelayer ? RELAYER_PRIVATE_KEY : signerKey;
338
+ const { walletAddress: w } = await deployAgentWallet(ownerForWallet, salt, netKey, deployerKey);
339
+ walletAddress = w;
340
+ }
341
+ netResult.agentWallet = walletAddress;
342
+ deployWalletSpinner.succeed(`Agent Wallet: ${walletAddress}`);
343
+
344
+ if (signerKey) {
345
+ const linkSpinner = spinner('Linking agent wallet to identity...').start();
346
+ try {
347
+ const deadline = Math.floor(Date.now() / 1000) + 3600;
348
+ const registryRead = new ethers.Contract(netConfig.identityRegistry, IdentityRegistryABI, provider);
349
+ const domainData = await registryRead.eip712Domain();
350
+ const domain = { name: domainData[1], version: domainData[2], chainId: domainData[3], verifyingContract: domainData[4] };
351
+ const types = {
352
+ SetAgentWallet: [
353
+ { name: 'agentId', type: 'uint256' },
354
+ { name: 'newWallet', type: 'address' },
355
+ { name: 'deadline', type: 'uint256' },
356
+ ],
357
+ };
358
+ const ownerWallet = new ethers.Wallet(signerKey);
359
+ const signature = await ownerWallet.signTypedData(domain, types, { agentId, newWallet: walletAddress, deadline });
360
+
361
+ if (useBackend()) {
362
+ await apiSetAgentWallet(agentId, walletAddress, deadline, signature, netKey);
363
+ } else {
364
+ const registryForLink = new ethers.Contract(netConfig.identityRegistry, IdentityRegistryABI, getSigner(hasRelayer ? RELAYER_PRIVATE_KEY : signerKey, provider));
365
+ const linkTx = await registryForLink.setAgentWallet(agentId, walletAddress, deadline, signature);
366
+ await linkTx.wait();
367
+ }
368
+ linkSpinner.succeed('Agent wallet linked to identity!');
369
+ } catch (e) {
370
+ linkSpinner.warn(`Wallet linking skipped: ${e.message}`);
371
+ }
372
+ }
373
+ } catch (e) {
374
+ mineSpinner.warn(`Agent wallet skipped: ${e.message}`);
375
+ }
376
+ } catch (e) {
377
+ error(`Failed on ${netConfig.name}: ${e.message}`);
378
+ }
379
+
380
+ results.push(netResult);
381
+ }
382
+
383
+ // ---- 7. Summary ----
384
+ printSummary(answers.name, agentUrl, paymentTxHash, results);
385
+
386
+ saveConfig({
387
+ lastCreation: {
388
+ name: answers.name,
389
+ url: agentUrl,
390
+ results,
391
+ timestamp: new Date().toISOString(),
392
+ },
393
+ });
394
+
395
+ success('\nYour agent has been created and registered!');
396
+ };
397
+
398
+ function printSummary(agentName, agentUrl, paymentTxHash, results) {
399
+ console.log('');
400
+ console.log(chalk.bold.cyan('================================================'));
401
+ console.log(chalk.bold.cyan(' AGENT CREATION SUMMARY'));
402
+ console.log(chalk.bold.cyan('================================================'));
403
+ console.log(chalk.white(` Agent Name: ${agentName}`));
404
+ console.log(chalk.white(` Agent URL: ${agentUrl}`));
405
+ console.log(chalk.white(` Payment Tx: ${paymentTxHash || 'N/A'}`));
406
+ console.log(chalk.bold.cyan('------------------------------------------------'));
407
+
408
+ for (const r of results) {
409
+ console.log(chalk.bold.yellow(`\n ${r.network}`));
410
+ console.log(chalk.white(` Agent ID: ${r.agentId || 'Failed'}`));
411
+ console.log(chalk.white(` Registration Tx: ${r.registrationTx || 'N/A'}`));
412
+ console.log(chalk.white(` Smart Wallet: ${r.smartWallet || 'N/A'}`));
413
+ console.log(chalk.white(` Agent Wallet: ${r.agentWallet || 'N/A'}`));
414
+ if (r.registrationTx && r.blockExplorer) {
415
+ console.log(
416
+ chalk.gray(
417
+ ` Explorer: ${r.blockExplorer}/tx/${r.registrationTx}`
418
+ )
419
+ );
420
+ }
421
+ }
422
+
423
+ console.log(chalk.bold.cyan('\n================================================'));
424
+ }
@@ -0,0 +1,26 @@
1
+ import inquirer from 'inquirer';
2
+ import { saveConfig, getConfigPath } from '../lib/config.js';
3
+ import { welcome, success } from '../lib/ui.js';
4
+ import { isValidAddress } from '../lib/web3.js';
5
+
6
+ export const init = async () => {
7
+ await welcome();
8
+
9
+ const answers = await inquirer.prompt([
10
+ {
11
+ type: 'input',
12
+ name: 'ownerAddress',
13
+ message: 'Please enter your Owner Wallet Address:',
14
+ validate: (input) => {
15
+ if (isValidAddress(input)) {
16
+ return true;
17
+ }
18
+ return 'Please enter a valid Ethereum address.';
19
+ },
20
+ },
21
+ ]);
22
+
23
+ saveConfig({ ownerAddress: answers.ownerAddress });
24
+ success(`Configuration saved! Owner address: ${answers.ownerAddress}`);
25
+ success(`Config location: ${getConfigPath()}`);
26
+ };
@@ -0,0 +1,40 @@
1
+ import inquirer from 'inquirer';
2
+ import { getConfig } from '../lib/config.js';
3
+ import { welcome } from '../lib/ui.js';
4
+ import { createAgent } from './create.js';
5
+ import { registerAgent } from './register.js';
6
+
7
+ export const mainMenu = async () => {
8
+ const config = getConfig();
9
+ if (!config || !config.ownerAddress) {
10
+ console.log('No configuration found. Please run "init" first.');
11
+ process.exit(1);
12
+ }
13
+
14
+ await welcome();
15
+
16
+ const { action } = await inquirer.prompt([
17
+ {
18
+ type: 'list',
19
+ name: 'action',
20
+ message: 'What would you like to do?',
21
+ choices: [
22
+ { name: '[A] Create Agent (No-Code Flow)', value: 'create' },
23
+ { name: '[B] Register Existing Agent', value: 'register' },
24
+ { name: '[C] Exit', value: 'exit' }
25
+ ]
26
+ }
27
+ ]);
28
+
29
+ switch (action) {
30
+ case 'create':
31
+ await createAgent();
32
+ break;
33
+ case 'register':
34
+ await registerAgent();
35
+ break;
36
+ case 'exit':
37
+ process.exit(0);
38
+ break;
39
+ }
40
+ };