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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</p>

<p align="center">
Works with Claude Code, Codex, and Gemini · Every change isolated in its own git worktree · Free, open source, no extra platform fee
Works with Claude Code, Codex, Gemini, and Kimi Code · Every change isolated in its own git worktree · Free, open source, no extra platform fee
</p>

<p align="center">
Expand Down Expand Up @@ -45,7 +45,7 @@

## Why Parallel Code?

- **Use the AI coding tools you already trust** — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), and [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) — all from one interface.
- **Use the AI coding tools you already trust** — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Kimi Code CLI](https://github.com/MoonshotAI/kimi-code), and [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) — all from one interface.
- **Free and open source** — no extra subscription required. MIT licensed.
- **Keep every change isolated and reviewable** — each task gets its own git branch and worktree automatically.
- **Run agents in parallel, not in sequence** — five agents on five features at the same time, zero conflicts.
Expand Down Expand Up @@ -115,7 +115,7 @@ When you're happy with the result, merge the branch back to main from the sideba
- **macOS** — `.dmg` (universal)
- **Linux** — `.AppImage` or `.deb`

2. **Install at least one AI coding CLI:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Antigravity CLI](https://antigravity.google/), or [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli)
2. **Install at least one AI coding CLI:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Kimi Code CLI](https://github.com/MoonshotAI/kimi-code), [Antigravity CLI](https://antigravity.google/), or [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli)

3. **Open Parallel Code**, point it at a git repo, and start dispatching tasks.

Expand Down
3 changes: 2 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN ln -sf "$(command -v fdfind)" /usr/local/bin/fd 2>/dev/null || true

# AI agent CLIs — must be present so Docker-mode tasks can execute them
RUN npm install -g @anthropic-ai/claude-code @openai/codex @google/gemini-cli opencode-ai
# Keep Kimi below 0.33: newer releases block fresh worktrees on workspace trust.
RUN npm install -g @anthropic-ai/claude-code @openai/codex @google/gemini-cli opencode-ai @moonshot-ai/kimi-code@0.32.0

# Antigravity CLI (agy) — distributed as a Go binary via the official installer
# (not on npm). The installer's `--dir` flag drops the binary straight into a
Expand Down
4 changes: 4 additions & 0 deletions electron/ipc/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ describe('getSkipPermissionsArgs', () => {

expect(getSkipPermissionsArgs('claude')).toEqual(['--dangerously-skip-permissions']);
});

it('returns Kimi Code skip-permission args', () => {
expect(getSkipPermissionsArgs('kimi')).toEqual(['--yolo']);
});
});
9 changes: 9 additions & 0 deletions electron/ipc/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ const DEFAULT_AGENTS: AgentDef[] = [
skip_permissions_args: ['--yolo'],
description: "Google's Gemini CLI agent",
},
{
id: 'kimi',
name: 'Kimi Code CLI',
command: 'kimi',
args: [],
resume_args: ['--continue'],
skip_permissions_args: ['--yolo'],
description: "Moonshot AI's Kimi Code CLI agent",
},
{
id: 'opencode',
name: 'OpenCode',
Expand Down
1 change: 1 addition & 0 deletions electron/ipc/pty.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ describe('spawnAgent docker mode', () => {
['opencode', '.config/opencode'],
['copilot', '.config/github-copilot'],
['agy', '.gemini/antigravity-cli'],
['kimi', '.kimi-code'],
])(
'%s bind-mounts a user-owned host directory when shareDockerAgentAuth is enabled',
(command, relDir) => {
Expand Down
1 change: 1 addition & 0 deletions electron/ipc/pty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ const AGENT_CONFIG_DIRS: Record<string, string[]> = {
opencode: ['.config/opencode'],
copilot: ['.config/github-copilot'],
agy: ['.gemini/antigravity-cli'],
kimi: ['.kimi-code'],
};

// Config files (not directories) each agent CLI uses for auth, relative to HOME.
Expand Down
2 changes: 2 additions & 0 deletions electron/ipc/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,7 @@ export function registerAllHandlers(win: BrowserWindow): void {
landingSummary?: string;
landedMetadata?: import('../mcp/types.js').LandedMetadata;
mcpConfigPath?: string;
autoDiscoveredMcpConfig?: import('../mcp/types.js').AutoDiscoveredMcpConfigState;
agentCommand?: string;
preambleFileExistedBefore?: boolean;
initialPrompt?: string;
Expand Down Expand Up @@ -1549,6 +1550,7 @@ export function registerAllHandlers(win: BrowserWindow): void {
landingSummary: args.landingSummary,
landedMetadata: args.landedMetadata,
mcpConfigPath: args.mcpConfigPath,
autoDiscoveredMcpConfig: args.autoDiscoveredMcpConfig,
agentCommand: args.agentCommand,
preambleFileExistedBefore: args.preambleFileExistedBefore,
initialPrompt: args.initialPrompt,
Expand Down
11 changes: 11 additions & 0 deletions electron/mcp/agent-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
isAntigravityCommand,
isCodexCommand,
isCopilotCommand,
isKimiCommand,
} from './agent-args.js';

const config = {
Expand Down Expand Up @@ -67,6 +68,16 @@ describe('MCP agent launch args', () => {
expect(buildMcpLaunchArgs('agy', '/tmp/config.json', config)).toEqual([]);
});

it('detects Kimi commands by executable name', () => {
expect(isKimiCommand('kimi')).toBe(true);
expect(isKimiCommand('/home/agent/.local/bin/kimi')).toBe(true);
expect(isKimiCommand('claude')).toBe(false);
});

it('emits no --mcp-config for Kimi Code', () => {
expect(buildMcpLaunchArgs('kimi', '/tmp/config.json', config)).toEqual([]);
});

it('detects copilot commands by executable name', () => {
expect(isCopilotCommand('copilot')).toBe(true);
expect(isCopilotCommand('/opt/homebrew/bin/copilot')).toBe(true);
Expand Down
9 changes: 9 additions & 0 deletions electron/mcp/agent-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export function isAntigravityCommand(command: string): boolean {
return command.split('/').pop() === 'agy';
}

export function isKimiCommand(command: string): boolean {
return command.split('/').pop() === 'kimi';
}

export function isCopilotCommand(command: string): boolean {
return command.split('/').pop() === 'copilot';
}
Expand Down Expand Up @@ -53,6 +57,11 @@ export function buildMcpLaunchArgs(
if (isAntigravityCommand(command)) {
return [];
}
// Kimi Code auto-discovers user and project MCP config files and does not
// accept the generic `--mcp-config` flag.
if (isKimiCommand(command)) {
return [];
}
// Copilot has no `--mcp-config` flag — passing it makes Copilot exit immediately
// with "unknown option" before the prompt is ever sent (#146). It accepts
// `--additional-mcp-config <@file|json>` (and also auto-discovers a workspace
Expand Down
15 changes: 15 additions & 0 deletions electron/mcp/coordinator-test-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const enoent = () => Object.assign(new Error('ENOENT'), { code: 'ENOENT' });

const mocks = vi.hoisted(() => {
const mockExecFile = vi.fn();
const mockSpawnSync = vi.fn();
const mockWriteFileSync = vi.fn();
const mockReadFileSync = vi.fn();
const mockExistsSync = vi.fn();
Expand All @@ -36,6 +37,7 @@ const mocks = vi.hoisted(() => {
const mockFsMkdir = vi.fn();
const mockAtomicWriteFileSync = vi.fn();
const mockAtomicWriteFile = vi.fn();
const mockAppendGitInfoExcludeBlock = vi.fn();
const mockNotifyRenderer = vi.fn();
const mockLogInfo = vi.fn();
const mockLogWarn = vi.fn();
Expand All @@ -55,6 +57,7 @@ const mocks = vi.hoisted(() => {

return {
mockExecFile,
mockSpawnSync,
mockWriteFileSync,
mockReadFileSync,
mockExistsSync,
Expand All @@ -67,6 +70,7 @@ const mocks = vi.hoisted(() => {
mockFsMkdir,
mockAtomicWriteFileSync,
mockAtomicWriteFile,
mockAppendGitInfoExcludeBlock,
mockNotifyRenderer,
mockLogInfo,
mockLogWarn,
Expand All @@ -88,6 +92,7 @@ const mocks = vi.hoisted(() => {

vi.mock('child_process', () => ({
execFile: mocks.mockExecFile,
spawnSync: mocks.mockSpawnSync,
}));

vi.mock('fs', () => ({
Expand All @@ -111,6 +116,10 @@ vi.mock('./atomic.js', () => ({
atomicWriteFile: mocks.mockAtomicWriteFile,
}));

vi.mock('../ipc/git-exclude.js', () => ({
appendGitInfoExcludeBlock: mocks.mockAppendGitInfoExcludeBlock,
}));

vi.mock('../shared/prompt-detect.js', () => ({
stripAnsi: (s: string) =>
s.replace(
Expand Down Expand Up @@ -214,6 +223,7 @@ vi.mock('../log.js', () => ({

export const {
mockExecFile,
mockSpawnSync,
mockWriteFileSync,
mockReadFileSync,
mockExistsSync,
Expand All @@ -226,6 +236,7 @@ export const {
mockFsMkdir,
mockAtomicWriteFileSync,
mockAtomicWriteFile,
mockAppendGitInfoExcludeBlock,
mockNotifyRenderer,
mockLogInfo,
mockLogWarn,
Expand Down Expand Up @@ -276,6 +287,8 @@ export function resetCoordinatorMocks(): void {
return { on: vi.fn() };
},
);
mockSpawnSync.mockReset();
mockSpawnSync.mockReturnValue({ status: 1, error: undefined, stderr: Buffer.alloc(0) });

mockWriteFileSync.mockReset();
mockReadFileSync.mockReset();
Expand All @@ -299,6 +312,8 @@ export function resetCoordinatorMocks(): void {
mockAtomicWriteFileSync.mockReset();
mockAtomicWriteFile.mockReset();
mockAtomicWriteFile.mockResolvedValue(undefined);
mockAppendGitInfoExcludeBlock.mockReset();
mockAppendGitInfoExcludeBlock.mockReturnValue('appended');

mockNotifyRenderer.mockReset();
mockLogInfo.mockReset();
Expand Down
Loading
Loading