Skip to content
Open
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3%2B-blue.svg)](https://www.typescriptlang.org/)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

> **Unified AI Coding Assistant CLI** - Manage Claude Code, Google Gemini, OpenCode, and custom AI agents from one powerful command-line interface. Multi-provider support (OpenAI, Azure OpenAI, AWS Bedrock, LiteLLM, Ollama, Enterprise SSO). Built-in LangGraph agent with file operations, command execution, and planning tools. Cross-platform support for Windows, Linux, and macOS.
> **Unified AI Coding Assistant CLI** - Manage Claude Code, Google Gemini, OpenCode, and custom AI agents from one powerful command-line interface. Multi-provider support (OpenAI, Azure OpenAI, AWS Bedrock, LiteLLM, Ollama, Enterprise SSO, JWT Bearer Auth). Built-in LangGraph agent with file operations, command execution, and planning tools. Cross-platform support for Windows, Linux, and macOS.

---

Expand All @@ -23,10 +23,10 @@
CodeMie CLI is the all-in-one AI coding assistant for developers.

- ✨ **One CLI, Multiple AI Agents** - Switch between Claude Code, Gemini, OpenCode, and built-in agent.
- 🔄 **Multi-Provider Support** - OpenAI, Azure OpenAI, AWS Bedrock, LiteLLM, Ollama, and Enterprise SSO.
- 🔄 **Multi-Provider Support** - OpenAI, Azure OpenAI, AWS Bedrock, LiteLLM, Ollama, Enterprise SSO, and JWT Bearer Auth.
- 🚀 **Built-in Agent** - A powerful LangGraph-based assistant with file operations, command execution, and planning tools.
- 🖥️ **Cross-Platform** - Full support for Windows, Linux, and macOS with platform-specific optimizations.
- 🔐 **Enterprise Ready** - SSO authentication, audit logging, and role-based access.
- 🔐 **Enterprise Ready** - SSO and JWT authentication, audit logging, and role-based access.
- ⚡ **Productivity Boost** - Code review, refactoring, test generation, and bug fixing.
- 🎯 **Profile Management** - Manage work, personal, and team configurations separately.
- 📊 **Usage Analytics** - Track and analyze AI usage across all agents with detailed insights.
Expand Down
147 changes: 147 additions & 0 deletions docs/AUTHENTICATION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Authentication & SSO Management

## Authentication Methods

CodeMie CLI supports multiple authentication methods:

- **CodeMie SSO** - Browser-based Single Sign-On (recommended for enterprise)
- **JWT Bearer Authorization** - Token-based authentication for CI/CD and external auth systems
- **API Key** - Direct API key authentication for other providers (OpenAI, Anthropic, etc.)

## AI/Run CodeMie SSO Setup

For enterprise environments with AI/Run CodeMie SSO (Single Sign-On):
Expand Down Expand Up @@ -103,3 +111,142 @@ AI/Run CodeMie SSO provides enterprise-grade features:
- **Automatic Plugin Installation**: Claude Code plugin auto-installs for session tracking
- **Audit Logging**: Enterprise audit trails for security compliance
- **Role-Based Access**: Model access based on organizational permissions

## JWT Bearer Authorization

For environments with external token management systems, CI/CD pipelines, or testing scenarios, CodeMie CLI supports JWT Bearer Authorization. This method provides tokens at runtime rather than during setup.

### Initial Setup

JWT setup only requires the API URL - tokens are provided later:

```bash
codemie setup
# Select: Bearer Authorization
```

**The wizard will:**
1. Prompt for the CodeMie base URL (e.g., `https://codemie.lab.epam.com`)
2. Optionally ask for a custom environment variable name (default: `CODEMIE_JWT_TOKEN`)
3. Save the configuration without requiring a token
4. Display instructions for providing tokens at runtime

### Providing JWT Tokens

After setup, provide tokens via environment variable or CLI option:

**Environment Variable (Recommended):**
```bash
# Set token in your environment
export CODEMIE_JWT_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Run commands normally
codemie-claude "analyze this code"
```

**CLI Option:**
```bash
# Provide token per command
codemie-claude --jwt-token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." "analyze this code"
```

**Custom Environment Variable:**
```bash
# If you configured a custom env var during setup
export MY_CUSTOM_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
codemie-claude "analyze this code"
```

### JWT Token Management

JWT tokens are validated automatically:

```bash
# Check JWT authentication status
codemie doctor

# View token status and expiration
codemie profile status
```

**Token Validation:**
- Format validation (header.payload.signature)
- Expiration checking (warns if expiring within 7 days)
- Automatic error messages for expired tokens

### Use Cases

JWT Bearer Authorization is ideal for:

**CI/CD Pipelines:**
```bash
# GitLab CI example
script:
- export CODEMIE_JWT_TOKEN="${CI_JOB_JWT}"
- codemie-claude --task "review changes in this commit"
```

**External Auth Systems:**
```bash
# Obtain token from your auth provider
TOKEN=$(curl -s https://auth.example.com/token | jq -r .access_token)

# Use with CodeMie
codemie-claude --jwt-token "$TOKEN" "your prompt"
```

**Testing & Development:**
```bash
# Use short-lived test tokens
export CODEMIE_JWT_TOKEN="test-token-expires-in-1h"
codemie-claude "run tests"
```

### JWT vs SSO

| Feature | JWT Bearer Auth | CodeMie SSO |
|---------|----------------|-------------|
| **Setup** | URL only | Browser-based flow |
| **Token Source** | Runtime (CLI/env) | Stored in keychain |
| **Best For** | CI/CD, external auth | Interactive development |
| **Token Refresh** | Manual (obtain new token) | Automatic |
| **Security** | Token management external | Managed by CLI |

### Troubleshooting JWT

**Token not found:**
```bash
# Check environment variable
echo $CODEMIE_JWT_TOKEN

# Verify variable name matches config
codemie profile status

# Provide via CLI instead
codemie-claude --jwt-token "your-token" "your prompt"
```

**Token expired:**
```bash
# Obtain new token from your auth provider
export CODEMIE_JWT_TOKEN="new-token-here"

# Verify expiration
codemie doctor
```

**Invalid token format:**
```bash
# JWT must have 3 parts (header.payload.signature)
# Check token structure
echo $CODEMIE_JWT_TOKEN | awk -F. '{print NF}' # Should output: 3
```

**Configuration issues:**
```bash
# Reset and reconfigure
codemie setup # Choose Bearer Authorization again

# Or manually edit config
cat ~/.codemie/codemie-cli.config.json
```
42 changes: 36 additions & 6 deletions src/agents/core/AgentCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,21 @@ export class AgentCLI {
.option('--api-key <key>', 'Override API key')
.option('--base-url <url>', 'Override base URL')
.option('--timeout <seconds>', 'Override timeout (in seconds)', parseInt)
.option('--jwt-token <token>', 'JWT token for authentication (overrides config)')
.option('--task <prompt>', 'Execute a single task (agent-specific flag mapping)')
.allowUnknownOption()
.argument('[args...]', `Arguments to pass to ${this.adapter.displayName}`)
.action(async (args, options) => {
await this.handleRun(args, options);
// Commander.js v11 behavior: options is Command instance when args array is empty,
// but plain object when args are provided. Handle both cases defensively.
const opts = typeof options?.opts === 'function' ? options.opts() : options;

// Debug logging
logger.debug(`[AgentCLI] action called with args: ${JSON.stringify(args)}`);
logger.debug(`[AgentCLI] options type: ${typeof options}, has opts(): ${typeof options?.opts === 'function'}`);
logger.debug(`[AgentCLI] extracted opts: ${JSON.stringify(opts)}`);

await this.handleRun(args, opts);
});

// Add health check command
Expand All @@ -89,7 +99,9 @@ export class AgentCLI {
.option('--force', 'Force re-initialization')
.option('--project-name <name>', 'Project name for framework initialization')
.action(async (framework, options) => {
await this.handleInit(framework, options);
// Commander.js v11 behavior: options might be Command instance or plain object
const opts = typeof options?.opts === 'function' ? options.opts() : options;
await this.handleInit(framework, opts);
});
}
}
Expand Down Expand Up @@ -123,8 +135,13 @@ export class AgentCLI {
process.exit(1);
}

// Apply silent mode from CLI flag (if provided)
if (options.silent) {
// Auto-enable silent mode in non-interactive mode (--task flag present)
// This suppresses welcome/goodbye messages and interactive prompts
const isNonInteractiveMode = !!options.task;
const shouldBeSilent = options.silent || isNonInteractiveMode;

// Apply silent mode from CLI flag or auto-detected non-interactive mode
if (shouldBeSilent) {
// Type-safe check: ensure adapter has setSilentMode method
if ('setSilentMode' in this.adapter && typeof this.adapter.setSilentMode === 'function') {
this.adapter.setSilentMode(true);
Expand All @@ -141,6 +158,12 @@ export class AgentCLI {
timeout: options.timeout as number | undefined
});

// JWT token from CLI overrides everything
if (options.jwtToken) {
process.env.CODEMIE_JWT_TOKEN = options.jwtToken as string;
process.env.CODEMIE_AUTH_METHOD = 'jwt';
}

// Validate essential configuration
const missingFields: string[] = [];
if (!config.baseUrl) missingFields.push('baseUrl');
Expand All @@ -151,7 +174,11 @@ export class AgentCLI {
const provider = config.provider ? ProviderRegistry.getProvider(config.provider) : null;
const requiresAuth = provider?.requiresAuth ?? true; // Default to true for safety

if (requiresAuth && !config.apiKey) {
// Skip apiKey validation for SSO and JWT authentication methods
const authMethod = config.authMethod;
const usesAlternativeAuth = authMethod === 'sso' || authMethod === 'jwt';

if (requiresAuth && !config.apiKey && !usesAlternativeAuth) {
missingFields.push('apiKey');
}

Expand Down Expand Up @@ -206,6 +233,9 @@ export class AgentCLI {
// Collect all arguments to pass to the agent
const agentArgs = this.collectPassThroughArgs(args, options);

// Debug logging
logger.debug(`[AgentCLI] collected agentArgs: ${JSON.stringify(agentArgs)}`);

// Run the agent (welcome message will be shown inside)
await this.adapter.run(agentArgs, providerEnv);
} catch (error) {
Expand Down Expand Up @@ -339,7 +369,7 @@ export class AgentCLI {
): string[] {
const agentArgs = [...args];
// Config-only options (not passed to agent, handled by CodeMie CLI)
const configOnlyOptions = ['profile', 'provider', 'apiKey', 'baseUrl', 'timeout', 'model', 'silent'];
const configOnlyOptions = ['profile', 'provider', 'apiKey', 'baseUrl', 'timeout', 'model', 'silent', 'jwtToken'];

for (const [key, value] of Object.entries(options)) {
// Skip config-only options (handled by CodeMie CLI layer)
Expand Down
8 changes: 6 additions & 2 deletions src/agents/core/BaseAgentAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,9 +528,11 @@ export abstract class BaseAgentAdapter implements AgentAdapter {

const provider = ProviderRegistry.getProvider(providerName);
const isSSOProvider = provider?.authType === 'sso';
const isJWTAuth = env.CODEMIE_AUTH_METHOD === 'jwt';
const isProxyEnabled = this.metadata.ssoConfig?.enabled ?? false;

return isSSOProvider && isProxyEnabled;
// Proxy needed for SSO cookie injection OR JWT bearer token injection
return (isSSOProvider || isJWTAuth) && isProxyEnabled;
}

/**
Expand Down Expand Up @@ -567,7 +569,9 @@ export abstract class BaseAgentAdapter implements AgentAdapter {
integrationId: env.CODEMIE_INTEGRATION_ID,
sessionId: env.CODEMIE_SESSION_ID,
version: env.CODEMIE_CLI_VERSION,
profileConfig
profileConfig,
authMethod: (env.CODEMIE_AUTH_METHOD as 'sso' | 'jwt') || undefined,
jwtToken: env.CODEMIE_JWT_TOKEN || undefined
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/agents/plugins/claude/claude.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const ClaudePluginMetadata: AgentMetadata = {
opusModel: ['ANTHROPIC_DEFAULT_OPUS_MODEL'],
},

supportedProviders: ['litellm', 'ai-run-sso', 'bedrock'],
supportedProviders: ['litellm', 'ai-run-sso', 'bedrock', 'bearer-auth'],
blockedModelPatterns: [],
recommendedModels: ['claude-4-5-sonnet', 'claude-4-opus', 'gpt-4.1'],

Expand Down
2 changes: 1 addition & 1 deletion src/agents/plugins/codemie-code.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const CodeMieCodePluginMetadata: AgentMetadata = {

envMapping: {},

supportedProviders: ['ollama', 'litellm', 'ai-run-sso'],
supportedProviders: ['ollama', 'litellm', 'ai-run-sso', 'bearer-auth'],
blockedModelPatterns: [],

// Built-in agent doesn't use proxy (handles auth internally)
Expand Down
2 changes: 1 addition & 1 deletion src/agents/plugins/gemini/gemini.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const metadata = {
model: ['GEMINI_MODEL']
},

supportedProviders: ['ai-run-sso', 'litellm'],
supportedProviders: ['ai-run-sso', 'litellm', 'bearer-auth'],
blockedModelPatterns: [/^claude/i, /^gpt/i], // Gemini models only
recommendedModels: ['gemini-3-pro'],

Expand Down
2 changes: 1 addition & 1 deletion src/agents/plugins/opencode/opencode.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export const OpenCodePluginMetadata: AgentMetadata = {
apiKey: [],
model: []
},
supportedProviders: ['litellm', 'ai-run-sso'],
supportedProviders: ['litellm', 'ai-run-sso', 'bearer-auth'],
ssoConfig: { enabled: true, clientType: 'codemie-opencode' },

lifecycle: {
Expand Down
Loading