diff --git a/packages/atxp/src/commands/agent.ts b/packages/atxp/src/commands/agent.ts new file mode 100644 index 0000000..0bfc1aa --- /dev/null +++ b/packages/atxp/src/commands/agent.ts @@ -0,0 +1,167 @@ +import chalk from 'chalk'; +import { getConnection } from '../config.js'; + +function getAccountsAuth(): { baseUrl: string; token: string } { + const connection = getConnection(); + if (!connection) { + console.error(chalk.red('Not logged in.')); + console.error(`Run: ${chalk.cyan('npx atxp login')}`); + process.exit(1); + } + const url = new URL(connection); + const token = url.searchParams.get('connection_token'); + if (!token) { + console.error(chalk.red('Invalid connection string: missing connection_token')); + process.exit(1); + } + return { baseUrl: `${url.protocol}//${url.host}`, token }; +} + +function showAgentHelp(): void { + console.log(chalk.bold('Agent Commands:')); + console.log(); + console.log(' ' + chalk.cyan('npx atxp agent create') + ' ' + 'Create a new agent account'); + console.log(' ' + chalk.cyan('npx atxp agent list') + ' ' + 'List your agents'); + console.log(); + console.log(chalk.bold('Details:')); + console.log(' Each agent gets:'); + console.log(' - A unique email address ({agentId}@atxp.email)'); + console.log(' - An Ethereum wallet'); + console.log(' - 10 IOU tokens to start'); + console.log(' - A connection token for SDK/CLI access'); + console.log(); + console.log(chalk.bold('Examples:')); + console.log(' npx atxp agent create'); + console.log(' npx atxp agent list'); + console.log(' CONNECTION_TOKEN= npx atxp email inbox'); +} + +export async function agentCommand(subCommand: string): Promise { + if (!subCommand || subCommand === 'help' || subCommand === '--help' || subCommand === '-h') { + showAgentHelp(); + return; + } + + switch (subCommand) { + case 'create': + await createAgent(); + break; + + case 'list': + await listAgents(); + break; + + default: + console.error(chalk.red(`Unknown agent command: ${subCommand}`)); + console.log(); + showAgentHelp(); + process.exit(1); + } +} + +async function createAgent(): Promise { + const { baseUrl, token } = getAccountsAuth(); + + console.log(chalk.gray('Creating agent...')); + + const res = await fetch(`${baseUrl}/agents`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + const body = await res.json().catch(() => ({})); + console.error(chalk.red(`Error: ${(body as Record).error || res.statusText}`)); + process.exit(1); + } + + const data = await res.json() as { + agentId: string; + email: string; + atxpAccountId: string; + connectionToken: string; + connectionString: string; + walletAddress: string; + fundedAmount: string; + }; + + console.log(); + console.log(chalk.green.bold('Agent created successfully!')); + console.log(); + console.log(' ' + chalk.bold('Agent ID:') + ' ' + data.agentId); + console.log(' ' + chalk.bold('Email:') + ' ' + chalk.cyan(data.email)); + console.log(' ' + chalk.bold('Account ID:') + ' ' + data.atxpAccountId); + console.log(' ' + chalk.bold('Connection Token:') + ' ' + data.connectionToken); + console.log(' ' + chalk.bold('Wallet:') + ' ' + data.walletAddress); + console.log(' ' + chalk.bold('Funded:') + ' ' + data.fundedAmount + ' IOU'); + console.log(); + console.log(chalk.bold('Connection String:')); + console.log(' ' + chalk.cyan(data.connectionString)); + console.log(); + console.log(chalk.bold('Use this to authenticate as the agent:')); + console.log(' ' + chalk.yellow(`CONNECTION_TOKEN=${data.connectionToken} npx atxp email inbox`)); +} + +async function listAgents(): Promise { + const { baseUrl, token } = getAccountsAuth(); + + const res = await fetch(`${baseUrl}/agents`, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!res.ok) { + const body = await res.json().catch(() => ({})); + console.error(chalk.red(`Error: ${(body as Record).error || res.statusText}`)); + process.exit(1); + } + + const data = await res.json() as { + agents: Array<{ + connectionToken: string; + atxpAccountId: string; + email: string | null; + walletAddress: string | null; + balance: { totalUsdc: string; totalIou: string } | null; + createdAt: string | null; + }>; + }; + + if (data.agents.length === 0) { + console.log(chalk.gray('No agents created yet.')); + console.log(); + console.log('Create one with: ' + chalk.cyan('npx atxp agent create')); + return; + } + + console.log(chalk.bold(`Your Agents (${data.agents.length})`)); + console.log(); + + for (const agent of data.agents) { + console.log(chalk.gray('─'.repeat(50))); + console.log(' ' + chalk.bold('Email:') + ' ' + chalk.cyan(agent.email || 'Unknown')); + console.log(' ' + chalk.bold('Account ID:') + ' ' + agent.atxpAccountId); + console.log(' ' + chalk.bold('Connection Token:') + ' ' + agent.connectionToken); + if (agent.walletAddress) { + console.log(' ' + chalk.bold('Wallet:') + ' ' + agent.walletAddress); + } + if (agent.balance) { + console.log(' ' + chalk.bold('Balance:') + ' ' + `$${agent.balance.totalUsdc} USDC / $${agent.balance.totalIou} IOU`); + } + if (agent.createdAt) { + const date = new Date(agent.createdAt).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + console.log(' ' + chalk.bold('Created:') + ' ' + chalk.gray(date)); + } + } + console.log(chalk.gray('─'.repeat(50))); +} diff --git a/packages/atxp/src/help.ts b/packages/atxp/src/help.ts index 5e8b0da..009c2c4 100644 --- a/packages/atxp/src/help.ts +++ b/packages/atxp/src/help.ts @@ -25,6 +25,7 @@ export function showHelp(): void { console.log(' ' + chalk.cyan('x') + ' ' + chalk.yellow('') + ' ' + 'Search X/Twitter'); console.log(' ' + chalk.cyan('email') + ' ' + chalk.yellow('') + ' ' + 'Send and receive emails'); console.log(' ' + chalk.cyan('balance') + ' ' + 'Check your ATXP account balance'); + console.log(' ' + chalk.cyan('agent') + ' ' + chalk.yellow('') + ' ' + 'Create and manage agent accounts'); console.log(); console.log(chalk.bold('PAAS (Platform as a Service):')); @@ -84,6 +85,11 @@ export function showHelp(): void { console.log(' npx atxp dev create my-app # Create a new project'); console.log(); + console.log(chalk.bold('Agent Examples:')); + console.log(' npx atxp agent create # Create a new agent'); + console.log(' npx atxp agent list # List your agents'); + console.log(); + console.log(chalk.bold('PAAS Examples:')); console.log(' npx atxp paas worker deploy my-api --code ./worker.js'); console.log(' npx atxp paas db create my-database'); diff --git a/packages/atxp/src/index.ts b/packages/atxp/src/index.ts index ae4eeb8..7d3bf30 100644 --- a/packages/atxp/src/index.ts +++ b/packages/atxp/src/index.ts @@ -15,6 +15,7 @@ import { xCommand } from './commands/x.js'; import { emailCommand } from './commands/email.js'; import { balanceCommand } from './commands/balance.js'; import { paasCommand } from './commands/paas/index.js'; +import { agentCommand } from './commands/agent.js'; interface DemoOptions { port: number; @@ -88,7 +89,7 @@ function parseArgs(): { // Check for help flags early - but NOT for paas or email commands (they handle --help internally) const helpFlag = process.argv.includes('--help') || process.argv.includes('-h'); - if (helpFlag && command !== 'paas' && command !== 'email') { + if (helpFlag && command !== 'paas' && command !== 'email' && command !== 'agent') { return { command: 'help', demoOptions: { port: 8017, dir: '', verbose: false, refresh: false }, @@ -315,6 +316,10 @@ async function main() { await paasCommand(paasArgs, paasOptions); break; + case 'agent': + await agentCommand(subCommand || ''); + break; + case 'dev': // Dev subcommands (demo, create) if (subCommand === 'demo') {