diff --git a/.changeset/cli-tool.md b/.changeset/cli-tool.md index 8e3a876..04fbbd9 100644 --- a/.changeset/cli-tool.md +++ b/.changeset/cli-tool.md @@ -1,5 +1,4 @@ --- -"@scope3/agentic-client": minor "scope3": minor --- diff --git a/README.md b/README.md index 65d7222..bd6a8bc 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@ TypeScript client for the Scope3 Agentic API with AdCP webhook support. ## Installation ```bash -npm install @scope3/agentic-client +npm install scope3 ``` ## Quick Start ```typescript -import { Scope3AgenticClient } from '@scope3/agentic-client'; +import { Scope3AgenticClient } from 'scope3'; const client = new Scope3AgenticClient({ apiKey: process.env.SCOPE3_API_KEY, @@ -50,9 +50,7 @@ const campaign = await client.campaigns.create({ The CLI dynamically discovers available commands from the API server, ensuring it's always up-to-date. -### Quick Start (Recommended) - -For the shortest command, use the `scope3` package: +### Quick Start ```bash # Use with npx (no install needed) @@ -61,15 +59,6 @@ npx scope3 --help # Or install globally npm install -g scope3 scope3 --help -``` - -### Alternative: Full Package Name - -You can also use the full package name: - -```bash -npm install -g @scope3/agentic-client -npx @scope3/agentic-client --help # Configure authentication scope3 config set apiKey your_api_key_here diff --git a/package.json b/package.json index ca44047..b331f6c 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,7 @@ { - "name": "@scope3/agentic-client", + "name": "scope3", "version": "1.0.5", - "description": "TypeScript client for the Scope3 Agentic API with AdCP webhook support", - "workspaces": [ - "packages/*" - ], + "description": "CLI and TypeScript client for the Scope3 Agentic API with AdCP webhook support", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ @@ -49,7 +46,8 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/scope3data/agentic-client.git" + "url": "git+https://github.com/scope3data/agentic-client.git", + "directory": "." }, "bugs": { "url": "https://github.com/scope3data/agentic-client/issues" diff --git a/packages/scope3-cli/.npmrc b/packages/scope3-cli/.npmrc deleted file mode 100644 index 63b729b..0000000 --- a/packages/scope3-cli/.npmrc +++ /dev/null @@ -1 +0,0 @@ -# Automatically rewrite file: dependencies to version numbers on publish diff --git a/packages/scope3-cli/README.md b/packages/scope3-cli/README.md deleted file mode 100644 index b083782..0000000 --- a/packages/scope3-cli/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# scope3 - -Command-line interface for the Scope3 Agentic API. - -This is a thin wrapper around [@scope3/agentic-client](https://www.npmjs.com/package/@scope3/agentic-client) that provides a shorter package name for easier CLI usage. - -## Installation - -```bash -# Global installation -npm install -g scope3 - -# Or use directly with npx -npx scope3 --help -``` - -## Usage - -```bash -# Configure your API key -scope3 config set apiKey YOUR_API_KEY - -# List available commands -scope3 list-tools - -# Use dynamic commands -scope3 brand-agent list -scope3 campaign create --name "My Campaign" - -# Output formats -scope3 media-product list --format json -scope3 media-product list --format list -scope3 media-product list --format table # default - -# Environment switching -scope3 --environment staging brand-agent list - -# Debug mode -scope3 --debug campaign get --campaignId 123 -``` - -## Documentation - -For full documentation, see [@scope3/agentic-client](https://www.npmjs.com/package/@scope3/agentic-client) - -## License - -MIT diff --git a/packages/scope3-cli/cli.js b/packages/scope3-cli/cli.js deleted file mode 100755 index c29d1f0..0000000 --- a/packages/scope3-cli/cli.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node -// Thin wrapper that re-exports the CLI from @scope3/agentic-client -require('@scope3/agentic-client/dist/cli.js'); diff --git a/packages/scope3-cli/package.json b/packages/scope3-cli/package.json deleted file mode 100644 index 05ff4df..0000000 --- a/packages/scope3-cli/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "scope3", - "version": "1.0.5", - "description": "Scope3 Agentic CLI - Command-line interface for the Scope3 Agentic API", - "bin": { - "scope3": "./cli.js" - }, - "files": [ - "cli.js", - "README.md" - ], - "keywords": [ - "scope3", - "agentic", - "cli", - "advertising", - "adtech" - ], - "author": "Scope3", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/scope3data/agentic-client.git", - "directory": "packages/scope3-cli" - }, - "homepage": "https://github.com/scope3data/agentic-client#readme", - "bugs": { - "url": "https://github.com/scope3data/agentic-client/issues" - }, - "dependencies": { - "@scope3/agentic-client": "file:../.." - }, - "publishConfig": { - "access": "public" - } -} diff --git a/src/__tests__/client-mcp.test.ts b/src/__tests__/client-mcp.test.ts index a0bc4ff..498dad4 100644 --- a/src/__tests__/client-mcp.test.ts +++ b/src/__tests__/client-mcp.test.ts @@ -173,10 +173,10 @@ describe('Scope3Client MCP Protocol', () => { }); await expect(client['callTool']('test_tool', {})).rejects.toThrow( - 'MCP API returned response without structuredContent' + 'API Error: Missing structured data' ); await expect(client['callTool']('test_tool', {})).rejects.toThrow( - 'This violates the Scope3 API specification' + 'This is an API bug that needs to be fixed upstream' ); }); @@ -187,7 +187,7 @@ describe('Scope3Client MCP Protocol', () => { }); await expect(client['callTool']('test_tool', {})).rejects.toThrow( - 'MCP API returned response without structuredContent' + 'API Error: Missing structured data' ); }); @@ -198,7 +198,7 @@ describe('Scope3Client MCP Protocol', () => { }); await expect(client['callTool']('test_tool', {})).rejects.toThrow( - 'MCP API returned response without structuredContent' + 'API Error: Missing structured data' ); }); @@ -208,7 +208,7 @@ describe('Scope3Client MCP Protocol', () => { }); await expect(client['callTool']('test_tool', {})).rejects.toThrow('test_tool'); - await expect(client['callTool']('test_tool', {})).rejects.toThrow('Debug info'); + await expect(client['callTool']('test_tool', {})).rejects.toThrow('API Error'); }); }); @@ -219,7 +219,7 @@ describe('Scope3Client MCP Protocol', () => { }); await expect(client['callTool']('test_tool', {})).rejects.toThrow( - 'MCP API returned response without structuredContent' + 'API Error: Missing structured data' ); }); @@ -229,7 +229,7 @@ describe('Scope3Client MCP Protocol', () => { }); await expect(client['callTool']('test_tool', {})).rejects.toThrow( - 'MCP API returned response without structuredContent' + 'API Error: Missing structured data' ); }); @@ -291,7 +291,7 @@ describe('Scope3Client MCP Protocol', () => { // Should throw before storing debug info await expect(client['callTool']('test_tool', {})).rejects.toThrow( - 'MCP API returned response without structuredContent' + 'API Error: Missing structured data' ); }); diff --git a/src/cli.ts b/src/cli.ts index 5bfed39..72b5997 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -16,7 +16,9 @@ const TOOLS_CACHE_FILE = path.join(CONFIG_DIR, 'tools-cache.json'); const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours interface CliConfig { - apiKey?: string; + apiKey?: string; // Legacy single API key + productionApiKey?: string; // Production-specific API key + stagingApiKey?: string; // Staging-specific API key environment?: 'production' | 'staging'; baseUrl?: string; } @@ -45,6 +47,8 @@ function loadConfig(): CliConfig { try { const fileConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); config.apiKey = fileConfig.apiKey; + config.productionApiKey = fileConfig.productionApiKey; + config.stagingApiKey = fileConfig.stagingApiKey; config.environment = fileConfig.environment; config.baseUrl = fileConfig.baseUrl; } catch (error) { @@ -382,19 +386,39 @@ function createClient( ): Scope3AgenticClient { const config = loadConfig(); - const finalApiKey = apiKey || config.apiKey; + // Determine final environment + const finalEnvironment = environment || config.environment || 'production'; + + // Select API key based on priority: + // 1. Explicitly passed apiKey (--api-key flag) + // 2. Environment-specific key from config (productionApiKey/stagingApiKey) + // 3. Legacy single apiKey from config + let finalApiKey = apiKey; + if (!finalApiKey) { + if (finalEnvironment === 'staging' && config.stagingApiKey) { + finalApiKey = config.stagingApiKey; + } else if (finalEnvironment === 'production' && config.productionApiKey) { + finalApiKey = config.productionApiKey; + } else { + finalApiKey = config.apiKey; // Fallback to legacy key + } + } + if (!finalApiKey) { console.error(chalk.red('Error: API key is required')); console.log('Set it via:'); console.log(' - Environment variable: export SCOPE3_API_KEY=your_key'); console.log(' - Config command: scope3 config set apiKey your_key'); + console.log(' - Or use environment-specific keys:'); + console.log(' scope3 config set productionApiKey your_production_key'); + console.log(' scope3 config set stagingApiKey your_staging_key'); console.log(' - Flag: --api-key your_key'); process.exit(1); } return new Scope3AgenticClient({ apiKey: finalApiKey, - environment: environment || config.environment, + environment: finalEnvironment, baseUrl: baseUrl || config.baseUrl, debug: debug || false, }); @@ -475,12 +499,19 @@ const configCmd = program.command('config').description('Manage CLI configuratio configCmd .command('set') .description('Set configuration value') - .argument('', 'Configuration key (apiKey, environment, or baseUrl)') + .argument( + '', + 'Configuration key (apiKey, productionApiKey, stagingApiKey, environment, or baseUrl)' + ) .argument('', 'Configuration value') .action((key: string, value: string) => { const config = loadConfig(); if (key === 'apiKey') { config.apiKey = value; + } else if (key === 'productionApiKey') { + config.productionApiKey = value; + } else if (key === 'stagingApiKey') { + config.stagingApiKey = value; } else if (key === 'environment') { if (value !== 'production' && value !== 'staging') { console.error(chalk.red(`Error: Invalid environment: ${value}`)); @@ -492,7 +523,7 @@ configCmd config.baseUrl = value; } else { console.error(chalk.red(`Error: Unknown config key: ${key}`)); - console.log('Valid keys: apiKey, environment, baseUrl'); + console.log('Valid keys: apiKey, productionApiKey, stagingApiKey, environment, baseUrl'); process.exit(1); } saveConfig(config); @@ -501,7 +532,10 @@ configCmd configCmd .command('get') .description('Get configuration value') - .argument('[key]', 'Configuration key (apiKey or baseUrl). If omitted, shows all config') + .argument( + '[key]', + 'Configuration key (apiKey, productionApiKey, stagingApiKey, environment, or baseUrl). If omitted, shows all config' + ) .action((key?: string) => { const config = loadConfig(); if (!key) { @@ -510,6 +544,12 @@ configCmd if (safeConfig.apiKey) { safeConfig.apiKey = safeConfig.apiKey.substring(0, 8) + '...[REDACTED]'; } + if (safeConfig.productionApiKey) { + safeConfig.productionApiKey = safeConfig.productionApiKey.substring(0, 8) + '...[REDACTED]'; + } + if (safeConfig.stagingApiKey) { + safeConfig.stagingApiKey = safeConfig.stagingApiKey.substring(0, 8) + '...[REDACTED]'; + } console.log(JSON.stringify(safeConfig, null, 2)); } else if (key in config) { console.log(config[key as keyof CliConfig]); @@ -689,9 +729,17 @@ async function setupDynamicCommands() { } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); const errorStack = error instanceof Error ? error.stack : undefined; - logger.error('Tool execution failed', error, { toolName: tool.name }); + + // Only log to logger in debug mode + if (globalOpts.debug) { + logger.error('Tool execution failed', error, { toolName: tool.name }); + } + console.error(chalk.red('Error:'), errorMessage); - if (errorStack && process.env.DEBUG) { + + // Show stack trace only in debug mode + if (errorStack && globalOpts.debug) { + console.error(chalk.gray('\nStack trace:')); console.error(chalk.gray(errorStack)); } process.exit(1); diff --git a/src/client.ts b/src/client.ts index beb649d..4422d93 100644 --- a/src/client.ts +++ b/src/client.ts @@ -175,24 +175,33 @@ export class Scope3Client { // This helps catch API bugs that need upstream fixes const content = result.content as Array<{ type: string; text?: string }> | undefined; const firstContent = content && content.length > 0 ? content[0] : null; - const errorDetails = { - toolName, - hasContent: Boolean(content), - contentType: firstContent?.type, - textPreview: - firstContent?.type === 'text' && firstContent.text - ? firstContent.text.substring(0, 200) - : undefined, - }; + const textPreview = + firstContent?.type === 'text' && firstContent.text + ? firstContent.text.substring(0, 200) + : undefined; - logger.error('MCP API VIOLATION: Missing structuredContent', errorDetails); + // Only log detailed info in debug mode + if (this.debug) { + const errorDetails = { + toolName, + hasContent: Boolean(content), + contentType: firstContent?.type, + textPreview, + }; + logger.error('MCP API VIOLATION: Missing structuredContent', errorDetails); + } - throw new Error( - `MCP API returned response without structuredContent for tool "${toolName}". ` + - `This violates the Scope3 API specification. ` + - `The API must be fixed to include structuredContent in all responses. ` + - `Debug info: ${JSON.stringify(errorDetails)}` - ); + // User-friendly error message + const errorMessage = [ + `API Error: Missing structured data for "${toolName}"`, + textPreview ? `\nAPI returned text: "${textPreview}"` : '', + '\n\nThe API should return structured JSON data but returned only text. ', + 'This is an API bug that needs to be fixed upstream.', + ] + .filter(Boolean) + .join(''); + + throw new Error(errorMessage); } protected getClient(): Client {