diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index b46158ee..305a600d 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -25,6 +25,7 @@ codemie version # Show version information ```bash --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 ``` @@ -46,6 +47,7 @@ All agent shortcuts support these options: --api-key # Override API key --base-url # Override base URL --timeout # Override timeout (in seconds) +-s, --silent # Enable silent mode ``` ### Built-in Agent (codemie-code) @@ -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. diff --git a/src/agents/core/AgentCLI.ts b/src/agents/core/AgentCLI.ts index 4cbded76..8d695342 100644 --- a/src/agents/core/AgentCLI.ts +++ b/src/agents/core/AgentCLI.ts @@ -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 ', 'Use specific provider profile') .option('--provider ', 'Override provider (ai-run-sso, litellm, ollama)') .option('-m, --model ', 'Override model') @@ -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 @@ -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) diff --git a/src/agents/core/BaseAgentAdapter.ts b/src/agents/core/BaseAgentAdapter.ts index 775627df..bdd3a757 100644 --- a/src/agents/core/BaseAgentAdapter.ts +++ b/src/agents/core/BaseAgentAdapter.ts @@ -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 diff --git a/src/agents/core/__tests__/BaseAgentAdapter.test.ts b/src/agents/core/__tests__/BaseAgentAdapter.test.ts new file mode 100644 index 00000000..f06ace5b --- /dev/null +++ b/src/agents/core/__tests__/BaseAgentAdapter.test.ts @@ -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 { + // 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); + }); + }); +});