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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ralph-starter",
"version": "0.4.3",
"version": "0.4.4",
"description": "Ralph Wiggum made easy. One command to run autonomous AI coding loops with auto-commit, PRs, and Docker sandbox.",
"main": "dist/index.js",
"bin": {
Expand Down Expand Up @@ -71,6 +71,7 @@
"dependencies": {
"@anthropic-ai/sdk": "^0.78.0",
"@modelcontextprotocol/sdk": "^1.0.0",
"@opencode-ai/sdk": "^1.2.25",
"chalk": "^5.3.0",
"chalk-animation": "^2.0.3",
"commander": "^14.0.2",
Expand Down
16 changes: 12 additions & 4 deletions pnpm-lock.yaml

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

10 changes: 8 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ program
.option('--docker', 'Run in Docker sandbox (coming soon)')
.option('--prd <file>', 'Read tasks from a PRD markdown file')
.option('--max-iterations <n>', 'Maximum loop iterations (auto-calculated if not specified)')
.option('--agent <name>', 'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp)')
.option(
'--agent <name>',
'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp, anthropic-sdk, opencode-sdk)'
)
.option('--model <name>', 'Model to use (e.g., claude-sonnet-4-5-20250929, claude-opus-4-6)')
.option(
'--amp-mode <mode>',
Expand Down Expand Up @@ -415,7 +418,10 @@ program
.option('--pr', 'Create a pull request when done')
.option('--validate', 'Run tests/lint/build after each iteration')
.option('--max-iterations <n>', 'Maximum loop iterations')
.option('--agent <name>', 'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp)')
.option(
'--agent <name>',
'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp, anthropic-sdk, opencode-sdk)'
)
.option(
'--amp-mode <mode>',
'Amp agent mode: smart (frontier), rush (fast), deep (extended reasoning)'
Expand Down
25 changes: 24 additions & 1 deletion src/loop/__tests__/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ function createMockChildProcess(exitCode: number, stdout = '', stderr = '') {
describe('agents', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.unstubAllEnvs();
vi.stubEnv('ANTHROPIC_API_KEY', '');
vi.stubEnv('OPENCODE_API_KEY', '');
});

describe('checkAgentAvailable', () => {
Expand All @@ -59,6 +62,25 @@ describe('agents', () => {
expect(result).toBe(false);
});

it('should return true for opencode-sdk when API key is set and opencode CLI exists', async () => {
mockExeca.mockResolvedValueOnce({
stdout: '1.0.0',
stderr: '',
exitCode: 0,
} as any);

const result = await checkAgentAvailable('opencode-sdk', { apiKey: 'test-key' });
expect(result).toBe(true);
expect(mockExeca).toHaveBeenCalledWith('opencode', ['--version'], { timeout: 5000 });
});

it('should return false for opencode-sdk when API key is set but opencode CLI is missing', async () => {
mockExeca.mockRejectedValueOnce(new Error('opencode not found'));

const result = await checkAgentAvailable('opencode-sdk', { apiKey: 'test-key' });
expect(result).toBe(false);
});

it('should return true when agent command succeeds', async () => {
mockExeca.mockResolvedValueOnce({
stdout: '1.0.0',
Expand Down Expand Up @@ -89,10 +111,11 @@ describe('agents', () => {
.mockRejectedValueOnce(new Error('not found')) // opencode
.mockRejectedValueOnce(new Error('not found')) // openclaw
.mockRejectedValueOnce(new Error('not found')); // amp
// anthropic-sdk has no CLI check — availability is based on API key

const agents = await detectAvailableAgents();

expect(agents).toHaveLength(6);
expect(agents).toHaveLength(8);
expect(agents.find((a) => a.type === 'claude-code')?.available).toBe(true);
expect(agents.find((a) => a.type === 'cursor')?.available).toBe(false);
});
Expand Down
Loading
Loading