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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ It pulls work from Linear or GitHub, creates isolated worktrees, runs an agent i
- Issue intake from Linear and GitHub.
- Global runtime state under `~/.parallax`.
- CLI control plane plus dashboard UI.
- Codex and Gemini adapters (configurable per project).
- Codex, Gemini, and Claude Code adapters (configurable per project).

## Requirements

Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Provider-specific requirement:

#### agent.provider (required)

- allowed values: `codex`, `gemini`
- allowed values: `codex`, `gemini`, `claude-code`

#### agent.model (optional)

Expand Down
3 changes: 2 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ parallax preflight
- `gh auth status`
- `codex` CLI (optional)
- `gemini` CLI (optional)
- at least one agent CLI (`codex` or `gemini`) is available
- `claude` CLI (optional)
- at least one agent CLI (`codex`, `gemini`, or `claude`) is available

If a required check fails, fix it before moving on.

Expand Down
2 changes: 1 addition & 1 deletion docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ gh auth login
gh auth status
```

### Neither codex nor gemini found
### None of codex, gemini, or claude found

Install at least one agent CLI and ensure it is on your `PATH`.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "parallax",
"version": "0.0.6",
"version": "0.0.7",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Parallax is a local-first AI orchestrator for software development tasks. It pul
- `git`
- `pnpm`
- `gh`
- at least one supported agent CLI (`codex` or `gemini`)
- at least one supported agent CLI (`codex`, `gemini`, or `claude`)

## Install

Expand Down
15 changes: 14 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
{
"name": "parallax-cli",
"version": "0.0.6",
"version": "0.0.7",
"description": "Local-first AI orchestration CLI for software tasks, plans, approvals, and pull requests.",
"type": "module",
"keywords": [
"ai",
"cli",
"developer-tools",
"automation",
"coding-agent",
"linear",
"github",
"pull-request",
"orchestrator",
"parallax"
],
"bin": {
"parallax": "dist/cli/src/index.js"
},
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/src/commands/preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,19 @@ export async function runPreflight(args: string[]) {
detail: geminiOk ? undefined : 'Install Gemini CLI (npm i -g @google/gemini-cli).',
})

const claudeOk = await commandExists('claude')
checks.push({
name: 'At least one agent CLI (codex or gemini)',
ok: codexOk || geminiOk,
name: 'claude CLI',
ok: claudeOk,
required: false,
detail: claudeOk ? undefined : 'Install Claude Code CLI (npm i -g @anthropic-ai/claude-code).',
})

checks.push({
name: 'At least one agent CLI (codex, gemini, or claude)',
ok: codexOk || geminiOk || claudeOk,
required: true,
detail: codexOk || geminiOk ? undefined : 'Install codex or gemini.',
detail: codexOk || geminiOk || claudeOk ? undefined : 'Install codex, gemini, or claude.',
})
} finally {
spinner?.stop()
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@parallax/common",
"version": "0.0.6",
"version": "0.0.7",
"private": true,
"type": "module",
"main": "./dist/index.js",
Expand Down
5 changes: 5 additions & 0 deletions packages/common/src/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class HostExecutor implements LocalExecutor {
cwd: options.cwd,
env: { ...process.env, ...options.env },
shell: false,
stdio: 'pipe',
})

let output = ''
Expand Down Expand Up @@ -70,6 +71,10 @@ export class HostExecutor implements LocalExecutor {
}
}

if (child.stdin) {
child.stdin.end()
}

child.stdout.on('data', createHandler('stdout'))
child.stderr.on('data', createHandler('stderr'))

Expand Down
70 changes: 70 additions & 0 deletions packages/common/test/executor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { EventEmitter } from 'node:events'
import { Writable, Readable } from 'node:stream'
import { afterEach, describe, expect, it, vi } from 'vitest'

const { spawnMock } = vi.hoisted(() => ({
spawnMock: vi.fn(),
}))

vi.mock('child_process', () => ({
spawn: spawnMock,
}))

import { HostExecutor } from '../src/executor'

class MockWritable extends Writable {
endSpy = vi.fn()

override _write(
_chunk: any,
_encoding: BufferEncoding,
callback: (error?: Error | null) => void
) {
callback()
}

override end(...args: any[]) {
this.endSpy(...args)
return super.end(...args)
}
}

class MockChildProcess extends EventEmitter {
stdin = new MockWritable()
stdout = new Readable({ read() {} })
stderr = new Readable({ read() {} })
}

afterEach(() => {
vi.restoreAllMocks()
spawnMock.mockReset()
})

describe('HostExecutor', () => {
it('closes stdin for non-interactive child processes', async () => {
const child = new MockChildProcess()
spawnMock.mockReturnValue(child)

const executor = new HostExecutor()
const execution = executor.executeCommand(['echo', 'ok'], {
cwd: '/tmp',
})

setTimeout(() => {
child.emit('close', 0)
}, 0)

await execution

expect(spawnMock).toHaveBeenCalledWith(
'echo',
['ok'],
expect.objectContaining({
cwd: '/tmp',
shell: false,
stdio: 'pipe',
})
)
expect(child.stdin.endSpy).toHaveBeenCalledTimes(1)
})
})
2 changes: 1 addition & 1 deletion packages/marketing/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@parallax/marketing",
"private": true,
"version": "0.0.6",
"version": "0.0.7",
"type": "module",
"scripts": {
"dev": "node ./scripts/sync-docs.mjs && vite",
Expand Down
1 change: 1 addition & 0 deletions packages/marketing/src/components/IntegrationsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const steps = [
label: "Running",
items: [
{ href: "https://openai.com/codex", logo: "/logos/codex.svg", label: "Codex", imgClass: "invert" },
{ href: "https://claude.ai/code", logo: "/logos/claude.svg", label: "Claude Code" },
{ href: "https://deepmind.google/technologies/gemini", logo: "/logos/gemini.svg", label: "Gemini" },
],
},
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestrator/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@parallax/orchestrator",
"version": "0.0.6",
"version": "0.0.7",
"private": true,
"type": "module",
"scripts": {
Expand Down
Loading
Loading