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
5 changes: 5 additions & 0 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ codemie version # Show version information

```bash
--task <task> # Execute a single task using the built-in agent and exit
-s, --silent # Enable silent mode
--help # Display help for command
--version # Output the version number
```
Expand All @@ -46,6 +47,7 @@ All agent shortcuts support these options:
--api-key <key> # Override API key
--base-url <url> # Override base URL
--timeout <seconds> # Override timeout (in seconds)
-s, --silent # Enable silent mode
```

### Built-in Agent (codemie-code)
Expand Down Expand Up @@ -94,6 +96,9 @@ codemie-opencode --profile work "refactor code"
# Agent-specific options (pass-through to underlying CLI)
codemie-claude --context large -p "review code" # -p = print mode (non-interactive)
codemie-gemini -p "your prompt" # -p for gemini's non-interactive mode

# Implement planned task without asking any questions (silent mode)
codemie-claude --task "Implement task 1" --silent --dangerously-skip-permissions --output-format stream-json --verbose
```

**Note**: Configuration options (`--profile`, `--model`, etc.) are handled by CodeMie CLI wrapper. All other options are passed directly to the underlying agent binary.
Expand Down
11 changes: 10 additions & 1 deletion src/agents/core/AgentCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class AgentCLI {
.name(programName)
.description(`CodeMie ${this.adapter.displayName} - ${this.adapter.description}`)
.version(this.version)
.option('-s, --silent', 'Enable silent mode')
.option('--profile <name>', 'Use specific provider profile')
.option('--provider <provider>', 'Override provider (ai-run-sso, litellm, ollama)')
.option('-m, --model <model>', 'Override model')
Expand Down Expand Up @@ -122,6 +123,14 @@ export class AgentCLI {
process.exit(1);
}

// Apply silent mode from CLI flag (if provided)
if (options.silent) {
// Type-safe check: ensure adapter has setSilentMode method
if ('setSilentMode' in this.adapter && typeof this.adapter.setSilentMode === 'function') {
this.adapter.setSilentMode(true);
}
}

// Load configuration with CLI overrides
const config = await ConfigLoader.load(process.cwd(), {
name: options.profile as string | undefined, // Profile selection
Expand Down Expand Up @@ -330,7 +339,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'];
const configOnlyOptions = ['profile', 'provider', 'apiKey', 'baseUrl', 'timeout', 'model', 'silent'];

for (const [key, value] of Object.entries(options)) {
// Skip config-only options (handled by CodeMie CLI layer)
Expand Down
15 changes: 14 additions & 1 deletion src/agents/core/BaseAgentAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,22 @@ import inquirer from 'inquirer';
*/
export abstract class BaseAgentAdapter implements AgentAdapter {
protected proxy: CodeMieProxy | null = null;
protected metadata: AgentMetadata;

constructor(protected metadata: AgentMetadata) {}
constructor(metadata: AgentMetadata) {
// Clone metadata to allow runtime overrides (e.g., CLI flags)
this.metadata = { ...metadata };
}

/**
* Override silent mode at runtime
* Used by CLI to apply --silent flag
*
* @param enabled - Whether to enable silent mode
*/
setSilentMode(enabled: boolean): void {
this.metadata.silentMode = enabled;
}

/**
* Get metrics configuration for this agent
Expand Down
126 changes: 126 additions & 0 deletions src/agents/core/__tests__/BaseAgentAdapter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, it, expect } from 'vitest';
import { BaseAgentAdapter } from '../BaseAgentAdapter.js';
import type { AgentMetadata } from '../types.js';

/**
* Test adapter that extends BaseAgentAdapter
* Used to test protected methods and metadata access
*/
class TestAdapter extends BaseAgentAdapter {
constructor(metadata: AgentMetadata) {
super(metadata);
}

// Expose protected metadata for testing
getMetadata(): AgentMetadata {
return this.metadata;
}

// Implement required abstract methods (no-ops for testing)
async run(): Promise<void> {
// No-op for testing
}
}

describe('BaseAgentAdapter', () => {
describe('setSilentMode', () => {
it('should set silentMode to true when enabled', () => {
const metadata: AgentMetadata = {
name: 'test',
displayName: 'Test Agent',
description: 'Test agent for unit testing',
npmPackage: null,
cliCommand: null,
envMapping: {},
supportedProviders: ['openai'],
silentMode: false // Start as false
};

const adapter = new TestAdapter(metadata);

// Initial state
expect(adapter.getMetadata().silentMode).toBe(false);

// Call setter
adapter.setSilentMode(true);

// Verify it changed
expect(adapter.getMetadata().silentMode).toBe(true);
});

it('should set silentMode to false when disabled', () => {
const metadata: AgentMetadata = {
name: 'test',
displayName: 'Test Agent',
description: 'Test agent for unit testing',
npmPackage: null,
cliCommand: null,
envMapping: {},
supportedProviders: ['openai'],
silentMode: true // Start as true
};

const adapter = new TestAdapter(metadata);

// Initial state
expect(adapter.getMetadata().silentMode).toBe(true);

// Call setter
adapter.setSilentMode(false);

// Verify it changed
expect(adapter.getMetadata().silentMode).toBe(false);
});

it('should not affect original metadata object (verify cloning)', () => {
const originalMetadata: AgentMetadata = {
name: 'test',
displayName: 'Test Agent',
description: 'Test agent for unit testing',
npmPackage: null,
cliCommand: null,
envMapping: {},
supportedProviders: ['openai'],
silentMode: false
};

const adapter = new TestAdapter(originalMetadata);

// Modify via setter
adapter.setSilentMode(true);

// Original should be unchanged (verify shallow copy worked)
expect(originalMetadata.silentMode).toBe(false);
expect(adapter.getMetadata().silentMode).toBe(true);
});
});

describe('constructor metadata cloning', () => {
it('should create a shallow copy of metadata', () => {
const envMapping = { apiKey: ['TEST_KEY'] };
const lifecycle = {
beforeRun: async (env: NodeJS.ProcessEnv) => env
};

const metadata: AgentMetadata = {
name: 'test',
displayName: 'Test Agent',
description: 'Test agent for unit testing',
npmPackage: null,
cliCommand: null,
envMapping,
supportedProviders: ['openai'],
lifecycle
};

const adapter = new TestAdapter(metadata);

// Top-level object should be different (cloned)
expect(adapter.getMetadata()).not.toBe(metadata);

// Nested objects should be same reference (shallow copy)
expect(adapter.getMetadata().envMapping).toBe(envMapping);
expect(adapter.getMetadata().lifecycle).toBe(lifecycle);
});
});
});