From e19d6219421c7c2bc1f25c19bf859c86818ba4e9 Mon Sep 17 00:00:00 2001 From: Emilio Amaya Date: Wed, 28 Jan 2026 12:55:40 -0500 Subject: [PATCH 1/6] feat: Add worker info command to display worker details - Add workerInfoCommand() function that calls get_worker_info tool - Display worker name, URL, creation/modification timestamps - Format bindings output: databases, storage, analytics, env vars, secrets - Add 'info' case to handleWorkerCommand() switch - Update help text and error messages to include info command ATXP-1430 Co-Authored-By: Claude Opus 4.5 --- packages/atxp/src/commands/paas/index.ts | 12 ++- packages/atxp/src/commands/paas/worker.ts | 90 +++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/packages/atxp/src/commands/paas/index.ts b/packages/atxp/src/commands/paas/index.ts index 7462a94..ac763e8 100644 --- a/packages/atxp/src/commands/paas/index.ts +++ b/packages/atxp/src/commands/paas/index.ts @@ -4,6 +4,7 @@ import { workerListCommand, workerLogsCommand, workerDeleteCommand, + workerInfoCommand, } from './worker.js'; import { dbCreateCommand, @@ -163,6 +164,15 @@ async function handleWorkerCommand( await workerListCommand(); break; + case 'info': + if (!name) { + console.error(chalk.red('Error: Worker name is required')); + console.log(`Usage: ${chalk.cyan('npx atxp paas worker info ')}`); + process.exit(1); + } + await workerInfoCommand(name); + break; + case 'logs': if (!name) { console.error(chalk.red('Error: Worker name is required')); @@ -189,7 +199,7 @@ async function handleWorkerCommand( default: console.error(chalk.red(`Unknown worker command: ${subCommand}`)); - console.log('Available commands: deploy, list, logs, delete'); + console.log('Available commands: deploy, list, info, logs, delete'); process.exit(1); } } diff --git a/packages/atxp/src/commands/paas/worker.ts b/packages/atxp/src/commands/paas/worker.ts index 727d36f..e6e7694 100644 --- a/packages/atxp/src/commands/paas/worker.ts +++ b/packages/atxp/src/commands/paas/worker.ts @@ -391,3 +391,93 @@ export async function workerDeleteCommand(name: string): Promise { const result = await callTool(SERVER, 'delete_worker', { name }); console.log(result); } + +interface WorkerInfoResponse { + success: boolean; + error?: string; + worker?: { + name: string; + url: string; + createdOn: string; + modifiedOn: string; + bindings: { + databases: Array<{ binding: string; databaseId: string }>; + storage: Array<{ binding: string; bucketName: string }>; + analytics: Array<{ binding: string; dataset: string }>; + envVars: string[]; + secrets: string[]; + }; + }; +} + +export async function workerInfoCommand(name: string): Promise { + const result = await callTool(SERVER, 'get_worker_info', { name }); + + try { + const data = JSON.parse(result) as WorkerInfoResponse; + + if (!data.success || !data.worker) { + console.error(chalk.red(`Error: ${data.error || 'Worker not found'}`)); + process.exit(1); + } + + const worker = data.worker; + + console.log(chalk.bold(`Worker: ${worker.name}`)); + console.log(); + console.log(`${chalk.gray('URL:')} ${worker.url}`); + console.log(`${chalk.gray('Created:')} ${worker.createdOn}`); + console.log(`${chalk.gray('Modified:')} ${worker.modifiedOn}`); + + const { bindings } = worker; + const hasBindings = + bindings.databases.length > 0 || + bindings.storage.length > 0 || + bindings.analytics.length > 0 || + bindings.envVars.length > 0 || + bindings.secrets.length > 0; + + if (hasBindings) { + console.log(); + console.log(chalk.bold('Bindings:')); + + if (bindings.databases.length > 0) { + console.log(` ${chalk.cyan('Databases:')}`); + for (const db of bindings.databases) { + console.log(` ${db.binding} -> ${chalk.gray(db.databaseId)}`); + } + } + + if (bindings.storage.length > 0) { + console.log(` ${chalk.cyan('Storage:')}`); + for (const bucket of bindings.storage) { + console.log(` ${bucket.binding} -> ${chalk.gray(bucket.bucketName)}`); + } + } + + if (bindings.analytics.length > 0) { + console.log(` ${chalk.cyan('Analytics:')}`); + for (const analytics of bindings.analytics) { + console.log(` ${analytics.binding} -> ${chalk.gray(analytics.dataset)}`); + } + } + + if (bindings.envVars.length > 0) { + console.log(` ${chalk.cyan('Environment Variables:')}`); + for (const envVar of bindings.envVars) { + console.log(` ${envVar}`); + } + } + + if (bindings.secrets.length > 0) { + console.log(` ${chalk.cyan('Secrets:')}`); + for (const secret of bindings.secrets) { + console.log(` ${secret}`); + } + } + } + } catch { + // If parsing fails, just output the raw result + console.log(result); + } +} From 812f1845acbc701ffd7bd7af8ce5bccf48c37d31 Mon Sep 17 00:00:00 2001 From: Emilio Amaya Date: Wed, 28 Jan 2026 16:12:35 -0500 Subject: [PATCH 2/6] chore: trigger CI From 663358c5b2cd01d45226058c8aeeef8536dea928 Mon Sep 17 00:00:00 2001 From: Emilio Amaya Date: Wed, 28 Jan 2026 16:13:36 -0500 Subject: [PATCH 3/6] test: add tests for workerInfoCommand Co-Authored-By: Claude Opus 4.5 --- .../atxp/src/commands/paas/worker.test.ts | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/packages/atxp/src/commands/paas/worker.test.ts b/packages/atxp/src/commands/paas/worker.test.ts index 8d984ec..80fe6bf 100644 --- a/packages/atxp/src/commands/paas/worker.test.ts +++ b/packages/atxp/src/commands/paas/worker.test.ts @@ -20,6 +20,7 @@ import { workerListCommand, workerLogsCommand, workerDeleteCommand, + workerInfoCommand, parseEnvArg, parseEnvFile, validateEnvVarName, @@ -689,4 +690,89 @@ describe('Worker Commands', () => { }); }); }); + + describe('workerInfoCommand', () => { + it('should get worker info and display details', async () => { + const mockResponse = JSON.stringify({ + success: true, + worker: { + name: 'my-worker', + url: 'https://my-worker.example.com', + createdOn: '2024-01-01T00:00:00Z', + modifiedOn: '2024-01-02T00:00:00Z', + bindings: { + databases: [], + storage: [], + analytics: [], + envVars: [], + secrets: [], + }, + }, + }); + vi.mocked(callTool).mockResolvedValue(mockResponse); + + await workerInfoCommand('my-worker'); + + expect(callTool).toHaveBeenCalledWith('paas.mcp.atxp.ai', 'get_worker_info', { + name: 'my-worker', + }); + expect(console.log).toHaveBeenCalled(); + }); + + it('should display all binding types when present', async () => { + const mockResponse = JSON.stringify({ + success: true, + worker: { + name: 'my-worker', + url: 'https://my-worker.example.com', + createdOn: '2024-01-01T00:00:00Z', + modifiedOn: '2024-01-02T00:00:00Z', + bindings: { + databases: [{ binding: 'DB', databaseId: 'db-123' }], + storage: [{ binding: 'BUCKET', bucketName: 'my-bucket' }], + analytics: [{ binding: 'ANALYTICS', dataset: 'my-dataset' }], + envVars: ['API_URL', 'DEBUG'], + secrets: ['API_KEY'], + }, + }, + }); + vi.mocked(callTool).mockResolvedValue(mockResponse); + + await workerInfoCommand('my-worker'); + + expect(callTool).toHaveBeenCalledWith('paas.mcp.atxp.ai', 'get_worker_info', { + name: 'my-worker', + }); + expect(console.log).toHaveBeenCalled(); + }); + + it('should exit with error when worker is not found', async () => { + const mockResponse = JSON.stringify({ + success: false, + error: 'Worker not found', + }); + vi.mocked(callTool).mockResolvedValue(mockResponse); + + await expect(workerInfoCommand('nonexistent-worker')).rejects.toThrow('process.exit called'); + expect(console.error).toHaveBeenCalled(); + }); + + it('should handle error response without error message', async () => { + const mockResponse = JSON.stringify({ + success: false, + }); + vi.mocked(callTool).mockResolvedValue(mockResponse); + + await expect(workerInfoCommand('my-worker')).rejects.toThrow('process.exit called'); + expect(console.error).toHaveBeenCalled(); + }); + + it('should fallback to raw output when JSON parsing fails', async () => { + vi.mocked(callTool).mockResolvedValue('not valid json'); + + await workerInfoCommand('my-worker'); + + expect(console.log).toHaveBeenCalledWith('not valid json'); + }); + }); }); From 59101743260755860942823f46283affd41c2d3e Mon Sep 17 00:00:00 2001 From: Emilio Amaya Date: Wed, 28 Jan 2026 16:14:54 -0500 Subject: [PATCH 4/6] fix: correct process.exit mocking in workerInfoCommand tests Co-Authored-By: Claude Opus 4.5 --- packages/atxp/src/commands/paas/worker.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/atxp/src/commands/paas/worker.test.ts b/packages/atxp/src/commands/paas/worker.test.ts index 80fe6bf..241bf24 100644 --- a/packages/atxp/src/commands/paas/worker.test.ts +++ b/packages/atxp/src/commands/paas/worker.test.ts @@ -746,15 +746,18 @@ describe('Worker Commands', () => { expect(console.log).toHaveBeenCalled(); }); - it('should exit with error when worker is not found', async () => { + it('should call process.exit when worker is not found', async () => { const mockResponse = JSON.stringify({ success: false, error: 'Worker not found', }); vi.mocked(callTool).mockResolvedValue(mockResponse); + const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never); + + await workerInfoCommand('nonexistent-worker'); - await expect(workerInfoCommand('nonexistent-worker')).rejects.toThrow('process.exit called'); expect(console.error).toHaveBeenCalled(); + expect(exitSpy).toHaveBeenCalledWith(1); }); it('should handle error response without error message', async () => { @@ -762,9 +765,12 @@ describe('Worker Commands', () => { success: false, }); vi.mocked(callTool).mockResolvedValue(mockResponse); + const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never); + + await workerInfoCommand('my-worker'); - await expect(workerInfoCommand('my-worker')).rejects.toThrow('process.exit called'); expect(console.error).toHaveBeenCalled(); + expect(exitSpy).toHaveBeenCalledWith(1); }); it('should fallback to raw output when JSON parsing fails', async () => { From d86b5ad8ffc0794bb4b3b8558dca7cd2e014cffe Mon Sep 17 00:00:00 2001 From: Emilio Amaya Date: Wed, 28 Jan 2026 16:27:37 -0500 Subject: [PATCH 5/6] chore: trigger CI From cb77a335051f955efecc66d44f2f5ff997fce640 Mon Sep 17 00:00:00 2001 From: Emilio Amaya Date: Wed, 28 Jan 2026 16:33:41 -0500 Subject: [PATCH 6/6] chore: trigger CI build --- packages/atxp/src/commands/paas/worker.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/atxp/src/commands/paas/worker.test.ts b/packages/atxp/src/commands/paas/worker.test.ts index 241bf24..8761eb6 100644 --- a/packages/atxp/src/commands/paas/worker.test.ts +++ b/packages/atxp/src/commands/paas/worker.test.ts @@ -782,3 +782,4 @@ describe('Worker Commands', () => { }); }); }); +