diff --git a/packages/atxp/src/call-tool.ts b/packages/atxp/src/call-tool.ts index 52119df..3ca7c2e 100644 --- a/packages/atxp/src/call-tool.ts +++ b/packages/atxp/src/call-tool.ts @@ -2,6 +2,7 @@ import { atxpClient, ATXPAccount } from '@atxp/client'; import chalk from 'chalk'; import { getConnection } from './config.js'; import { FileOAuthDb } from './file-oauth-db.js'; +import { getCliLogger } from './verbose.js'; let oAuthDb: FileOAuthDb | null = null; function getOAuthDb(): FileOAuthDb { @@ -33,6 +34,7 @@ export async function callTool( mcpServer: `https://${server}`, account: new ATXPAccount(connection), oAuthDb: getOAuthDb(), + logger: getCliLogger(), }); const result = (await client.callTool({ diff --git a/packages/atxp/src/help.ts b/packages/atxp/src/help.ts index 6bd0878..a9f1cd3 100644 --- a/packages/atxp/src/help.ts +++ b/packages/atxp/src/help.ts @@ -9,6 +9,10 @@ export function showHelp(): void { console.log(' npx atxp [options]'); console.log(); + console.log(chalk.bold('Global Options:')); + console.log(' ' + chalk.yellow('--verbose, -v') + ' ' + 'Enable verbose output (OAuth debug logs)'); + console.log(); + console.log(chalk.bold('Authentication:')); console.log(' ' + chalk.cyan('login') + ' ' + 'Log in to ATXP (save connection string)'); console.log(); diff --git a/packages/atxp/src/verbose.test.ts b/packages/atxp/src/verbose.test.ts new file mode 100644 index 0000000..9dfcfad --- /dev/null +++ b/packages/atxp/src/verbose.test.ts @@ -0,0 +1,103 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; + +describe('Verbose Mode', () => { + let originalArgv: string[]; + let originalDebug: string | undefined; + + beforeEach(() => { + originalArgv = process.argv; + originalDebug = process.env.DEBUG; + }); + + afterEach(() => { + process.argv = originalArgv; + if (originalDebug === undefined) { + delete process.env.DEBUG; + } else { + process.env.DEBUG = originalDebug; + } + }); + + describe('isVerboseMode', () => { + it('should return true when --verbose flag is present', async () => { + process.argv = ['node', 'atxp', 'search', '--verbose']; + delete process.env.DEBUG; + + // Re-import to get fresh evaluation + const { isVerboseMode } = await import('./verbose.js'); + expect(isVerboseMode()).toBe(true); + }); + + it('should return true when -v flag is present', async () => { + process.argv = ['node', 'atxp', 'search', '-v']; + delete process.env.DEBUG; + + const { isVerboseMode } = await import('./verbose.js'); + expect(isVerboseMode()).toBe(true); + }); + + it('should return true when DEBUG=atxp is set', async () => { + process.argv = ['node', 'atxp', 'search']; + process.env.DEBUG = 'atxp'; + + const { isVerboseMode } = await import('./verbose.js'); + expect(isVerboseMode()).toBe(true); + }); + + it('should return true when DEBUG=1 is set', async () => { + process.argv = ['node', 'atxp', 'search']; + process.env.DEBUG = '1'; + + const { isVerboseMode } = await import('./verbose.js'); + expect(isVerboseMode()).toBe(true); + }); + + it('should return true when DEBUG=true is set', async () => { + process.argv = ['node', 'atxp', 'search']; + process.env.DEBUG = 'true'; + + const { isVerboseMode } = await import('./verbose.js'); + expect(isVerboseMode()).toBe(true); + }); + + it('should return true when DEBUG contains atxp', async () => { + process.argv = ['node', 'atxp', 'search']; + process.env.DEBUG = 'other,atxp,more'; + + const { isVerboseMode } = await import('./verbose.js'); + expect(isVerboseMode()).toBe(true); + }); + + it('should return false when no verbose indicators are present', async () => { + process.argv = ['node', 'atxp', 'search']; + delete process.env.DEBUG; + + const { isVerboseMode } = await import('./verbose.js'); + expect(isVerboseMode()).toBe(false); + }); + + it('should return false when DEBUG is set to unrelated value', async () => { + process.argv = ['node', 'atxp', 'search']; + process.env.DEBUG = 'other'; + + const { isVerboseMode } = await import('./verbose.js'); + expect(isVerboseMode()).toBe(false); + }); + }); + + describe('getCliLogger', () => { + it('should return a ConsoleLogger instance', async () => { + process.argv = ['node', 'atxp', 'search']; + delete process.env.DEBUG; + + const { getCliLogger } = await import('./verbose.js'); + const logger = getCliLogger(); + + expect(logger).toBeDefined(); + expect(typeof logger.debug).toBe('function'); + expect(typeof logger.info).toBe('function'); + expect(typeof logger.warn).toBe('function'); + expect(typeof logger.error).toBe('function'); + }); + }); +}); diff --git a/packages/atxp/src/verbose.ts b/packages/atxp/src/verbose.ts new file mode 100644 index 0000000..0689017 --- /dev/null +++ b/packages/atxp/src/verbose.ts @@ -0,0 +1,15 @@ +import { ConsoleLogger, LogLevel } from '@atxp/common'; + +export function isVerboseMode(): boolean { + if (process.argv.includes('--verbose') || process.argv.includes('-v')) { + return true; + } + const debug = process.env.DEBUG?.toLowerCase(); + return debug === '1' || debug === 'true' || debug?.includes('atxp') === true; +} + +export function getCliLogger(): ConsoleLogger { + return new ConsoleLogger({ + level: isVerboseMode() ? LogLevel.DEBUG : LogLevel.WARN, + }); +}