mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
feat(daemon): add Kiro CLI agent adapter (#185)
* feat(daemon): add Kiro CLI agent adapter * docs: add Kiro CLI to README and agent-adapters * test(daemon): add kiro fetchModels fallback test
This commit is contained in:
parent
147a438c1a
commit
534349aa4c
4 changed files with 43 additions and 5 deletions
11
README.md
11
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Open Design
|
||||
|
||||
> **The open-source alternative to [Claude Design][cd].** Local-first, web-deployable, BYOK at every layer — **10 coding-agent CLIs** auto-detected on your `PATH` (Claude Code, Codex, Cursor Agent, Gemini CLI, OpenCode, Qwen, GitHub Copilot CLI, Hermes, Kimi, Pi) become the design engine, driven by **31 composable Skills** and **72 brand-grade Design Systems**. No CLI? An OpenAI-compatible BYOK proxy is the same loop minus the spawn.
|
||||
> **The open-source alternative to [Claude Design][cd].** Local-first, web-deployable, BYOK at every layer — **11 coding-agent CLIs** auto-detected on your `PATH` (Claude Code, Codex, Cursor Agent, Gemini CLI, OpenCode, Qwen, GitHub Copilot CLI, Hermes, Kimi, Pi, Kiro) become the design engine, driven by **31 composable Skills** and **72 brand-grade Design Systems**. No CLI? An OpenAI-compatible BYOK proxy is the same loop minus the spawn.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/banner.png" alt="Open Design — editorial cover: design with the agent on your laptop" width="100%" />
|
||||
|
|
@ -49,7 +49,7 @@ OD stands on four open-source shoulders:
|
|||
|
||||
| | What you get |
|
||||
|---|---|
|
||||
| **Coding-agent CLIs (10)** | Claude Code · Codex CLI · Cursor Agent · Gemini CLI · OpenCode · Qwen Code · GitHub Copilot CLI · Hermes (ACP) · Kimi CLI (ACP) · Pi (RPC) — auto-detected on `PATH`, swap with one click |
|
||||
| **Coding-agent CLIs (11)** | Claude Code · Codex CLI · Cursor Agent · Gemini CLI · OpenCode · Qwen Code · GitHub Copilot CLI · Hermes (ACP) · Kimi CLI (ACP) · Pi (RPC) · Kiro CLI (ACP) — auto-detected on `PATH`, swap with one click |
|
||||
| **BYOK fallback** | OpenAI-compatible proxy at `/api/proxy/stream` — paste `baseUrl` + `apiKey` + `model` and any vendor (Anthropic-via-OpenAI, DeepSeek, Groq, MiMo, OpenRouter, your self-hosted vLLM, or any other OpenAI-compatible provider) becomes the engine. Internal-IP/SSRF blocked at the daemon edge. |
|
||||
| **Design systems built-in** | **72** — 2 hand-authored starters + 70 product systems (Linear, Stripe, Vercel, Airbnb, Tesla, Notion, Anthropic, Apple, Cursor, Supabase, Figma, Xiaohongshu, …) imported from [`awesome-design-md`][acd2] |
|
||||
| **Skills built-in** | **31** — 27 in `prototype` mode (web-prototype, saas-landing, dashboard, mobile-app, gamified-app, social-carousel, magazine-poster, dating-web, sprite-animation, motion-frames, critique, tweaks, wireframe-sketch, pm-spec, eng-runbook, finance-report, hr-onboarding, invoice, kanban-board, team-okrs, …) + 4 in `deck` mode (`guizang-ppt` · `simple-deck` · `replit-deck` · `weekly-update`). Grouped in the picker by `scenario`: design / marketing / operation / engineering / product / finance / hr / sale / personal. |
|
||||
|
|
@ -215,7 +215,7 @@ Adding a skill takes one folder. Read [`docs/skills-protocol.md`](docs/skills-pr
|
|||
|
||||
### 1 · We don't ship an agent. Yours is good enough.
|
||||
|
||||
The daemon scans your `PATH` for [`claude`](https://docs.anthropic.com/en/docs/claude-code), [`codex`](https://github.com/openai/codex), [`cursor-agent`](https://www.cursor.com/cli), [`gemini`](https://github.com/google-gemini/gemini-cli), [`opencode`](https://opencode.ai/), [`qwen`](https://github.com/QwenLM/qwen-code), [`copilot`](https://github.com/features/copilot/cli), `hermes`, `kimi`, and [`pi`](https://github.com/mariozechner/pi-ai) on startup. Whichever ones it finds become candidate design engines — driven over stdio with one adapter per CLI, swappable from the model picker. Inspired by [`multica`](https://github.com/multica-ai/multica) and [`cc-switch`](https://github.com/farion1231/cc-switch). No CLI installed? `POST /api/proxy/stream` is the same pipeline minus the spawn — paste any OpenAI-compatible `baseUrl` + `apiKey` and the daemon forwards SSE chunks back, with loopback / link-local / RFC1918 destinations rejected at the edge.
|
||||
The daemon scans your `PATH` for [`claude`](https://docs.anthropic.com/en/docs/claude-code), [`codex`](https://github.com/openai/codex), [`cursor-agent`](https://www.cursor.com/cli), [`gemini`](https://github.com/google-gemini/gemini-cli), [`opencode`](https://opencode.ai/), [`qwen`](https://github.com/QwenLM/qwen-code), [`copilot`](https://github.com/features/copilot/cli), `hermes`, `kimi`, [`pi`](https://github.com/mariozechner/pi-ai), and [`kiro-cli`](https://kiro.dev) on startup. Whichever ones it finds become candidate design engines — driven over stdio with one adapter per CLI, swappable from the model picker. Inspired by [`multica`](https://github.com/multica-ai/multica) and [`cc-switch`](https://github.com/farion1231/cc-switch). No CLI installed? `POST /api/proxy/stream` is the same pipeline minus the spawn — paste any OpenAI-compatible `baseUrl` + `apiKey` and the daemon forwards SSE chunks back, with loopback / link-local / RFC1918 destinations rejected at the edge.
|
||||
|
||||
### 2 · Skills are files, not plugins.
|
||||
|
||||
|
|
@ -277,7 +277,7 @@ Every layer is composable. Every layer is a file you can edit. Read [`apps/web/s
|
|||
▼
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ claude · codex · gemini · opencode · cursor-agent · qwen │
|
||||
│ copilot · hermes (ACP) · kimi (ACP) · pi (RPC) │
|
||||
│ copilot · hermes (ACP) · kimi (ACP) · pi (RPC) · kiro (ACP) │
|
||||
│ reads SKILL.md + DESIGN.md, writes artifacts to disk │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
|
@ -286,7 +286,7 @@ Every layer is composable. Every layer is a file you can edit. Read [`apps/web/s
|
|||
|---|---|
|
||||
| Frontend | Next.js 16 App Router + React 18 + TypeScript, Vercel-deployable |
|
||||
| Daemon | Node 24 · Express · SSE streaming · `better-sqlite3`; tables: `projects` · `conversations` · `messages` · `tabs` · `templates` |
|
||||
| Agent transport | `child_process.spawn`; typed-event parsers for `claude-stream-json` (Claude Code), `copilot-stream-json` (Copilot), `json-event-stream` per-CLI parsers (Codex / Gemini / OpenCode / Cursor Agent), `acp-json-rpc` (Hermes / Kimi via Agent Client Protocol), `pi-rpc` (Pi via stdio JSON-RPC), `plain` (Qwen Code) |
|
||||
| Agent transport | `child_process.spawn`; typed-event parsers for `claude-stream-json` (Claude Code), `copilot-stream-json` (Copilot), `json-event-stream` per-CLI parsers (Codex / Gemini / OpenCode / Cursor Agent), `acp-json-rpc` (Hermes / Kimi / Kiro via Agent Client Protocol), `pi-rpc` (Pi via stdio JSON-RPC), `plain` (Qwen Code) |
|
||||
| BYOK proxy | `POST /api/proxy/stream` → OpenAI-compatible `/v1/chat/completions`, SSE pass-through; rejects loopback / link-local / RFC1918 hosts at the daemon edge |
|
||||
| Storage | Plain files in `.od/projects/<id>/` + SQLite at `.od/app.sqlite` (gitignored, auto-created). Override the root with `OD_DATA_DIR` for test isolation |
|
||||
| Preview | Sandboxed iframe via `srcdoc` + per-skill `<artifact>` parser ([`apps/web/src/artifacts/parser.ts`](apps/web/src/artifacts/parser.ts)) |
|
||||
|
|
@ -553,6 +553,7 @@ Auto-detected from `PATH` on daemon boot. No config required. Streaming dispatch
|
|||
| [GitHub Copilot CLI](https://github.com/features/copilot/cli) | `copilot` | `copilot-stream-json` (typed events) | `copilot -p <prompt> --allow-all-tools --output-format json [--model …] [--add-dir …]` |
|
||||
| [Hermes](https://github.com/eqlabs/hermes) | `hermes` | `acp-json-rpc` (Agent Client Protocol) | `hermes acp --accept-hooks` |
|
||||
| Kimi CLI | `kimi` | `acp-json-rpc` | `kimi acp` |
|
||||
| [Kiro CLI](https://kiro.dev) | `kiro-cli` | `acp-json-rpc` | `kiro-cli acp` |
|
||||
| [Pi](https://github.com/mariozechner/pi-ai) | `pi` | `pi-rpc` (stdio JSON-RPC) | `pi --mode rpc --no-session [--model …] [--thinking …]` (prompt sent as RPC `prompt` command) |
|
||||
| **OpenAI-compatible BYOK** | n/a | SSE pass-through | `POST /api/proxy/stream` → `<baseUrl>/v1/chat/completions`; SSRF-guarded against loopback / link-local / RFC1918 |
|
||||
|
||||
|
|
|
|||
|
|
@ -472,6 +472,24 @@ export const AGENT_DEFS = [
|
|||
promptViaStdin: true,
|
||||
streamFormat: 'pi-rpc',
|
||||
},
|
||||
{
|
||||
id: 'kiro',
|
||||
name: 'Kiro CLI',
|
||||
bin: 'kiro-cli',
|
||||
versionArgs: ['--version'],
|
||||
fetchModels: async (resolvedBin) =>
|
||||
detectAcpModels({
|
||||
bin: resolvedBin,
|
||||
args: ['acp'],
|
||||
timeoutMs: 15_000,
|
||||
defaultModelOption: DEFAULT_MODEL_OPTION,
|
||||
}),
|
||||
fallbackModels: [
|
||||
DEFAULT_MODEL_OPTION,
|
||||
],
|
||||
buildArgs: () => ['acp'],
|
||||
streamFormat: 'acp-json-rpc',
|
||||
},
|
||||
];
|
||||
|
||||
export function resolveOnPath(bin) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { AGENT_DEFS } from '../src/agents.js';
|
|||
|
||||
const codex = AGENT_DEFS.find((agent) => agent.id === 'codex');
|
||||
const cursorAgent = AGENT_DEFS.find((agent) => agent.id === 'cursor-agent');
|
||||
const kiro = AGENT_DEFS.find((agent) => agent.id === 'kiro');
|
||||
const originalDisablePlugins = process.env.OD_CODEX_DISABLE_PLUGINS;
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -67,3 +68,20 @@ test('cursor-agent args deliver prompts via stdin without passing a literal dash
|
|||
'/tmp/od-project',
|
||||
]);
|
||||
});
|
||||
|
||||
test('kiro args use acp subcommand for json-rpc streaming', () => {
|
||||
const args = kiro.buildArgs('', [], [], {});
|
||||
|
||||
assert.deepEqual(args, ['acp']);
|
||||
assert.equal(kiro.streamFormat, 'acp-json-rpc');
|
||||
});
|
||||
|
||||
test('kiro fetchModels falls back to fallbackModels when detection fails', async () => {
|
||||
// fetchModels rejects when the binary doesn't exist; the daemon's
|
||||
// probe() catches this and uses fallbackModels instead.
|
||||
const result = await kiro.fetchModels('/nonexistent/kiro-cli').catch(() => null);
|
||||
|
||||
assert.equal(result, null);
|
||||
assert.ok(Array.isArray(kiro.fallbackModels));
|
||||
assert.equal(kiro.fallbackModels[0].id, 'default');
|
||||
});
|
||||
|
|
@ -91,6 +91,7 @@ If both signals agree, detection is confident. If only one signal fires, we mark
|
|||
| **opencode** | `opencode` | `~/.opencode/` | 〜 | 〜 | ✅ | P2 |
|
||||
| **openclaw** | `openclaw` | `~/.openclaw/` | 〜 | 〜 | 〜 | P2 |
|
||||
| **copilot** | `copilot` | `~/.copilot/` | ❌ | ✅ (`edit` tool) | ✅ (`--output-format json` JSONL) | P2 |
|
||||
| **kiro** | `kiro-cli` | `~/.kiro/` | ❌ | ✅ | ✅ (`acp-json-rpc`) | P2 |
|
||||
|
||||
"P0/P1/P2" correspond to the roadmap phases in [`roadmap.md`](roadmap.md).
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue