From d053f244a0dd796bd327b41385dab689098f8473 Mon Sep 17 00:00:00 2001 From: R-M-Naveen Date: Thu, 12 Feb 2026 11:21:16 -0600 Subject: [PATCH] feat: Add --registration-id flag for two-step agent registration Allows agents to resume a previous registration challenge without fetching a new one. When both --registration-id and --answer are provided, the CLI skips step 1 (challenge fetch) and goes directly to verification. This enables non-interactive registration flows. Co-Authored-By: Claude Opus 4.6 --- packages/atxp/src/commands/agent.ts | 79 +++++++++++++++++------------ 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/packages/atxp/src/commands/agent.ts b/packages/atxp/src/commands/agent.ts index 8ee3a5f..4dd8255 100644 --- a/packages/atxp/src/commands/agent.ts +++ b/packages/atxp/src/commands/agent.ts @@ -48,14 +48,16 @@ function showAgentHelp(): void { console.log(' - A connection token for SDK/CLI access'); console.log(); console.log(chalk.bold('Register Options:')); - console.log(' ' + chalk.yellow('--server') + ' ' + 'Accounts server URL (default: https://accounts.atxp.ai)'); - console.log(' ' + chalk.yellow('--answer') + ' ' + 'Provide the challenge answer non-interactively'); + console.log(' ' + chalk.yellow('--server') + ' ' + 'Accounts server URL (default: https://accounts.atxp.ai)'); + console.log(' ' + chalk.yellow('--answer') + ' ' + 'Provide the challenge answer non-interactively'); + console.log(' ' + chalk.yellow('--registration-id') + ' ' + 'Resume a previous challenge (skip fetching a new one)'); console.log(); console.log(chalk.bold('Examples:')); console.log(' npx atxp agent create'); console.log(' npx atxp agent list'); console.log(' npx atxp agent register'); console.log(' npx atxp agent register --server http://localhost:8016'); + console.log(' npx atxp agent register --registration-id reg_xxx --answer "535.00"'); console.log(' CONNECTION_TOKEN= npx atxp email inbox'); } @@ -150,42 +152,53 @@ function promptForInput(prompt: string): Promise { async function registerAgent(): Promise { const baseUrl = getArgValue('--server') || getBaseUrl(); const presetAnswer = getArgValue('--answer'); + const presetRegistrationId = getArgValue('--registration-id'); - // Step 1: Get challenge - console.log(chalk.gray(`Requesting challenge from ${baseUrl}...`)); + let registrationId: string; - const challengeRes = await fetch(`${baseUrl}/agents/register`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - }); + if (presetRegistrationId) { + // Resume a previous challenge — skip fetching a new one + registrationId = presetRegistrationId; + console.log(chalk.gray(`Resuming registration ${registrationId}...`)); + } else { + // Step 1: Get challenge + console.log(chalk.gray(`Requesting challenge from ${baseUrl}...`)); - if (!challengeRes.ok) { - const body = await challengeRes.json().catch(() => ({})) as Record; - console.error(chalk.red(`Error: ${body.error_description || body.error || challengeRes.statusText}`)); - process.exit(1); - } + const challengeRes = await fetch(`${baseUrl}/agents/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + }); - const challenge = await challengeRes.json() as { - registration_id: string; - challenge: string; - instructions: string; - expires_at: string; - }; + if (!challengeRes.ok) { + const body = await challengeRes.json().catch(() => ({})) as Record; + console.error(chalk.red(`Error: ${body.error_description || body.error || challengeRes.statusText}`)); + process.exit(1); + } - // Decode base64 challenge and instructions - const decodedChallenge = Buffer.from(challenge.challenge, 'base64').toString('utf-8'); - const decodedInstructions = Buffer.from(challenge.instructions, 'base64').toString('utf-8'); + const challenge = await challengeRes.json() as { + registration_id: string; + challenge: string; + instructions: string; + expires_at: string; + }; - console.log(); - console.log(chalk.bold('Challenge:')); - console.log(' ' + chalk.yellow(decodedChallenge)); - console.log(); - console.log(chalk.bold('Instructions:')); - console.log(' ' + decodedInstructions); - console.log(); - console.log(chalk.gray(`Registration ID: ${challenge.registration_id}`)); - console.log(chalk.gray(`Expires at: ${challenge.expires_at}`)); - console.log(); + registrationId = challenge.registration_id; + + // Decode base64 challenge and instructions + const decodedChallenge = Buffer.from(challenge.challenge, 'base64').toString('utf-8'); + const decodedInstructions = Buffer.from(challenge.instructions, 'base64').toString('utf-8'); + + console.log(); + console.log(chalk.bold('Challenge:')); + console.log(' ' + chalk.yellow(decodedChallenge)); + console.log(); + console.log(chalk.bold('Instructions:')); + console.log(' ' + decodedInstructions); + console.log(); + console.log(chalk.gray(`Registration ID: ${registrationId}`)); + console.log(chalk.gray(`Expires at: ${challenge.expires_at}`)); + console.log(); + } // Step 2: Get answer let answer: string; @@ -208,7 +221,7 @@ async function registerAgent(): Promise { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - registration_id: challenge.registration_id, + registration_id: registrationId, answer, }), });