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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@ local/*
MCP directories
/.serena/
/.agents/skills/bmad*

.codemie
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ CodeMie CLI is the all-in-one AI coding assistant for developers.
- 🔐 **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.
- 🧩 **CodeMie Assistants in Claude** - Connect your available CodeMie assistants as Claude subagents or skills.
- 📊 **Usage Analytics** - Track and analyze AI usage across all agents with detailed insights.
- 🔧 **CI/CD Workflows** - Automated code review, fixes, and feature implementation.

Expand Down Expand Up @@ -212,6 +213,33 @@ Auto-updates are automatically disabled to maintain version control. CodeMie not

For more detailed information on the available agents, see the [Agents Documentation](docs/AGENTS.md).

### CodeMie Assistants as Claude Skills or Subagents

CodeMie can connect assistants available in your CodeMie account directly into Claude Code. Register them as Claude subagents and call them with `@slug`, or register them as Claude skills and invoke them with `/slug`.

```bash
# Pick assistants from your CodeMie account and choose how to register them
codemie setup assistants
```

During setup, choose:
- **Claude Subagents** - register selected assistants as `@slug`
- **Claude Skills** - register selected assistants as `/slug`
- **Manual Configuration** - choose skill or subagent per assistant

After registration, use them from Claude Code:

```text
@api-reviewer Review this authentication flow
/release-checklist prepare a release checklist for this branch
```

You can also message a registered assistant directly through CodeMie:

```bash
codemie assistants chat "assistant-id" "Review this API design"
```

### Claude Code Built-in Commands

When using Claude Code (`codemie-claude`), you get access to powerful built-in commands for project documentation:
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

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

14 changes: 12 additions & 2 deletions src/agents/core/BaseAgentAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -794,11 +794,19 @@ export abstract class BaseAgentAdapter implements AgentAdapter {
if (!providerName) return false;

const provider = ProviderRegistry.getProvider(providerName);

// Providers with no authentication requirement never route through the proxy.
// This also guards against stale CODEMIE_AUTH_METHOD='jwt' values persisting
// in process.env from a previous JWT-authenticated session (written by
// Object.assign(process.env, env) at the end of run()).
if (provider?.authType === 'none') return false;

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

// Proxy needed for SSO cookie injection OR JWT bearer token injection
// Proxy is only for model API authentication/forwarding. Analytics sync can
// be configured independently and must not force native providers through it.
return (isSSOProvider || isJWTAuth) && isProxyEnabled;
}

Expand Down Expand Up @@ -846,7 +854,9 @@ export abstract class BaseAgentAdapter implements AgentAdapter {
jwtToken: env.CODEMIE_JWT_TOKEN || undefined,
repository,
branch: branch || undefined,
project: env.CODEMIE_PROJECT || undefined
project: env.CODEMIE_PROJECT || undefined,
syncApiUrl: env.CODEMIE_SYNC_API_URL || undefined,
syncCodeMieUrl: env.CODEMIE_URL || undefined
};
}

Expand Down
97 changes: 96 additions & 1 deletion src/agents/core/__tests__/BaseAgentAdapter.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import { BaseAgentAdapter } from '../BaseAgentAdapter.js';
import type { AgentMetadata } from '../types.js';

// Provide a minimal ProviderRegistry stub so shouldUseProxy can look up authType
// without needing real provider templates to be registered.
// registerProvider / registerSetupSteps / registerHealthCheck must also be stubbed
// because provider templates call them as side-effects when their modules are imported
// transitively through BaseAgentAdapter.
vi.mock('../../../providers/core/registry.js', () => {
const providers: Record<string, { authType: string }> = {
'anthropic-subscription': { authType: 'none' },
'ai-run-sso': { authType: 'sso' },
'bearer-auth': { authType: 'jwt' },
};
return {
ProviderRegistry: {
registerProvider: vi.fn((t: any) => t),
registerSetupSteps: vi.fn(),
registerHealthCheck: vi.fn(),
registerModelProxy: vi.fn(),
getProvider: vi.fn((name: string) => providers[name]),
getProviderNames: vi.fn(() => Object.keys(providers)),
},
};
});

/**
* Test adapter that extends BaseAgentAdapter
* Used to test protected methods and metadata access
Expand Down Expand Up @@ -123,4 +146,76 @@ describe('BaseAgentAdapter', () => {
expect(adapter.getMetadata().lifecycle).toBe(lifecycle);
});
});

describe('proxy selection', () => {
// Shared metadata with ssoConfig enabled (same as the real claude plugin)
const proxyCapableMetadata: AgentMetadata = {
name: 'test',
displayName: 'Test Agent',
description: 'Test agent for unit testing',
npmPackage: null,
cliCommand: null,
envMapping: {},
supportedProviders: ['anthropic-subscription', 'ai-run-sso', 'bearer-auth'],
ssoConfig: { enabled: true, clientType: 'codemie-claude' },
};

it('does not enable the model proxy just because CodeMie analytics sync is configured', () => {
const adapter = new TestAdapter(proxyCapableMetadata);

expect((adapter as any).shouldUseProxy({
CODEMIE_PROVIDER: 'anthropic-subscription',
CODEMIE_URL: 'https://codemie.lab.epam.com',
CODEMIE_SYNC_API_URL: 'https://codemie.lab.epam.com/code-assistant-api',
})).toBe(false);
});

it('does not start proxy for authType:none even when CODEMIE_AUTH_METHOD=jwt is stale in env', () => {
// Regression guard for the stale-env contamination bug:
// A previous JWT session writes CODEMIE_AUTH_METHOD=jwt to process.env.
// The next anthropic-subscription run must NOT start the proxy.
const adapter = new TestAdapter(proxyCapableMetadata);

expect((adapter as any).shouldUseProxy({
CODEMIE_PROVIDER: 'anthropic-subscription',
CODEMIE_AUTH_METHOD: 'jwt', // stale value from a prior JWT session
CODEMIE_URL: 'https://codemie.lab.epam.com',
CODEMIE_SYNC_API_URL: 'https://codemie.lab.epam.com/code-assistant-api',
})).toBe(false);
});

it('does not start proxy when CODEMIE_PROVIDER is absent', () => {
const adapter = new TestAdapter(proxyCapableMetadata);
expect((adapter as any).shouldUseProxy({})).toBe(false);
});

it('starts proxy for SSO provider when ssoConfig is enabled', () => {
const adapter = new TestAdapter(proxyCapableMetadata);

expect((adapter as any).shouldUseProxy({
CODEMIE_PROVIDER: 'ai-run-sso',
})).toBe(true);
});

it('starts proxy for JWT auth method on a non-native provider', () => {
const adapter = new TestAdapter(proxyCapableMetadata);

expect((adapter as any).shouldUseProxy({
CODEMIE_PROVIDER: 'bearer-auth',
CODEMIE_AUTH_METHOD: 'jwt',
})).toBe(true);
});

it('does not start proxy when ssoConfig is disabled even for an SSO provider', () => {
const noProxyMetadata: AgentMetadata = {
...proxyCapableMetadata,
ssoConfig: { enabled: false, clientType: 'codemie-claude' },
};
const adapter = new TestAdapter(noProxyMetadata);

expect((adapter as any).shouldUseProxy({
CODEMIE_PROVIDER: 'ai-run-sso',
})).toBe(false);
});
});
});
72 changes: 72 additions & 0 deletions src/agents/core/__tests__/model-tier-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,76 @@ describe('ConfigLoader.exportProviderEnvVars', () => {
expect(env.CODEMIE_SONNET_MODEL).toBeUndefined();
expect(env.CODEMIE_OPUS_MODEL).toBeUndefined();
});

it('should not export placeholder auth token for anthropic-subscription', async () => {
const { ConfigLoader } = await import('../../../utils/config.js');

const config = {
provider: 'anthropic-subscription',
baseUrl: 'https://api.anthropic.com',
model: 'claude-sonnet-4-6',
apiKey: '',
authMethod: 'manual' as const,
codeMieUrl: 'https://codemie.lab.epam.com',
codeMieProject: 'codemie-platform',
};

const env = ConfigLoader.exportProviderEnvVars(config);

expect(env.CODEMIE_PROVIDER).toBe('anthropic-subscription');
expect(env.CODEMIE_BASE_URL).toBe('https://api.anthropic.com');
expect(env.CODEMIE_API_KEY).toBe('');
expect(env.CODEMIE_MODEL).toBe('claude-sonnet-4-6');
expect(env.CODEMIE_URL).toBe('https://codemie.lab.epam.com');
expect(env.CODEMIE_SYNC_API_URL).toBe('https://codemie.lab.epam.com/code-assistant-api');
expect(env.CODEMIE_PROJECT).toBe('codemie-platform');
});

describe('CODEMIE_AUTH_METHOD export (stale-env contamination guard)', () => {
it('exports CODEMIE_AUTH_METHOD=manual for anthropic-subscription', async () => {
const { ConfigLoader } = await import('../../../utils/config.js');

const env = ConfigLoader.exportProviderEnvVars({
provider: 'anthropic-subscription',
baseUrl: 'https://api.anthropic.com',
apiKey: '',
authMethod: 'manual',
});

expect(env.CODEMIE_AUTH_METHOD).toBe('manual');
});

it('exports CODEMIE_AUTH_METHOD="" when authMethod is not set', async () => {
const { ConfigLoader } = await import('../../../utils/config.js');

const env = ConfigLoader.exportProviderEnvVars({
provider: 'openai',
baseUrl: 'https://api.openai.com',
apiKey: 'sk-test',
// authMethod intentionally absent
});

expect(env.CODEMIE_AUTH_METHOD).toBe('');
});

it('CODEMIE_AUTH_METHOD overrides stale jwt value when merged into env', async () => {
// Simulates what BaseAgentAdapter does:
// env = { ...process.env (stale), ...envOverrides (fresh) }
// The fresh exportProviderEnvVars output must win over the stale value.
const { ConfigLoader } = await import('../../../utils/config.js');

const staleProcessEnv = { CODEMIE_AUTH_METHOD: 'jwt' };

const providerEnv = ConfigLoader.exportProviderEnvVars({
provider: 'anthropic-subscription',
baseUrl: 'https://api.anthropic.com',
apiKey: '',
authMethod: 'manual',
});

const mergedEnv = { ...staleProcessEnv, ...providerEnv };

expect(mergedEnv.CODEMIE_AUTH_METHOD).toBe('manual');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { describe, expect, it } from 'vitest';
import { ClaudePluginMetadata } from '../claude.plugin.js';

describe('ClaudePluginMetadata', () => {
it('supports anthropic-subscription provider', () => {
expect(ClaudePluginMetadata.supportedProviders).toContain('anthropic-subscription');
});
});
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 @@ -84,7 +84,7 @@ export const ClaudePluginMetadata: AgentMetadata = {
opusModel: ['ANTHROPIC_DEFAULT_OPUS_MODEL'],
},

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

Expand Down
Loading
Loading