Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .changeset/cli-tool.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
---
"@scope3/agentic-client": minor
"scope3": minor
---

Expand Down
17 changes: 3 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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
Expand Down
10 changes: 4 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down Expand Up @@ -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"
Expand Down
1 change: 0 additions & 1 deletion packages/scope3-cli/.npmrc

This file was deleted.

48 changes: 0 additions & 48 deletions packages/scope3-cli/README.md

This file was deleted.

3 changes: 0 additions & 3 deletions packages/scope3-cli/cli.js

This file was deleted.

36 changes: 0 additions & 36 deletions packages/scope3-cli/package.json

This file was deleted.

16 changes: 8 additions & 8 deletions src/__tests__/client-mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
);
});

Expand All @@ -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'
);
});

Expand All @@ -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'
);
});

Expand All @@ -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');
});
});

Expand All @@ -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'
);
});

Expand All @@ -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'
);
});

Expand Down Expand Up @@ -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'
);
});

Expand Down
64 changes: 56 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -475,12 +499,19 @@ const configCmd = program.command('config').description('Manage CLI configuratio
configCmd
.command('set')
.description('Set configuration value')
.argument('<key>', 'Configuration key (apiKey, environment, or baseUrl)')
.argument(
'<key>',
'Configuration key (apiKey, productionApiKey, stagingApiKey, environment, or baseUrl)'
)
.argument('<value>', '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}`));
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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]);
Expand Down Expand Up @@ -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);
Expand Down
Loading