agnic 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/dist/cli.d.ts +2 -0
- package/dist/cli.js +25 -0
- package/dist/commands/address.d.ts +2 -0
- package/dist/commands/address.js +27 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +69 -0
- package/dist/commands/balance.d.ts +2 -0
- package/dist/commands/balance.js +69 -0
- package/dist/commands/send.d.ts +2 -0
- package/dist/commands/send.js +48 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +45 -0
- package/dist/commands/trade.d.ts +2 -0
- package/dist/commands/trade.js +82 -0
- package/dist/commands/x402.d.ts +2 -0
- package/dist/commands/x402.js +122 -0
- package/dist/lib/api-client.d.ts +21 -0
- package/dist/lib/api-client.js +88 -0
- package/dist/lib/config.d.ts +18 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/output.d.ts +8 -0
- package/dist/lib/output.js +31 -0
- package/package.json +48 -0
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerAuthCommand } from './commands/auth.js';
|
|
4
|
+
import { registerStatusCommand } from './commands/status.js';
|
|
5
|
+
import { registerBalanceCommand } from './commands/balance.js';
|
|
6
|
+
import { registerAddressCommand } from './commands/address.js';
|
|
7
|
+
import { registerSendCommand } from './commands/send.js';
|
|
8
|
+
import { registerTradeCommand } from './commands/trade.js';
|
|
9
|
+
import { registerX402Command } from './commands/x402.js';
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program
|
|
12
|
+
.name('agnic')
|
|
13
|
+
.description('CLI for AgnicPay — AI agent wallet for x402 payments and token trading')
|
|
14
|
+
.version('1.0.0')
|
|
15
|
+
.option('--token <token>', 'API token (overrides stored token and AGNIC_TOKEN env)')
|
|
16
|
+
.option('--api-url <url>', 'API base URL (default: https://api.agnic.ai)');
|
|
17
|
+
// Register all commands
|
|
18
|
+
registerAuthCommand(program);
|
|
19
|
+
registerStatusCommand(program);
|
|
20
|
+
registerBalanceCommand(program);
|
|
21
|
+
registerAddressCommand(program);
|
|
22
|
+
registerSendCommand(program);
|
|
23
|
+
registerTradeCommand(program);
|
|
24
|
+
registerX402Command(program);
|
|
25
|
+
program.parse();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getToken, loadConfig } from '../lib/config.js';
|
|
2
|
+
import { printError, printJson } from '../lib/output.js';
|
|
3
|
+
export function registerAddressCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command('address')
|
|
6
|
+
.description('Show wallet address')
|
|
7
|
+
.option('--json', 'Output as JSON')
|
|
8
|
+
.action(async (opts) => {
|
|
9
|
+
const token = getToken(program.opts());
|
|
10
|
+
if (!token) {
|
|
11
|
+
printError('Not authenticated. Run: agnic auth login <email>');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const config = loadConfig();
|
|
15
|
+
const address = config.walletAddress;
|
|
16
|
+
if (!address) {
|
|
17
|
+
printError('No wallet address found. Visit https://pay.agnic.ai to create a wallet.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
if (opts.json) {
|
|
21
|
+
printJson({ address });
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log(address);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { saveConfig, clearConfig, getApiUrl } from '../lib/config.js';
|
|
4
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
5
|
+
import { printSuccess, printError, printJson } from '../lib/output.js';
|
|
6
|
+
export function registerAuthCommand(program) {
|
|
7
|
+
const auth = program.command('auth').description('Manage authentication');
|
|
8
|
+
auth
|
|
9
|
+
.command('login <email>')
|
|
10
|
+
.description('Login with email OTP')
|
|
11
|
+
.action(async (email) => {
|
|
12
|
+
const apiUrl = getApiUrl();
|
|
13
|
+
const spinner = ora('Sending verification code...').start();
|
|
14
|
+
try {
|
|
15
|
+
const result = await ApiClient.otpInitiate(apiUrl, email);
|
|
16
|
+
spinner.succeed('Verification code sent!');
|
|
17
|
+
console.log(chalk.gray(`Check your email: ${email}`));
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(`Flow ID: ${chalk.cyan(result.flowId)}`);
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(chalk.bold('Run:'));
|
|
22
|
+
console.log(` agnic auth verify ${result.flowId} <6-digit-code>`);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
spinner.fail('Failed to send verification code');
|
|
26
|
+
printError(error.message);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
auth
|
|
31
|
+
.command('verify <flowId> <otp>')
|
|
32
|
+
.description('Verify OTP code')
|
|
33
|
+
.option('--json', 'Output as JSON')
|
|
34
|
+
.action(async (flowId, otp, opts) => {
|
|
35
|
+
const apiUrl = getApiUrl();
|
|
36
|
+
const spinner = ora('Verifying code...').start();
|
|
37
|
+
try {
|
|
38
|
+
const result = await ApiClient.otpVerify(apiUrl, flowId, otp);
|
|
39
|
+
spinner.succeed('Authenticated!');
|
|
40
|
+
// Save token
|
|
41
|
+
saveConfig({
|
|
42
|
+
token: result.token,
|
|
43
|
+
email: result.email,
|
|
44
|
+
walletAddress: result.walletAddress,
|
|
45
|
+
});
|
|
46
|
+
if (opts.json) {
|
|
47
|
+
printJson({ email: result.email, walletAddress: result.walletAddress });
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(`Email: ${chalk.cyan(result.email)}`);
|
|
51
|
+
console.log(`Wallet: ${chalk.cyan(result.walletAddress || 'Not created yet')}`);
|
|
52
|
+
console.log();
|
|
53
|
+
printSuccess('Token saved to ~/.agnic/config.json');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
spinner.fail('Verification failed');
|
|
58
|
+
printError(error.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
auth
|
|
63
|
+
.command('logout')
|
|
64
|
+
.description('Remove stored credentials')
|
|
65
|
+
.action(() => {
|
|
66
|
+
clearConfig();
|
|
67
|
+
printSuccess('Logged out. Token removed from ~/.agnic/config.json');
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getToken, getApiUrl } from '../lib/config.js';
|
|
4
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
5
|
+
import { printError, printJson, printTable } from '../lib/output.js';
|
|
6
|
+
export function registerBalanceCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('balance')
|
|
9
|
+
.description('Check USDC balance')
|
|
10
|
+
.option('--network <network>', 'Specific network (base, base-sepolia, solana, solana-devnet)')
|
|
11
|
+
.option('--json', 'Output as JSON')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const token = getToken(program.opts());
|
|
14
|
+
if (!token) {
|
|
15
|
+
printError('Not authenticated. Run: agnic auth login <email>');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const spinner = ora('Fetching balance...').start();
|
|
19
|
+
try {
|
|
20
|
+
const client = new ApiClient(getApiUrl(), token);
|
|
21
|
+
if (opts.network) {
|
|
22
|
+
const result = await client.getBalance(opts.network);
|
|
23
|
+
spinner.stop();
|
|
24
|
+
if (opts.json) {
|
|
25
|
+
printJson(result);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log(`${chalk.bold(opts.network)}: ${chalk.green('$' + result.usdcBalance)} USDC`);
|
|
29
|
+
if (result.address)
|
|
30
|
+
console.log(`Address: ${chalk.gray(result.address)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Fetch all networks
|
|
35
|
+
const networks = ['base', 'solana'];
|
|
36
|
+
const results = [];
|
|
37
|
+
for (const net of networks) {
|
|
38
|
+
try {
|
|
39
|
+
const result = await client.getBalance(net);
|
|
40
|
+
results.push({
|
|
41
|
+
network: net,
|
|
42
|
+
balance: result.usdcBalance || '0',
|
|
43
|
+
address: result.address || 'N/A',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
results.push({ network: net, balance: '0', address: 'N/A' });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
spinner.stop();
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
printJson(results);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
printTable(results.map((r) => ({
|
|
56
|
+
Network: r.network,
|
|
57
|
+
Currency: 'USDC',
|
|
58
|
+
Balance: `$${r.balance}`,
|
|
59
|
+
})));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
spinner.fail('Failed to fetch balance');
|
|
65
|
+
printError(error.message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getToken, getApiUrl } from '../lib/config.js';
|
|
4
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
5
|
+
import { printError, printJson } from '../lib/output.js';
|
|
6
|
+
export function registerSendCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('send <amount> <to>')
|
|
9
|
+
.description('Send USDC to a wallet address')
|
|
10
|
+
.option('--network <network>', 'Network (default: base)', 'base')
|
|
11
|
+
.option('--memo <memo>', 'Transaction memo')
|
|
12
|
+
.option('--json', 'Output as JSON')
|
|
13
|
+
.action(async (amount, to, opts) => {
|
|
14
|
+
const token = getToken(program.opts());
|
|
15
|
+
if (!token) {
|
|
16
|
+
printError('Not authenticated. Run: agnic auth login <email>');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const amountNum = parseFloat(amount);
|
|
20
|
+
if (isNaN(amountNum) || amountNum <= 0) {
|
|
21
|
+
printError('Amount must be a positive number');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const spinner = ora(`Sending ${amount} USDC to ${to.slice(0, 8)}...${to.slice(-6)}...`).start();
|
|
25
|
+
try {
|
|
26
|
+
const client = new ApiClient(getApiUrl(), token);
|
|
27
|
+
const result = await client.send(to, amountNum, opts.network, opts.memo);
|
|
28
|
+
spinner.succeed('Transfer complete!');
|
|
29
|
+
if (opts.json) {
|
|
30
|
+
printJson(result);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.log(`Amount: ${chalk.green('$' + result.amount)} USDC`);
|
|
34
|
+
console.log(`To: ${chalk.cyan(result.to)}`);
|
|
35
|
+
console.log(`Network: ${result.network}`);
|
|
36
|
+
console.log(`Tx: ${chalk.gray(result.transactionHash)}`);
|
|
37
|
+
if (result.explorerUrl) {
|
|
38
|
+
console.log(`Explorer: ${chalk.underline(result.explorerUrl)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
spinner.fail('Send failed');
|
|
44
|
+
printError(error.message);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getToken, getApiUrl, loadConfig } from '../lib/config.js';
|
|
4
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
5
|
+
import { printError, printJson } from '../lib/output.js';
|
|
6
|
+
export function registerStatusCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('status')
|
|
9
|
+
.description('Check authentication and wallet status')
|
|
10
|
+
.option('--json', 'Output as JSON')
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const token = getToken(program.opts());
|
|
13
|
+
if (!token) {
|
|
14
|
+
printError('Not authenticated. Run: agnic auth login <email>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const spinner = ora('Checking status...').start();
|
|
18
|
+
try {
|
|
19
|
+
const client = new ApiClient(getApiUrl(), token);
|
|
20
|
+
const result = await client.validate();
|
|
21
|
+
spinner.succeed('Authenticated');
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
if (opts.json) {
|
|
24
|
+
printJson({
|
|
25
|
+
authenticated: true,
|
|
26
|
+
userId: result.userId || result.valid,
|
|
27
|
+
email: config.email || null,
|
|
28
|
+
walletAddress: config.walletAddress || null,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.log(`User ID: ${chalk.cyan(result.userId || 'OK')}`);
|
|
33
|
+
if (config.email)
|
|
34
|
+
console.log(`Email: ${chalk.cyan(config.email)}`);
|
|
35
|
+
if (config.walletAddress)
|
|
36
|
+
console.log(`Wallet: ${chalk.cyan(config.walletAddress)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
spinner.fail('Authentication check failed');
|
|
41
|
+
printError(error.message);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getToken, getApiUrl } from '../lib/config.js';
|
|
4
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
5
|
+
import { printError, printJson } from '../lib/output.js';
|
|
6
|
+
export function registerTradeCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('trade <amount> <from> <to>')
|
|
9
|
+
.description('Trade/swap tokens on Base (e.g., agnic trade 10 usdc eth)')
|
|
10
|
+
.option('--slippage <percent>', 'Max slippage percentage (default: 1.0)', '1.0')
|
|
11
|
+
.option('--dry-run', 'Show quote without executing')
|
|
12
|
+
.option('--json', 'Output as JSON')
|
|
13
|
+
.action(async (amount, from, to, opts) => {
|
|
14
|
+
const token = getToken(program.opts());
|
|
15
|
+
if (!token) {
|
|
16
|
+
printError('Not authenticated. Run: agnic auth login <email>');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const amountNum = parseFloat(amount);
|
|
20
|
+
if (isNaN(amountNum) || amountNum <= 0) {
|
|
21
|
+
printError('Amount must be a positive number');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const sellToken = from.toUpperCase();
|
|
25
|
+
const buyToken = to.toUpperCase();
|
|
26
|
+
const slippage = parseFloat(opts.slippage);
|
|
27
|
+
const client = new ApiClient(getApiUrl(), token);
|
|
28
|
+
// Dry run — just show quote
|
|
29
|
+
if (opts.dryRun) {
|
|
30
|
+
const spinner = ora('Fetching quote...').start();
|
|
31
|
+
try {
|
|
32
|
+
const quote = await client.tradeQuote(sellToken, buyToken, amountNum);
|
|
33
|
+
spinner.stop();
|
|
34
|
+
if (opts.json) {
|
|
35
|
+
printJson(quote);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
console.log(chalk.bold('Trade Quote:'));
|
|
39
|
+
console.log(` Sell: ${amount} ${sellToken}`);
|
|
40
|
+
console.log(` Buy: ${chalk.green(quote.buyAmount)} ${buyToken}`);
|
|
41
|
+
console.log(` Price: ${quote.price}`);
|
|
42
|
+
console.log(` Slippage: ${quote.slippage}`);
|
|
43
|
+
console.log(` Network: ${quote.network}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
spinner.fail('Quote failed');
|
|
48
|
+
printError(error.message);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Execute trade
|
|
54
|
+
const spinner = ora(`Swapping ${amount} ${sellToken} → ${buyToken} on Base...`).start();
|
|
55
|
+
try {
|
|
56
|
+
const result = await client.trade(sellToken, buyToken, amountNum, slippage);
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
spinner.fail('Trade failed');
|
|
59
|
+
printError(result.error || result.message || 'Unknown error');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
spinner.succeed('Trade complete!');
|
|
63
|
+
if (opts.json) {
|
|
64
|
+
printJson(result);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log(` Sold: ${result.sellAmount} ${result.sellToken}`);
|
|
68
|
+
console.log(` Got: ${chalk.green(result.buyAmount)} ${result.buyToken}`);
|
|
69
|
+
console.log(` Price: ${result.price}`);
|
|
70
|
+
console.log(` Tx: ${chalk.gray(result.transactionHash)}`);
|
|
71
|
+
if (result.explorerUrl) {
|
|
72
|
+
console.log(` Explorer: ${chalk.underline(result.explorerUrl)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
spinner.fail('Trade failed');
|
|
78
|
+
printError(error.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getToken, getApiUrl } from '../lib/config.js';
|
|
4
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
5
|
+
import { printError, printJson } from '../lib/output.js';
|
|
6
|
+
export function registerX402Command(program) {
|
|
7
|
+
const x402 = program
|
|
8
|
+
.command('x402')
|
|
9
|
+
.description('Search and pay for x402-enabled APIs');
|
|
10
|
+
// Search subcommand
|
|
11
|
+
x402
|
|
12
|
+
.command('search <query>')
|
|
13
|
+
.description('Search for x402-enabled APIs')
|
|
14
|
+
.option('--category <category>', 'Filter by category (AI, Crypto, Data, Trading, Finance, Weather)')
|
|
15
|
+
.option('--limit <n>', 'Max results (default: 10)', '10')
|
|
16
|
+
.option('--json', 'Output as JSON')
|
|
17
|
+
.action(async (query, opts) => {
|
|
18
|
+
const spinner = ora('Searching x402 APIs...').start();
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch('https://hub.agnic.ai/api/discover', {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'Content-Type': 'application/json' },
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
search: query,
|
|
25
|
+
category: opts.category,
|
|
26
|
+
limit: parseInt(opts.limit, 10),
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
throw new Error(`Search failed: ${res.status}`);
|
|
31
|
+
}
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
spinner.stop();
|
|
34
|
+
if (opts.json) {
|
|
35
|
+
printJson(data);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const apis = data.apis || data.results || data;
|
|
39
|
+
if (!Array.isArray(apis) || apis.length === 0) {
|
|
40
|
+
console.log(chalk.yellow('No APIs found matching your query.'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.log(chalk.bold(`Found ${apis.length} API(s):\n`));
|
|
44
|
+
apis.forEach((api, i) => {
|
|
45
|
+
console.log(` ${chalk.cyan(`${i + 1}.`)} ${chalk.bold(api.name || api.title)}`);
|
|
46
|
+
if (api.description) {
|
|
47
|
+
console.log(` ${chalk.gray(api.description.slice(0, 80))}`);
|
|
48
|
+
}
|
|
49
|
+
if (api.price || api.cost) {
|
|
50
|
+
console.log(` Price: ${chalk.green('$' + (api.price || api.cost))} /request`);
|
|
51
|
+
}
|
|
52
|
+
if (api.url || api.endpoint) {
|
|
53
|
+
console.log(` URL: ${chalk.underline(api.url || api.endpoint)}`);
|
|
54
|
+
}
|
|
55
|
+
console.log();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
spinner.fail('Search failed');
|
|
60
|
+
printError(error.message);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// Pay subcommand
|
|
65
|
+
x402
|
|
66
|
+
.command('pay <url>')
|
|
67
|
+
.description('Make an x402 payment request to an API')
|
|
68
|
+
.option('--method <method>', 'HTTP method (default: GET)', 'GET')
|
|
69
|
+
.option('--body <json>', 'Request body (JSON string)')
|
|
70
|
+
.option('--network <network>', 'Payment network (default: base)', 'base')
|
|
71
|
+
.option('--json', 'Output as JSON')
|
|
72
|
+
.action(async (url, opts) => {
|
|
73
|
+
const token = getToken(program.opts());
|
|
74
|
+
if (!token) {
|
|
75
|
+
printError('Not authenticated. Run: agnic auth login <email>');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const spinner = ora(`Paying ${url}...`).start();
|
|
79
|
+
try {
|
|
80
|
+
const client = new ApiClient(getApiUrl(), token);
|
|
81
|
+
let body = undefined;
|
|
82
|
+
if (opts.body) {
|
|
83
|
+
try {
|
|
84
|
+
body = JSON.parse(opts.body);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
spinner.fail('Invalid JSON body');
|
|
88
|
+
printError('--body must be a valid JSON string');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const result = await client.x402Fetch(url, opts.method, body);
|
|
93
|
+
spinner.succeed('Request complete!');
|
|
94
|
+
if (opts.json) {
|
|
95
|
+
printJson(result);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (result.status)
|
|
99
|
+
console.log(`Status: ${chalk.green(result.status)}`);
|
|
100
|
+
if (result.cost)
|
|
101
|
+
console.log(`Cost: ${chalk.yellow('$' + result.cost)}`);
|
|
102
|
+
if (result.network)
|
|
103
|
+
console.log(`Network: ${result.network}`);
|
|
104
|
+
if (result.transactionHash) {
|
|
105
|
+
console.log(`Tx: ${chalk.gray(result.transactionHash)}`);
|
|
106
|
+
}
|
|
107
|
+
console.log(chalk.bold('\nResponse:'));
|
|
108
|
+
if (typeof result.data === 'object') {
|
|
109
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log(result.data || result.body || JSON.stringify(result, null, 2));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
spinner.fail('Request failed');
|
|
118
|
+
printError(error.message);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for agnic CLI
|
|
3
|
+
* Wraps api.agnic.ai endpoints
|
|
4
|
+
*/
|
|
5
|
+
export declare class ApiClient {
|
|
6
|
+
private apiUrl;
|
|
7
|
+
private token;
|
|
8
|
+
constructor(apiUrl: string, token: string);
|
|
9
|
+
private getHeaders;
|
|
10
|
+
get(path: string): Promise<any>;
|
|
11
|
+
post(path: string, body: any): Promise<any>;
|
|
12
|
+
static otpInitiate(apiUrl: string, email: string): Promise<any>;
|
|
13
|
+
static otpVerify(apiUrl: string, flowId: string, otp: string): Promise<any>;
|
|
14
|
+
validate(): Promise<any>;
|
|
15
|
+
getBalance(network?: string): Promise<any>;
|
|
16
|
+
send(to: string, amount: number, network: string, memo?: string): Promise<any>;
|
|
17
|
+
trade(sellToken: string, buyToken: string, sellAmount: number, slippagePercentage?: number): Promise<any>;
|
|
18
|
+
tradeQuote(sellToken: string, buyToken: string, sellAmount: number): Promise<any>;
|
|
19
|
+
x402Fetch(url: string, method?: string, body?: any): Promise<any>;
|
|
20
|
+
getAgentIdentity(): Promise<any>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for agnic CLI
|
|
3
|
+
* Wraps api.agnic.ai endpoints
|
|
4
|
+
*/
|
|
5
|
+
export class ApiClient {
|
|
6
|
+
apiUrl;
|
|
7
|
+
token;
|
|
8
|
+
constructor(apiUrl, token) {
|
|
9
|
+
this.apiUrl = apiUrl;
|
|
10
|
+
this.token = token;
|
|
11
|
+
}
|
|
12
|
+
getHeaders() {
|
|
13
|
+
return {
|
|
14
|
+
'X-Agnic-Token': this.token,
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async get(path) {
|
|
19
|
+
const response = await fetch(`${this.apiUrl}${path}`, {
|
|
20
|
+
headers: this.getHeaders(),
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
const err = await response.json().catch(() => ({ error: response.statusText }));
|
|
24
|
+
throw new Error(err.message || err.error || `HTTP ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
return response.json();
|
|
27
|
+
}
|
|
28
|
+
async post(path, body) {
|
|
29
|
+
const response = await fetch(`${this.apiUrl}${path}`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: this.getHeaders(),
|
|
32
|
+
body: JSON.stringify(body),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const err = await response.json().catch(() => ({ error: response.statusText }));
|
|
36
|
+
throw new Error(err.message || err.error || `HTTP ${response.status}`);
|
|
37
|
+
}
|
|
38
|
+
return response.json();
|
|
39
|
+
}
|
|
40
|
+
// Auth endpoints (no token needed)
|
|
41
|
+
static async otpInitiate(apiUrl, email) {
|
|
42
|
+
const response = await fetch(`${apiUrl}/api/auth/otp/initiate`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
body: JSON.stringify({ email }),
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const err = await response.json().catch(() => ({ error: response.statusText }));
|
|
49
|
+
throw new Error(err.message || err.error || `HTTP ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
return response.json();
|
|
52
|
+
}
|
|
53
|
+
static async otpVerify(apiUrl, flowId, otp) {
|
|
54
|
+
const response = await fetch(`${apiUrl}/api/auth/otp/verify`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'Content-Type': 'application/json' },
|
|
57
|
+
body: JSON.stringify({ flowId, otp }),
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const err = await response.json().catch(() => ({ error: response.statusText }));
|
|
61
|
+
throw new Error(err.message || err.error || `HTTP ${response.status}`);
|
|
62
|
+
}
|
|
63
|
+
return response.json();
|
|
64
|
+
}
|
|
65
|
+
// Wallet endpoints
|
|
66
|
+
async validate() {
|
|
67
|
+
return this.get('/api/auth/validate');
|
|
68
|
+
}
|
|
69
|
+
async getBalance(network) {
|
|
70
|
+
const params = network ? `?network=${network}` : '';
|
|
71
|
+
return this.get(`/api/balance${params}`);
|
|
72
|
+
}
|
|
73
|
+
async send(to, amount, network, memo) {
|
|
74
|
+
return this.post('/api/send', { to, amount, network, ...(memo && { memo }) });
|
|
75
|
+
}
|
|
76
|
+
async trade(sellToken, buyToken, sellAmount, slippagePercentage) {
|
|
77
|
+
return this.post('/api/trade', { sellToken, buyToken, sellAmount, slippagePercentage, network: 'base' });
|
|
78
|
+
}
|
|
79
|
+
async tradeQuote(sellToken, buyToken, sellAmount) {
|
|
80
|
+
return this.get(`/api/trade/quote?sellToken=${sellToken}&buyToken=${buyToken}&sellAmount=${sellAmount}`);
|
|
81
|
+
}
|
|
82
|
+
async x402Fetch(url, method = 'GET', body) {
|
|
83
|
+
return this.post(`/api/x402/fetch?url=${encodeURIComponent(url)}&method=${method}`, body || {});
|
|
84
|
+
}
|
|
85
|
+
async getAgentIdentity() {
|
|
86
|
+
return this.get('/api/agent/identity');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for agnic CLI
|
|
3
|
+
* Stores auth token and preferences in ~/.agnic/config.json
|
|
4
|
+
*/
|
|
5
|
+
interface AgnicConfig {
|
|
6
|
+
token?: string;
|
|
7
|
+
email?: string;
|
|
8
|
+
walletAddress?: string;
|
|
9
|
+
apiUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadConfig(): AgnicConfig;
|
|
12
|
+
export declare function saveConfig(config: AgnicConfig): void;
|
|
13
|
+
export declare function getToken(flags: {
|
|
14
|
+
token?: string;
|
|
15
|
+
}): string | undefined;
|
|
16
|
+
export declare function getApiUrl(): string;
|
|
17
|
+
export declare function clearConfig(): void;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for agnic CLI
|
|
3
|
+
* Stores auth token and preferences in ~/.agnic/config.json
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
const CONFIG_DIR = join(homedir(), '.agnic');
|
|
9
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
10
|
+
function ensureConfigDir() {
|
|
11
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
12
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function loadConfig() {
|
|
16
|
+
try {
|
|
17
|
+
if (!existsSync(CONFIG_FILE))
|
|
18
|
+
return {};
|
|
19
|
+
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function saveConfig(config) {
|
|
27
|
+
ensureConfigDir();
|
|
28
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
export function getToken(flags) {
|
|
31
|
+
// Priority: flag > env > stored
|
|
32
|
+
return flags.token || process.env.AGNIC_TOKEN || loadConfig().token;
|
|
33
|
+
}
|
|
34
|
+
export function getApiUrl() {
|
|
35
|
+
return process.env.AGNIC_API_URL || loadConfig().apiUrl || 'https://api.agnic.ai';
|
|
36
|
+
}
|
|
37
|
+
export function clearConfig() {
|
|
38
|
+
saveConfig({});
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting for agnic CLI
|
|
3
|
+
*/
|
|
4
|
+
export declare function printJson(data: any): void;
|
|
5
|
+
export declare function printTable(rows: Record<string, string | number>[]): void;
|
|
6
|
+
export declare function printSuccess(message: string): void;
|
|
7
|
+
export declare function printError(message: string): void;
|
|
8
|
+
export declare function printInfo(message: string): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting for agnic CLI
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
export function printJson(data) {
|
|
6
|
+
console.log(JSON.stringify(data, null, 2));
|
|
7
|
+
}
|
|
8
|
+
export function printTable(rows) {
|
|
9
|
+
if (rows.length === 0)
|
|
10
|
+
return;
|
|
11
|
+
const keys = Object.keys(rows[0]);
|
|
12
|
+
const widths = keys.map((k) => Math.max(k.length, ...rows.map((r) => String(r[k]).length)));
|
|
13
|
+
// Header
|
|
14
|
+
const header = keys.map((k, i) => k.padEnd(widths[i])).join(' ');
|
|
15
|
+
console.log(chalk.bold(header));
|
|
16
|
+
console.log(chalk.gray('-'.repeat(header.length)));
|
|
17
|
+
// Rows
|
|
18
|
+
for (const row of rows) {
|
|
19
|
+
const line = keys.map((k, i) => String(row[k]).padEnd(widths[i])).join(' ');
|
|
20
|
+
console.log(line);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function printSuccess(message) {
|
|
24
|
+
console.log(chalk.green(`✓ ${message}`));
|
|
25
|
+
}
|
|
26
|
+
export function printError(message) {
|
|
27
|
+
console.error(chalk.red(`✗ ${message}`));
|
|
28
|
+
}
|
|
29
|
+
export function printInfo(message) {
|
|
30
|
+
console.log(chalk.cyan(`ℹ ${message}`));
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agnic",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for AgnicPay - AI agent wallet for x402 payments and token trading",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agnic": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"dev": "tsx src/cli.ts",
|
|
16
|
+
"start": "node dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"commander": "^12.0.0",
|
|
20
|
+
"chalk": "^5.3.0",
|
|
21
|
+
"ora": "^8.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.4.0",
|
|
25
|
+
"tsx": "^4.7.0",
|
|
26
|
+
"@types/node": "^22.0.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"agnic",
|
|
33
|
+
"agnicpay",
|
|
34
|
+
"x402",
|
|
35
|
+
"wallet",
|
|
36
|
+
"cli",
|
|
37
|
+
"ai-agent",
|
|
38
|
+
"trading",
|
|
39
|
+
"usdc"
|
|
40
|
+
],
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/agnicpay/agnicwallet-project",
|
|
45
|
+
"directory": "agnic-cli"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://docs.agnic.ai/docs/agnicpay-features/cli"
|
|
48
|
+
}
|