11import chalk from 'chalk' ;
2+ import { createInterface } from 'readline' ;
23import { getConnection } from '../config.js' ;
34
5+ const DEFAULT_ACCOUNTS_URL = 'https://accounts.atxp.ai' ;
6+
47function getAccountsAuth ( ) : { baseUrl : string ; token : string } {
58 const connection = getConnection ( ) ;
69 if ( ! connection ) {
@@ -17,11 +20,25 @@ function getAccountsAuth(): { baseUrl: string; token: string } {
1720 return { baseUrl : `${ url . protocol } //${ url . host } ` , token } ;
1821}
1922
23+ function getBaseUrl ( ) : string {
24+ const connection = getConnection ( ) ;
25+ if ( connection ) {
26+ try {
27+ const url = new URL ( connection ) ;
28+ return `${ url . protocol } //${ url . host } ` ;
29+ } catch {
30+ // Fall through to default
31+ }
32+ }
33+ return DEFAULT_ACCOUNTS_URL ;
34+ }
35+
2036function showAgentHelp ( ) : void {
2137 console . log ( chalk . bold ( 'Agent Commands:' ) ) ;
2238 console . log ( ) ;
23- console . log ( ' ' + chalk . cyan ( 'npx atxp agent create' ) + ' ' + 'Create a new agent account' ) ;
24- console . log ( ' ' + chalk . cyan ( 'npx atxp agent list' ) + ' ' + 'List your agents' ) ;
39+ console . log ( ' ' + chalk . cyan ( 'npx atxp agent create' ) + ' ' + 'Create a new agent account (requires login)' ) ;
40+ console . log ( ' ' + chalk . cyan ( 'npx atxp agent list' ) + ' ' + 'List your agents (requires login)' ) ;
41+ console . log ( ' ' + chalk . cyan ( 'npx atxp agent register' ) + ' ' + 'Self-register as an agent (no login required)' ) ;
2542 console . log ( ) ;
2643 console . log ( chalk . bold ( 'Details:' ) ) ;
2744 console . log ( ' Each agent gets:' ) ;
@@ -30,9 +47,15 @@ function showAgentHelp(): void {
3047 console . log ( ' - 10 IOU tokens to start' ) ;
3148 console . log ( ' - A connection token for SDK/CLI access' ) ;
3249 console . log ( ) ;
50+ console . log ( chalk . bold ( 'Register Options:' ) ) ;
51+ console . log ( ' ' + chalk . yellow ( '--server' ) + ' ' + 'Accounts server URL (default: https://accounts.atxp.ai)' ) ;
52+ console . log ( ' ' + chalk . yellow ( '--answer' ) + ' ' + 'Provide the challenge answer non-interactively' ) ;
53+ console . log ( ) ;
3354 console . log ( chalk . bold ( 'Examples:' ) ) ;
3455 console . log ( ' npx atxp agent create' ) ;
3556 console . log ( ' npx atxp agent list' ) ;
57+ console . log ( ' npx atxp agent register' ) ;
58+ console . log ( ' npx atxp agent register --server http://localhost:8016' ) ;
3659 console . log ( ' CONNECTION_TOKEN=<agent_token> npx atxp email inbox' ) ;
3760}
3861
@@ -51,6 +74,10 @@ export async function agentCommand(subCommand: string): Promise<void> {
5174 await listAgents ( ) ;
5275 break ;
5376
77+ case 'register' :
78+ await registerAgent ( ) ;
79+ break ;
80+
5481 default :
5582 console . error ( chalk . red ( `Unknown agent command: ${ subCommand } ` ) ) ;
5683 console . log ( ) ;
@@ -105,6 +132,118 @@ async function createAgent(): Promise<void> {
105132 console . log ( ' ' + chalk . yellow ( `CONNECTION_TOKEN=${ data . connectionToken } npx atxp email inbox` ) ) ;
106133}
107134
135+ function getArgValue ( flag : string ) : string | undefined {
136+ const index = process . argv . findIndex ( ( arg ) => arg === flag ) ;
137+ return index !== - 1 ? process . argv [ index + 1 ] : undefined ;
138+ }
139+
140+ function promptForInput ( prompt : string ) : Promise < string > {
141+ const rl = createInterface ( { input : process . stdin , output : process . stdout } ) ;
142+ return new Promise ( ( resolve ) => {
143+ rl . question ( prompt , ( answer ) => {
144+ rl . close ( ) ;
145+ resolve ( answer . trim ( ) ) ;
146+ } ) ;
147+ } ) ;
148+ }
149+
150+ async function registerAgent ( ) : Promise < void > {
151+ const baseUrl = getArgValue ( '--server' ) || getBaseUrl ( ) ;
152+ const presetAnswer = getArgValue ( '--answer' ) ;
153+
154+ // Step 1: Get challenge
155+ console . log ( chalk . gray ( `Requesting challenge from ${ baseUrl } ...` ) ) ;
156+
157+ const challengeRes = await fetch ( `${ baseUrl } /agents/register` , {
158+ method : 'POST' ,
159+ headers : { 'Content-Type' : 'application/json' } ,
160+ } ) ;
161+
162+ if ( ! challengeRes . ok ) {
163+ const body = await challengeRes . json ( ) . catch ( ( ) => ( { } ) ) as Record < string , string > ;
164+ console . error ( chalk . red ( `Error: ${ body . error_description || body . error || challengeRes . statusText } ` ) ) ;
165+ process . exit ( 1 ) ;
166+ }
167+
168+ const challenge = await challengeRes . json ( ) as {
169+ registration_id : string ;
170+ challenge : string ;
171+ instructions : string ;
172+ expires_at : string ;
173+ } ;
174+
175+ // Decode base64 challenge and instructions
176+ const decodedChallenge = Buffer . from ( challenge . challenge , 'base64' ) . toString ( 'utf-8' ) ;
177+ const decodedInstructions = Buffer . from ( challenge . instructions , 'base64' ) . toString ( 'utf-8' ) ;
178+
179+ console . log ( ) ;
180+ console . log ( chalk . bold ( 'Challenge:' ) ) ;
181+ console . log ( ' ' + chalk . yellow ( decodedChallenge ) ) ;
182+ console . log ( ) ;
183+ console . log ( chalk . bold ( 'Instructions:' ) ) ;
184+ console . log ( ' ' + decodedInstructions ) ;
185+ console . log ( ) ;
186+ console . log ( chalk . gray ( `Registration ID: ${ challenge . registration_id } ` ) ) ;
187+ console . log ( chalk . gray ( `Expires at: ${ challenge . expires_at } ` ) ) ;
188+ console . log ( ) ;
189+
190+ // Step 2: Get answer
191+ let answer : string ;
192+ if ( presetAnswer ) {
193+ answer = presetAnswer ;
194+ console . log ( chalk . gray ( `Using provided answer: ${ answer } ` ) ) ;
195+ } else {
196+ answer = await promptForInput ( chalk . bold ( 'Your answer: ' ) ) ;
197+ if ( ! answer ) {
198+ console . error ( chalk . red ( 'No answer provided.' ) ) ;
199+ process . exit ( 1 ) ;
200+ }
201+ }
202+
203+ // Step 3: Verify and create account
204+ console . log ( ) ;
205+ console . log ( chalk . gray ( 'Verifying answer and creating account...' ) ) ;
206+
207+ const verifyRes = await fetch ( `${ baseUrl } /agents/register/verify` , {
208+ method : 'POST' ,
209+ headers : { 'Content-Type' : 'application/json' } ,
210+ body : JSON . stringify ( {
211+ registration_id : challenge . registration_id ,
212+ answer,
213+ } ) ,
214+ } ) ;
215+
216+ if ( ! verifyRes . ok ) {
217+ const body = await verifyRes . json ( ) . catch ( ( ) => ( { } ) ) as Record < string , string > ;
218+ console . error ( chalk . red ( `Error: ${ body . error_description || body . error || verifyRes . statusText } ` ) ) ;
219+ process . exit ( 1 ) ;
220+ }
221+
222+ const data = await verifyRes . json ( ) as {
223+ agentId : string ;
224+ connectionToken : string ;
225+ connectionString : string ;
226+ email : string ;
227+ walletAddress : string ;
228+ fundedAmount : string ;
229+ } ;
230+
231+ console . log ( ) ;
232+ console . log ( chalk . green . bold ( 'Agent self-registered successfully!' ) ) ;
233+ console . log ( ) ;
234+ console . log ( ' ' + chalk . bold ( 'Agent ID:' ) + ' ' + data . agentId ) ;
235+ console . log ( ' ' + chalk . bold ( 'Email:' ) + ' ' + chalk . cyan ( data . email ) ) ;
236+ console . log ( ' ' + chalk . bold ( 'Connection Token:' ) + ' ' + data . connectionToken ) ;
237+ console . log ( ' ' + chalk . bold ( 'Wallet:' ) + ' ' + data . walletAddress ) ;
238+ console . log ( ' ' + chalk . bold ( 'Funded:' ) + ' ' + data . fundedAmount + ' IOU' ) ;
239+ console . log ( ) ;
240+ console . log ( chalk . bold ( 'Connection String:' ) ) ;
241+ console . log ( ' ' + chalk . cyan ( data . connectionString ) ) ;
242+ console . log ( ) ;
243+ console . log ( chalk . bold ( 'Use this to authenticate as the agent:' ) ) ;
244+ console . log ( ' ' + chalk . yellow ( `npx atxp login --token "${ data . connectionString } "` ) ) ;
245+ }
246+
108247async function listAgents ( ) : Promise < void > {
109248 const { baseUrl, token } = getAccountsAuth ( ) ;
110249
0 commit comments