Skip to content
Open
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