Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,12 @@ Inside a project's Studio, the same design system streams out multiple artifact
| [Pi Agent](https://github.com/badlogic/pi-mono) | ✅ Supported | `od mcp install pi` |
| [Mistral Vibe CLI](https://github.com/mistralai/mistral-vibe) | ✅ Supported | `od mcp install vibe` |
| [Hermes Agent](https://github.com/nousresearch/hermes-agent) | ✅ Supported | `od mcp install hermes` |
| [Kimchi](https://github.com/getkimchi/kimchi) | ✅ Supported | `od mcp install kimchi` |

`od mcp install <agent> --print` for a dry-run preview · `--uninstall` to remove · full list with `od mcp install --help`.

<p align="center">
<img src="https://repo-assets.open-design.ai/resources/images/coding-agents.png" alt="The 21 coding-agent CLIs Open Design supports — Claude Code · Codex · OpenCode · Hermes · Antigravity · Gemini · Grok Build · Kimi · Cursor Agent · Qwen · Qoder · GitHub Copilot · Pi · Kiro · Kilo · Mistral Vibe · DeepSeek · Reasonix · Aider · Devin · Trae" width="100%" />
<img src="https://repo-assets.open-design.ai/resources/images/coding-agents.png" alt="The 22 coding-agent CLIs Open Design supports — Claude Code · Codex · OpenCode · Hermes · Antigravity · Gemini · Grok Build · Kimi · Cursor Agent · Qwen · Qoder · GitHub Copilot · Pi · Kiro · Kilo · Mistral Vibe · DeepSeek · Reasonix · Aider · Devin · Trae · Kimchi" width="100%" />
</p>

**No CLI installed?** The BYOK proxy at `POST /api/proxy/{anthropic,openai,azure,google,ollama,senseaudio}/stream` gives you the same loop (no process spawn) — paste `baseUrl` + `apiKey` + `model`, with support for OpenAI, Anthropic, Azure OpenAI, Google Gemini, Ollama, LM Studio, vLLM, or any OpenAI-compatible endpoint. Per-target SSRF protection blocks internal IPs / link-local / CGNAT at the daemon edge.
Expand Down Expand Up @@ -262,7 +263,7 @@ Every deck exports to **HTML** (single file, inlined assets), **PDF** (browser p

Open Design (OD) is the open-source alternative. Same loop, same artifact-first mental model, none of the lock-in:

- 🤖 **Agent-native, model-agnostic.** We don't ship an agent. The `claude` / `codex` / `cursor-agent` / `copilot` / `hermes` / `kimi` already on your `PATH` are the design engine. Swap with one click.
- 🤖 **Agent-native, model-agnostic.** We don't ship an agent. The `claude` / `codex` / `cursor-agent` / `copilot` / `hermes` / `kimi` / `kimchi` already on your `PATH` are the design engine. Swap with one click.
- 🧠 **Brand-grade by default.** Every render reads the active `DESIGN.md` — a 9-section schema covering palette, type, spacing, motion, voice, anti-patterns. 150 systems ship with the repo (Linear, Stripe, Vercel, Airbnb, Apple, Tesla, Notion, Anthropic, Cursor, Supabase, Figma…). Drop a folder in, the picker finds it.
- 🖥️ **Local-first, BYOK at every layer.** Native desktop apps for macOS (Apple Silicon + Intel) and Windows (x64). Linux AppImage on the optional release lane. No telemetry, no cloud round-trip. Before describing daemon data paths, contributors and operators MUST read `AGENTS.md` → **Daemon data directory contract**. This README MUST NOT restate it.
- 🌍 **Composable on three planes.** **Plugins** carry runnable workflows · **skills** carry the agent's design taste · **design systems** carry the brand. All three are plain files anyone can author, version, and publish.
Expand Down Expand Up @@ -562,7 +563,8 @@ Plugin registry endpoint: `GET /api/plugins`. Directory overview → [`plugins/R
┌──────────────────────────────────────────────────────────────────┐
│ claude · codex · cursor-agent · copilot · openclaw · antigravity ·│
│ gemini · opencode · qwen · qoder · hermes (ACP) · kimi (ACP) · │
│ pi (RPC) · kiro · kilo · vibe (ACP) · cline · trae · deepseek │
│ pi (RPC) · kiro · kilo · vibe (ACP) · cline · trae · deepseek · │
│ kimchi (RPC) │
│ reads SKILL.md + DESIGN.md, writes artifacts to disk │
└──────────────────────────────────────────────────────────────────┘
```
Expand Down
1 change: 1 addition & 0 deletions apps/daemon/src/app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ const AGENT_CLI_ENV_KEYS: ReadonlyMap<string, ReadonlySet<string>> = new Map([
['kimi', new Set(['KIMI_BIN'])],
['kiro', new Set(['KIRO_BIN'])],
['kilo', new Set(['KILO_BIN'])],
['kimchi', new Set(['KIMCHI_BIN', 'KIMCHI_API_KEY'])],
['opencode', new Set(['OPENCODE_BIN'])],
['pi', new Set(['PI_BIN'])],
['qoder', new Set(['QODER_BIN'])],
Expand Down
10 changes: 10 additions & 0 deletions apps/daemon/src/mcp-agent-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const AGENT_SLUGS = [
'kiro',
'trae',
'opencode',
'kimchi',
] as const;

export type AgentSlug = (typeof AGENT_SLUGS)[number];
Expand Down Expand Up @@ -274,6 +275,15 @@ export function planAgentInstall(
serverKey: serverName,
entry: jsonEntry(spec),
};
case 'kimchi':
return {
kind: 'json',
slug,
configPath: path.join(home, '.config', 'kimchi', 'harness', 'mcp.json'),
keyPath: ['mcpServers'],
serverKey: serverName,
entry: jsonEntry(spec),
};

// ----- Unverified formats: print-only, never write -----
case 'vibe':
Expand Down
5 changes: 3 additions & 2 deletions apps/daemon/src/memory-llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,8 @@ function chatProtocolFromAgentId(agentId) {
if (id === 'claude') return 'anthropic';
if (id === 'gemini') return 'google';
// Codex, OpenCode, Qwen, DeepSeek, Kimi, Copilot, Pi, Kiro, Kilo,
// Vibe, Devin, Hermes, Cursor-Agent, Qoder all use the OpenAI chat-
// completions wire format.
// Vibe, Devin, Hermes, Cursor-Agent, Qoder, Kimchi all use the
// OpenAI chat-completions wire format.
if (
id === 'codex'
|| id === 'opencode'
Expand All @@ -268,6 +268,7 @@ function chatProtocolFromAgentId(agentId) {
|| id === 'hermes'
|| id === 'cursor-agent'
|| id === 'qoder'
|| id === 'kimchi'
) {
return 'openai';
}
Expand Down
71 changes: 71 additions & 0 deletions apps/daemon/src/runtimes/defs/kimchi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import path from "node:path";
import {
DEFAULT_MODEL_OPTION,
execAgentFile,
parsePiModels,
} from "./shared.js";
import type { RuntimeAgentDef } from "../types.js";

export const kimchiAgentDef = {
id: "kimchi",
name: "Kimchi",
bin: "kimchi",
versionArgs: ["--version"],
// Kimchi is built on the pi-mono SDK (same engine as pi), so it inherits
// pi's `--mode rpc` JSON-RPC-over-stdio transport, `--list-models` TSV
// output, `--thinking` levels, and `--append-system-prompt` for extra
// directories. Using pi-rpc instead of ACP gives us multimodal image
// support, reasoning levels, richer event stream, and avoids the ACP
// `mcpServers` rejection issue.
versionProbeTimeoutMs: 15_000,
fetchModels: async (resolvedBin, env) => {
try {
const { stderr } = await execAgentFile(resolvedBin, ["--list-models"], {
env,
timeout: 60_000,
maxBuffer: 8 * 1024 * 1024,
});
const parsed = parsePiModels(stderr);
if (!parsed || parsed.length === 0) return null;
return parsed;
} catch {
return null;
}
},
fallbackModels: [
DEFAULT_MODEL_OPTION,
{ id: "kimi-k2.6", label: "kimi-k2.6" },
{ id: "minimax-m3", label: "minimax-m3" },
{ id: "deepseek-v4-flash", label: "deepseek-v4-flash" },
],
reasoningOptions: [
{ id: "default", label: "Default" },
{ id: "off", label: "Off" },
{ id: "minimal", label: "Minimal" },
{ id: "low", label: "Low" },
{ id: "medium", label: "Medium" },
{ id: "high", label: "High" },
{ id: "xhigh", label: "XHigh" },
],
buildArgs: (_prompt, _imagePaths, extraAllowedDirs = [], options = {}) => {
const args = ["--mode", "rpc"];
if (options.model && options.model !== "default") {
args.push("--model", options.model);
}
if (options.reasoning && options.reasoning !== "default") {
args.push("--thinking", options.reasoning);
}
const dirs = (extraAllowedDirs || []).filter(
(d) => typeof d === "string" && path.isAbsolute(d),
);
for (const d of dirs) {
args.push("--append-system-prompt", d);
}
return args;
},
promptViaStdin: true,
streamFormat: "pi-rpc",
supportsImagePaths: true,
installUrl: "https://kimchi.dev",
docsUrl: "https://docs.kimchi.dev",
} satisfies RuntimeAgentDef;
1 change: 1 addition & 0 deletions apps/daemon/src/runtimes/executables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const AGENT_BIN_ENV_KEYS = new Map<string, string>([
['kimi', 'KIMI_BIN'],
['kiro', 'KIRO_BIN'],
['kilo', 'KILO_BIN'],
['kimchi', 'KIMCHI_BIN'],
['mimo', 'MIMO_BIN'],
['opencode', 'OPENCODE_BIN'],
['byok-opencode', 'OPENCODE_BIN'],
Expand Down
2 changes: 2 additions & 0 deletions apps/daemon/src/runtimes/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { antigravityAgentDef } from './defs/antigravity.js';
import { codebuddyAgentDef } from './defs/codebuddy.js';
import { reasonixAgentDef } from './defs/reasonix.js';
import { mimoAgentDef } from './defs/mimo.js';
import { kimchiAgentDef } from './defs/kimchi.js';
import { readLocalAgentProfileDefs as readLocalAgentProfileDefsFromFile } from './local-profiles.js';
import type { RuntimeAgentDef } from './types.js';

Expand Down Expand Up @@ -52,6 +53,7 @@ const BASE_AGENT_DEFS: RuntimeAgentDef[] = [
reasonixAgentDef,
codebuddyAgentDef,
mimoAgentDef,
kimchiAgentDef,
];

export function readLocalAgentProfileDefs(
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/tests/mcp-agent-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('agent slug guard', () => {
it('accepts every documented slug and rejects others', () => {
for (const s of AGENT_SLUGS) expect(isAgentSlug(s)).toBe(true);
expect(isAgentSlug('not-an-agent')).toBe(false);
expect(AGENT_SLUGS).toHaveLength(14);
expect(AGENT_SLUGS).toHaveLength(15);
expect(isAgentSlug('kiro')).toBe(true);
});
});
Expand Down
1 change: 1 addition & 0 deletions apps/daemon/tests/runtimes/env-and-detection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ test('resolveAgentExecutable supports configured binary overrides for non-Codex
['deepseek', 'deepseek', 'DEEPSEEK_BIN'],
['trae-cli', 'traecli', 'TRAE_CLI_BIN'],
['aider', 'aider', 'AIDER_BIN'],
['kimchi', 'kimchi', 'KIMCHI_BIN'],
];
const dir = mkdtempSync(join(tmpdir(), 'od-agent-bin-overrides-'));
try {
Expand Down
1 change: 1 addition & 0 deletions apps/daemon/tests/runtimes/helpers/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const qoder = requireAgent('qoder');
export const qwen = requireAgent('qwen');
export const opencode = requireAgent('opencode');
export const mimo = requireAgent('mimo');
export const kimchi = requireAgent('kimchi');
export const grokBuild = requireAgent('grok-build');
export const aider = requireAgent('aider');
export const antigravity = requireAgent('antigravity');
Expand Down
Binary file added apps/web/public/agent-icons/kimchi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ const CANONICAL_AGENT_ORDER = [
'pi',
'kiro',
'kilo',
'kimchi',
'vibe',
'deepseek',
'aider',
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/AgentIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const ICON_EXT: Record<string, 'svg' | 'png'> = {
hermes: 'svg',
'grok-build': 'svg',
kimi: 'svg',
kimchi: 'png',
pi: 'svg',
kiro: 'svg',
kilo: 'svg',
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/HandoffButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const CLI_ORDER = [
'devin',
'kiro',
'kilo',
'kimchi',
'vibe',
'antigravity',
'aider',
Expand All @@ -95,6 +96,7 @@ const FALLBACK_CLI_TARGETS: CliTarget[] = [
{ id: 'deepseek', name: 'DeepSeek TUI', bin: 'deepseek', available: false },
{ id: 'kimi', name: 'Kimi CLI', bin: 'kimi', available: false },
{ id: 'hermes', name: 'Hermes', bin: 'hermes', available: false },
{ id: 'kimchi', name: 'Kimchi', bin: 'kimchi', available: false },
{ id: 'devin', name: 'Devin for Terminal', bin: 'devin', available: false },
{ id: 'kiro', name: 'Kiro CLI', bin: 'kiro-cli', available: false },
{ id: 'kilo', name: 'Kilo', bin: 'kilo', available: false },
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/MemoryModelInline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function chatProtocolFromAgent(
|| id === 'hermes'
|| id === 'cursor-agent'
|| id === 'qoder'
|| id === 'kimchi'
) {
return 'openai';
}
Expand Down
14 changes: 13 additions & 1 deletion docs/agent-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ If both signals agree, detection is confident. If only one signal fires, we mark
| **deepseek** | `deepseek` | `~/.deepseek/` | `~/.deepseek/skills/` | ❌ (prompt-injected) | ✅ | ✅ (plain text) | P2 |
| **qoder** | `qodercli` | Qoder CLI config | Qoder CLI managed | ❌ (prompt-injected) | ✅ | ✅ (`stream-json`) | P2 |
| **pi** | `pi` | `~/.pi/agent/` | `~/.pi/agent/skills/` | ❌ (prompt-injected) | ✅ | ✅ (`pi-rpc` JSON-RPC) | P2 |
| **kimchi** | `kimchi` | `~/.config/kimchi/harness/` | ❌ | ✅ | ✅ (`pi-rpc` JSON-RPC) | P2 |
Comment thread
bentleyvk marked this conversation as resolved.
Outdated

"P0/P1/P2" correspond to the roadmap phases in [`roadmap.md`](roadmap.md).

Expand Down Expand Up @@ -244,7 +245,18 @@ The adapter declares which strategy to use via `capabilities().nativeSkillLoadin
- Models: ships `deepseek-v4-pro` and `deepseek-v4-flash` as fallback hints (1M-token context windows, native thinking-mode streaming). Users can paste any other id (e.g. `nvidia-nim/deepseek-v4-pro`, `fireworks/deepseek-v4-flash`) via the Settings dialog's custom-model input.
- **Gotcha — auth state is not auto-detected.** DeepSeek TUI reads its API key from `~/.deepseek/config.toml` or `DEEPSEEK_API_KEY`. If the user hasn't run `deepseek auth set --provider deepseek` (or set the env var), the first run errors out with a non-actionable message. Detection currently only reports `available: true` based on the binary being on PATH; surface auth state via `deepseek doctor --json` in a follow-up.

### 5.13 Plain stream artifact handoff
### 5.13 Kimchi

- Invocation: `kimchi --mode rpc [--model <id>] [--thinking <level>] [--append-system-prompt <dir> …]`, with the composed prompt delivered over stdin via JSON-RPC. Kimchi is built on the [pi-mono](https://github.com/badlogic/pi-mono) coding agent SDK and inherits pi's RPC transport. Using `--mode rpc` (instead of `--mode acp`) gives us multimodal image support, reasoning levels, richer event stream, and avoids the ACP `mcpServers` rejection issue.
- Streaming: `pi-rpc` JSON-RPC over stdio. Events include `agent_start`, `turn_start/end`, `message_update` (text deltas, thinking deltas, tool calls), `tool_execution_start/end`, `compaction_start`, `auto_retry_start/end`, `extension_error`. `apps/daemon/src/pi-rpc.ts` maps these onto the same UI event set as the other stream parsers.
- Models: dynamic — `kimchi --list-models` prints a TSV table to stderr that the daemon parses into provider/model picker entries. Fallback hints for the default orchestrator/builder/explorer models (`kimi-k2.6`, `minimax-m3`, `deepseek-v4-flash`) are shipped for when the list command times out.
Comment thread
bentleyvk marked this conversation as resolved.
Outdated
- Images: kimchi's RPC `prompt` command supports an `images` field (base64-encoded `ImageContent` objects), inherited from pi-mono. The daemon reads validated `imagePaths` at session attach time and includes them in the prompt command.
- Thinking: the daemon exposes kimchi's `--thinking` levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`) in the Settings model picker.
- Skills: prompt injection in v1. `extraAllowedDirs` (skill seed and design-system directories) are forwarded as `--append-system-prompt` repeatable flags so the agent knows these directories exist and can Read files inside them. Same pattern as the pi adapter.
- MCP: Kimchi does not accept `mcpServers` in the ACP `session/new` message — it rejects with `Invalid params: mcpServers is not supported`. MCP servers are configured via `~/.config/kimchi/harness/mcp.json` (same `mcpServers` JSON schema as Claude Desktop / Cursor). `od mcp install kimchi` writes to the JSON config file directly.
- Auth: API key resolved from `KIMCHI_API_KEY` env var or `~/.config/kimchi/config.json`. Run `kimchi setup` for interactive first-time configuration.

### 5.14 Plain stream artifact handoff

Adapters with `streamFormat: 'plain'` do not expose structured file-write tool calls to the daemon. Their stdout is still a valid artifact handoff when the model emits Anthropic-style source blocks:

Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/analytics/events/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ export function agentIdToTracking(agentId: string | null | undefined): TrackingC
return 'pi';
case 'kilo':
return 'kilo';
case 'kimchi':
return 'kimchi';
case 'amr':
return 'amr';
default:
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/analytics/events/shared-enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export type TrackingCliProviderId =
| 'github_copilot_cli'
| 'pi'
| 'kilo'
| 'kimchi'
| 'amr'
| 'other';

Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/src/analytics/events/ui-click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,7 @@ export const TRACKING_HANDOFF_TARGET_IDS = [
// code-agent CLIs (HandoffButton CLI_ORDER; qoder / antigravity already above)
'amr', 'claude', 'codex', 'opencode', 'cursor-agent', 'gemini', 'qwen',
'copilot', 'grok-build', 'deepseek', 'kimi', 'hermes', 'devin', 'kiro',
'kilo', 'vibe', 'aider', 'trae-cli', 'pi', 'reasonix',
'kilo', 'kimchi', 'vibe', 'aider', 'trae-cli', 'pi', 'reasonix',
] as const;

export type TrackingHandoffTargetId =
Expand Down
Loading