From c26bc2c8ed7b07301f8ba5991943fa85acb35f0a Mon Sep 17 00:00:00 2001 From: Emilio Amaya Date: Wed, 28 Jan 2026 12:46:38 -0500 Subject: [PATCH 1/2] feat(paas): add analytics list and schema commands Add two new CLI commands for analytics: - `npx atxp paas analytics list` - List all available analytics datasets - `npx atxp paas analytics schema ` - Show column names for a dataset Co-Authored-By: Claude Opus 4.5 --- .../atxp/src/commands/paas/analytics.test.ts | 30 +++++++++++++++++++ packages/atxp/src/commands/paas/analytics.ts | 22 ++++++++++++++ packages/atxp/src/commands/paas/index.ts | 18 +++++++++-- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/packages/atxp/src/commands/paas/analytics.test.ts b/packages/atxp/src/commands/paas/analytics.test.ts index 45048ca..0ffdd87 100644 --- a/packages/atxp/src/commands/paas/analytics.test.ts +++ b/packages/atxp/src/commands/paas/analytics.test.ts @@ -10,6 +10,8 @@ import { analyticsQueryCommand, analyticsEventsCommand, analyticsStatsCommand, + analyticsListCommand, + analyticsSchemaCommand, } from './analytics.js'; describe('Analytics Commands', () => { @@ -147,4 +149,32 @@ describe('Analytics Commands', () => { expect(console.error).toHaveBeenCalled(); }); }); + + describe('analyticsListCommand', () => { + it('should list all analytics datasets', async () => { + vi.mocked(callTool).mockResolvedValue('{"datasets": ["events", "page_views"]}'); + + await analyticsListCommand(); + + expect(callTool).toHaveBeenCalledWith('paas.mcp.atxp.ai', 'list_analytics_datasets', {}); + expect(console.log).toHaveBeenCalledWith('{"datasets": ["events", "page_views"]}'); + }); + }); + + describe('analyticsSchemaCommand', () => { + it('should get schema for a dataset', async () => { + vi.mocked(callTool).mockResolvedValue('{"columns": ["timestamp", "event_name", "user_id"]}'); + + await analyticsSchemaCommand({ dataset: 'events' }); + + expect(callTool).toHaveBeenCalledWith('paas.mcp.atxp.ai', 'get_dataset_schema', { + dataset: 'events', + }); + }); + + it('should exit with error when dataset is missing', async () => { + await expect(analyticsSchemaCommand({})).rejects.toThrow('process.exit called'); + expect(console.error).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/atxp/src/commands/paas/analytics.ts b/packages/atxp/src/commands/paas/analytics.ts index ea0ab1d..8e7e97f 100644 --- a/packages/atxp/src/commands/paas/analytics.ts +++ b/packages/atxp/src/commands/paas/analytics.ts @@ -93,3 +93,25 @@ export async function analyticsStatsCommand(options: AnalyticsStatsOptions): Pro const result = await callTool(SERVER, 'get_analytics_stats', args); console.log(result); } + +export async function analyticsListCommand(): Promise { + const result = await callTool(SERVER, 'list_analytics_datasets', {}); + console.log(result); +} + +interface AnalyticsSchemaOptions { + dataset?: string; +} + +export async function analyticsSchemaCommand(options: AnalyticsSchemaOptions): Promise { + if (!options.dataset) { + console.error(chalk.red('Error: Dataset name is required')); + console.log(`Usage: ${chalk.cyan('npx atxp paas analytics schema ')}`); + console.log(); + console.log('Run ' + chalk.cyan('npx atxp paas analytics list') + ' to see available datasets.'); + process.exit(1); + } + + const result = await callTool(SERVER, 'get_dataset_schema', { dataset: options.dataset }); + console.log(result); +} diff --git a/packages/atxp/src/commands/paas/index.ts b/packages/atxp/src/commands/paas/index.ts index 29d77d8..bd01e93 100644 --- a/packages/atxp/src/commands/paas/index.ts +++ b/packages/atxp/src/commands/paas/index.ts @@ -32,6 +32,8 @@ import { analyticsQueryCommand, analyticsEventsCommand, analyticsStatsCommand, + analyticsListCommand, + analyticsSchemaCommand, } from './analytics.js'; import { secretsSetCommand, @@ -112,6 +114,8 @@ function showPaasHelp(): void { console.log(); console.log(chalk.bold('Analytics Commands:')); + console.log(' ' + chalk.cyan('paas analytics list') + ' List analytics datasets'); + console.log(' ' + chalk.cyan('paas analytics schema') + ' ' + chalk.yellow('') + ' Show dataset columns'); console.log(' ' + chalk.cyan('paas analytics query') + ' Query analytics data'); console.log(' ' + chalk.cyan('paas analytics events') + ' List analytics events'); console.log(' ' + chalk.cyan('paas analytics stats') + ' Get analytics statistics'); @@ -457,10 +461,20 @@ async function handleDnsCommand( async function handleAnalyticsCommand( subCommand: string, - _args: string[], + args: string[], options: PaasOptions ): Promise { switch (subCommand) { + case 'list': + await analyticsListCommand(); + break; + + case 'schema': { + const dataset = args[0]; + await analyticsSchemaCommand({ dataset }); + break; + } + case 'query': await analyticsQueryCommand({ sql: options.sql, @@ -485,7 +499,7 @@ async function handleAnalyticsCommand( default: console.error(chalk.red(`Unknown analytics command: ${subCommand}`)); - console.log('Available commands: query, events, stats'); + console.log('Available commands: list, schema, query, events, stats'); process.exit(1); } } From cf2e437e884b63dbfe83c5d4174cc7330e424953 Mon Sep 17 00:00:00 2001 From: Emilio Amaya Date: Wed, 28 Jan 2026 12:49:14 -0500 Subject: [PATCH 2/2] test: add integration tests for analytics list and schema commands - Update analytics subcommands list to include 'list' and 'schema' - Add test for parsing analytics schema dataset argument - Add test for identifying analytics list command Co-Authored-By: Claude Opus 4.5 --- packages/atxp/src/index.test.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/atxp/src/index.test.ts b/packages/atxp/src/index.test.ts index 60100bc..94c013f 100644 --- a/packages/atxp/src/index.test.ts +++ b/packages/atxp/src/index.test.ts @@ -257,16 +257,43 @@ describe('ATXP CLI', () => { }); it('should identify analytics subcommands', () => { - const analyticsCommands = ['query', 'events', 'stats']; + const analyticsCommands = ['list', 'schema', 'query', 'events', 'stats']; const isAnalyticsCommand = (subCommand: string) => { return analyticsCommands.includes(subCommand); }; + expect(isAnalyticsCommand('list')).toBe(true); + expect(isAnalyticsCommand('schema')).toBe(true); expect(isAnalyticsCommand('query')).toBe(true); expect(isAnalyticsCommand('events')).toBe(true); expect(isAnalyticsCommand('stats')).toBe(true); expect(isAnalyticsCommand('deploy')).toBe(false); }); + + it('should parse analytics schema dataset argument', () => { + const parseAnalyticsSchemaArgs = (argv: string[]) => { + // npx atxp paas analytics schema + if (argv[2] === 'paas' && argv[3] === 'analytics' && argv[4] === 'schema') { + return argv[5]; + } + return undefined; + }; + + expect(parseAnalyticsSchemaArgs(['node', 'script', 'paas', 'analytics', 'schema', 'events'])).toBe('events'); + expect(parseAnalyticsSchemaArgs(['node', 'script', 'paas', 'analytics', 'schema', 'page_views'])).toBe('page_views'); + expect(parseAnalyticsSchemaArgs(['node', 'script', 'paas', 'analytics', 'schema'])).toBe(undefined); + expect(parseAnalyticsSchemaArgs(['node', 'script', 'paas', 'analytics', 'list'])).toBe(undefined); + }); + + it('should parse analytics list command', () => { + const isAnalyticsListCommand = (argv: string[]) => { + return argv[2] === 'paas' && argv[3] === 'analytics' && argv[4] === 'list'; + }; + + expect(isAnalyticsListCommand(['node', 'script', 'paas', 'analytics', 'list'])).toBe(true); + expect(isAnalyticsListCommand(['node', 'script', 'paas', 'analytics', 'query'])).toBe(false); + expect(isAnalyticsListCommand(['node', 'script', 'paas', 'db', 'list'])).toBe(false); + }); }); });