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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nimbletools/ntcli",
"version": "0.2.0",
"version": "0.4.0",
"description": "Command-line interface for NimbleTools MCP Platform - deploy, manage, and integrate MCP servers",
"keywords": [
"mcp",
Expand Down Expand Up @@ -89,4 +89,4 @@
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
}
6 changes: 3 additions & 3 deletions src/commands/auth/login.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import chalk from 'chalk';
import ora from 'ora';
import open from 'open';
import ora from 'ora';
import { v4 as uuidv4 } from 'uuid';
import { ClerkOAuthClient } from '../../lib/auth/clerk-oauth-client.js';
import { TokenManager } from '../../lib/auth/token-manager.js';
import { LocalCallbackServer } from '../../lib/auth/local-server.js';
import { TokenExchangeClient } from '../../lib/auth/token-exchange.js';
import { TokenManager } from '../../lib/auth/token-manager.js';
import { Config } from '../../lib/config.js';
import { AuthCommandOptions } from '../../types/index.js';

Expand Down Expand Up @@ -91,7 +91,7 @@ export async function handleLogin(options: AuthCommandOptions = {}): Promise<voi
callbackResponse.code,
pkceChallenge.codeVerifier
);

waitSpinner.text = '👤 Fetching user information...';

// Fetch user information from Clerk
Expand Down
10 changes: 7 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ async function main() {
.option("--debug", "Show detailed HTTP debugging (headers, body, errors)")
.configureOutput({
writeErr: (str) => process.stderr.write(chalk.red(str)),
});
})
.addHelpText('afterAll', `\nNeed more help? Visit ${chalk.cyan('https://www.nimblebrain.ai/discord')}`);

// Info command
program
Expand Down Expand Up @@ -1062,15 +1063,18 @@ async function main() {

// Parse command line arguments
try {
// Check for global flags before parsing
// Check for global flags before parsing
if (process.argv.includes("--debug")) {
process.env.NTCLI_DEBUG = "1";
}

await program.parseAsync(process.argv);
} catch (error) {
if (error instanceof Error) {
console.error(chalk.red(`Error: ${error.message}`));
// Don't show error for help/version outputs
if (error.message !== '(outputHelp)' && error.message !== '(outputVersion)') {
console.error(chalk.red(`Error: ${error.message}`));
}
} else {
console.error(chalk.red("An unexpected error occurred"));
}
Expand Down
11 changes: 0 additions & 11 deletions src/lib/auth/clerk-oauth-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ import {
UserInfo,
} from "../../types/index.js";

// Get Clerk domain from environment or use default
const DEFAULT_CLERK_OAUTH_DOMAIN = process.env.CLERK_OAUTH_DOMAIN || "clerk.nimbletools.ai";

// Get Clerk OAuth audience from environment
const CLERK_OAUTH_AUDIENCE = process.env.CLERK_OAUTH_AUDIENCE;

/**
* Clerk OAuth client implementing OAuth 2.0 with PKCE flow
*/
Expand Down Expand Up @@ -61,11 +55,6 @@ export class ClerkOAuthClient {
code_challenge_method: pkceChallenge.codeChallengeMethod,
});

// Add audience if configured
if (CLERK_OAUTH_AUDIENCE) {
params.append("audience", CLERK_OAUTH_AUDIENCE);
}

// Use domain from environment variable if available, otherwise use config domain
const domain = process.env.CLERK_OAUTH_DOMAIN || this.config.domain;
return `https://${domain}/oauth/authorize?${params.toString()}`;
Expand Down
24 changes: 22 additions & 2 deletions src/lib/auth/token-exchange.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ConfigManager } from '../config-manager.js';

/**
* Token exchange response from the API
Expand All @@ -14,8 +15,13 @@ export class TokenExchangeClient {
private baseUrl: string;

constructor(baseUrl?: string) {
// Use environment variable or default to production URL
this.baseUrl = baseUrl || process.env.NIMBLEBRAIN_API_URL || 'https://studio-api.nimblebrain.ai';
if (baseUrl) {
this.baseUrl = baseUrl;
} else {
// Use ConfigManager to get the Studio API URL
const configManager = new ConfigManager();
this.baseUrl = configManager.getStudioApiUrl();
}
}

/**
Expand All @@ -24,6 +30,12 @@ export class TokenExchangeClient {
async exchangeToken(idToken: string): Promise<string> {
const url = `${this.baseUrl}/api/v1/auth/token-exchange`;

if (process.env.NTCLI_DEBUG) {
console.log('\n[DEBUG] Token Exchange Request:');
console.log(' URL:', url);
console.log(' ID Token (first 50 chars):', idToken.substring(0, 50) + '...');
}

try {
const response = await fetch(url, {
method: 'POST',
Expand All @@ -35,6 +47,10 @@ export class TokenExchangeClient {
}),
});

if (process.env.NTCLI_DEBUG) {
console.log('[DEBUG] Response Status:', response.status);
}

if (!response.ok) {
let errorMessage = '';
try {
Expand All @@ -44,6 +60,10 @@ export class TokenExchangeClient {
errorMessage = await response.text();
}

if (process.env.NTCLI_DEBUG) {
console.log('[DEBUG] Error Response:', errorMessage);
}

// Include status code in error message for better handling
if (response.status === 403) {
throw new Error(`403: ${errorMessage || 'Access forbidden'}`);
Expand Down
21 changes: 15 additions & 6 deletions src/lib/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,30 @@ export class ConfigManager {

/**
* Get the Clerk authentication domain
* Note: This is independent of the main domain and always defaults to nimblebrain.ai
*/
getClerkDomain(): string {
// Use environment variable if set
if (process.env.CLERK_OAUTH_DOMAIN) {
return process.env.CLERK_OAUTH_DOMAIN;
}

// Fall back to deriving from main domain
const domain = this.getDomain();
// Always use nimblebrain.ai for production Clerk, not derived from main domain
return 'clerk.nimblebrain.ai';
}

// For localhost, return as-is
if (domain.includes('localhost') || domain.includes('127.0.0.1')) {
return domain;
/**
* Get the Studio API URL (for token exchange)
* Note: This is independent of the main domain and always defaults to nimblebrain.ai
*/
getStudioApiUrl(): string {
// Use environment variable if set
if (process.env.NIMBLEBRAIN_API_URL) {
return process.env.NIMBLEBRAIN_API_URL;
}
return `clerk.${domain}`;

// Always use nimblebrain.ai for Studio API, not derived from main domain
return 'https://studio-api.nimblebrain.ai';
}

// Authentication methods
Expand Down
2 changes: 1 addition & 1 deletion src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class Config {
* Get Clerk OAuth configuration
*/
getClerkConfig(): ClerkOAuthConfig {
const clientId = process.env.CLERK_OAUTH_CLIENT_ID || "0MUyvaWYSj4g0lzE";
const clientId = process.env.CLERK_OAUTH_CLIENT_ID || "96WRYcxabFktp9wn";

// Get domain from unified config
const configManager = new ConfigManager();
Expand Down
Loading