diff --git a/Makefile b/Makefile index 8bcd72c7..53514eec 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all lint test format check +.PHONY: all lint test format check install-kiro all: lint test @@ -13,3 +13,6 @@ test: check: lint test @echo "All checks passed" + +install-kiro: + uv run python3 clients/kiro/install.py diff --git a/README.md b/README.md index cba90462..2c31338d 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,32 @@ sub-agent. Verify with `/plugin list`, `/mcp` (expect `sdpm` connected), and `/a skill drives briefing → outline → art direction, then delegates Phase 2 (compose) to parallel composer sub-agents, and finishes with review. -> **Which entry point?** **Kiro CLI** → use the Layer 1 skill. **Claude Desktop / other MCP -> clients** → use the Layer 2 local MCP server. **Claude Code** → use this plugin (it wraps the -> same Layer 2 MCP server with a CC-native skill + parallel compose sub-agent). +> **Which entry point?** **Kiro CLI** → `make install-kiro` (next section). **Claude Desktop / +> other MCP clients** → use the Layer 2 local MCP server. **Claude Code** → use this plugin (it +> wraps the same Layer 2 MCP server with a CC-native skill + parallel compose sub-agent). + +### 🛠 Kiro CLI setup (skill + parallel compose sub-agents) + +Kiro CLI users get the same flow — the `sdpm-vibe` skill drives Phase 1, then dispatches +parallel `sdpm-composer` sub-agents for Phase 2 — via one make target. The same +prerequisites apply: [`uv`](https://docs.astral.sh/uv/) on your `PATH`, plus **LibreOffice** +and **poppler** for slide previews. + +```bash +git clone https://github.com/aws-samples/sample-spec-driven-presentation-maker.git +cd sample-spec-driven-presentation-maker +make install-kiro +kiro-cli chat # then just ask: "make slides about ..." +``` + +`make install-kiro` symlinks the skills into `~/.kiro/skills/`, generates the composer +agent config at `~/.kiro/agents/sdpm-composer.json`, and registers the `sdpm` local MCP +server in the global `~/.kiro/settings/mcp.json` (pass `--agent NAME` to +`clients/kiro/install.py` to target a specific agent config instead). It is safe to re-run. + +**Updating:** `git pull` in the checkout is enough — skills are symlinks and the composer +prompt is a `file://` reference into the checkout, so no reinstall is needed. Re-run +`make install-kiro` only if you move the checkout to a different path. ### 🚀 One-Click Deploy — Just an AWS Account to Get Started diff --git a/README_ja.md b/README_ja.md index 4a57ffd3..b57ff5a3 100644 --- a/README_ja.md +++ b/README_ja.md @@ -73,10 +73,33 @@ Claude Code ユーザーはプラグインで一括導入できます(手動 art direction を進め、Phase 2(compose)を複数の composer サブエージェントへ並列委譲し、 review まで実施します。 -> **どの入口を使う?** **Kiro CLI** → Layer 1 の skill。**Claude Desktop / その他 MCP -> クライアント** → Layer 2 のローカル MCP サーバー。**Claude Code** → このプラグイン(同じ +> **どの入口を使う?** **Kiro CLI** → `make install-kiro`(次節)。**Claude Desktop / その他 +> MCP クライアント** → Layer 2 のローカル MCP サーバー。**Claude Code** → このプラグイン(同じ > Layer 2 MCP サーバーを CC ネイティブの skill + 並列 compose サブエージェントで包んだもの)。 +### 🛠 Kiro CLI セットアップ(skill + 並列 compose サブエージェント) + +Kiro CLI ユーザーも同じフロー — `sdpm-vibe` skill が Phase 1 を進め、Phase 2 を +`sdpm-composer` サブエージェントへ並列委譲 — を make ターゲット 1 つで導入できます。 +事前インストールは同じく [`uv`](https://docs.astral.sh/uv/)(`PATH` に)、および +スライドプレビュー用の **LibreOffice** と **poppler** です。 + +```bash +git clone https://github.com/aws-samples/sample-spec-driven-presentation-maker.git +cd sample-spec-driven-presentation-maker +make install-kiro +kiro-cli chat # あとは「〇〇のスライドを作って」と頼むだけ +``` + +`make install-kiro` は skill を `~/.kiro/skills/` へ symlink し、composer エージェント設定 +`~/.kiro/agents/sdpm-composer.json` を生成し、`sdpm` ローカル MCP サーバーをグローバル +`~/.kiro/settings/mcp.json` に登録します(特定のエージェント設定に追加したい場合は +`clients/kiro/install.py --agent NAME` を直接実行)。再実行しても安全です。 + +**更新:** チェックアウトで `git pull` するだけで反映されます — skill は symlink、composer +プロンプトはチェックアウトへの `file://` 参照のため再インストール不要です。チェックアウトを +別パスへ移動した場合のみ `make install-kiro` を再実行してください。 + ### 🚀 AWS アカウントだけですぐに開始! ワンクリックデプロイ | リージョン | デプロイ | diff --git a/clients/kiro/install.py b/clients/kiro/install.py new file mode 100644 index 00000000..156cb037 --- /dev/null +++ b/clients/kiro/install.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Install sdpm support for Kiro CLI. + +Idempotent installer that wires this repository checkout into Kiro CLI: + +1. Symlinks the skills under ``skills/`` (sdpm-vibe, sdpm-spec, ...) into + ``~/.kiro/skills/`` so Kiro CLI discovers them. +2. Renders ``clients/kiro/sdpm-composer.json.tmpl`` (resolving ``{{CHECKOUT}}`` + to this checkout's absolute path) into ``~/.kiro/agents/sdpm-composer.json`` + so the sdpm-vibe skill can dispatch composer sub-agents via the subagent tool. +3. Registers the ``sdpm`` MCP server in the global ``~/.kiro/settings/mcp.json`` + via ``kiro-cli mcp add`` (skipped if already registered). Use ``--agent NAME`` + to register it into a specific agent config instead. + +The repository stays the single source of truth: skills are symlinks and the +composer prompt is a ``file://`` reference, so ``git pull`` updates take effect +without re-running this script. Re-run only if you move the checkout. +""" + +from __future__ import annotations + +import argparse +import json +import shutil +import subprocess +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +REPO_ROOT = HERE.parent.parent +SKILLS_SRC = REPO_ROOT / "skills" +TEMPLATE = HERE / "sdpm-composer.json.tmpl" + +KIRO_HOME = Path.home() / ".kiro" +SKILLS_DEST = KIRO_HOME / "skills" +AGENTS_DEST = KIRO_HOME / "agents" +GLOBAL_MCP_JSON = KIRO_HOME / "settings" / "mcp.json" + +MCP_SERVER_NAME = "sdpm" + + +def info(msg: str) -> None: + print(f" {msg}") + + +def warn(msg: str) -> None: + print(f" WARNING: {msg}", file=sys.stderr) + + +def symlink_skills() -> None: + """Symlink each skill directory under skills/ into ~/.kiro/skills/.""" + print("[1/3] Linking skills into ~/.kiro/skills/") + SKILLS_DEST.mkdir(parents=True, exist_ok=True) + for src in sorted(SKILLS_SRC.iterdir()): + if not src.is_dir() or not (src / "SKILL.md").is_file(): + continue + link = SKILLS_DEST / src.name + if link.is_symlink(): + if link.resolve() == src.resolve(): + info(f"{link} -> {src} (already linked, skipped)") + continue + link.unlink() + link.symlink_to(src) + info(f"{link} -> {src} (re-pointed stale symlink)") + elif link.exists(): + warn( + f"{link} exists and is not a symlink — left untouched. " + f"Remove it and re-run to link {src}." + ) + else: + link.symlink_to(src) + info(f"{link} -> {src} (linked)") + + +def render_composer_agent() -> Path: + """Render the composer agent config template into ~/.kiro/agents/.""" + print("[2/3] Generating ~/.kiro/agents/sdpm-composer.json") + rendered = TEMPLATE.read_text(encoding="utf-8").replace( + "{{CHECKOUT}}", str(REPO_ROOT) + ) + config = json.loads(rendered) # validate JSON before writing + AGENTS_DEST.mkdir(parents=True, exist_ok=True) + dest = AGENTS_DEST / f"{config['name']}.json" + if dest.exists() and dest.read_text(encoding="utf-8") == rendered: + info(f"{dest} (up to date, skipped)") + else: + dest.write_text(rendered, encoding="utf-8") + info(f"{dest} (written)") + return dest + + +def _mcp_server_registered(config_path: Path) -> bool: + """Return True if the sdpm MCP server is present in the given config file.""" + try: + data = json.loads(config_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return False + return MCP_SERVER_NAME in data.get("mcpServers", {}) + + +def register_mcp_server(kiro_cli: str | None, agent: str | None) -> None: + """Register the sdpm MCP server via `kiro-cli mcp add` (idempotent).""" + target = f"agent '{agent}'" if agent else "global ~/.kiro/settings/mcp.json" + print(f"[3/3] Registering MCP server '{MCP_SERVER_NAME}' in {target}") + + config_path = AGENTS_DEST / f"{agent}.json" if agent else GLOBAL_MCP_JSON + if _mcp_server_registered(config_path): + info(f"'{MCP_SERVER_NAME}' already registered in {config_path} (skipped)") + return + + if kiro_cli is None: + warn( + "kiro-cli not found on PATH — skipped MCP registration. " + "Install Kiro CLI and re-run `make install-kiro` (files above were " + "still generated)." + ) + return + + args = ["run", "--directory", str(REPO_ROOT / "mcp-local"), "python", "server.py"] + cmd = [ + kiro_cli, + "mcp", + "add", + "--name", + MCP_SERVER_NAME, + "--command", + "uv", + "--args", + json.dumps(args), + "--timeout", + "120000", + ] + if agent: + cmd += ["--agent", agent] + else: + cmd += ["--scope", "global"] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + warn(f"`{' '.join(cmd)}` failed:\n{result.stderr.strip() or result.stdout.strip()}") + return + info(f"'{MCP_SERVER_NAME}' registered in {config_path}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument( + "--agent", + metavar="NAME", + help=( + "register the sdpm MCP server into ~/.kiro/agents/NAME.json instead " + "of the global ~/.kiro/settings/mcp.json" + ), + ) + opts = parser.parse_args() + + print(f"Installing sdpm for Kiro CLI (checkout: {REPO_ROOT})\n") + kiro_cli = shutil.which("kiro-cli") + if kiro_cli is None: + warn("kiro-cli not found on PATH — file generation will proceed anyway.") + + symlink_skills() + render_composer_agent() + register_mcp_server(kiro_cli, opts.agent) + + print( + "\nDone. Usage:\n" + " 1. Make sure `uv` and LibreOffice/poppler are installed (previews need them).\n" + " 2. Start a NEW session: kiro-cli chat\n" + ' 3. Ask for slides, e.g. "このURLをスライドにして https://..." — the sdpm-vibe\n' + " skill runs Phase 1 and dispatches sdpm-composer sub-agents for Phase 2.\n" + "\n" + "Updates: `git pull` in this checkout is enough (skills are symlinks; the\n" + "composer prompt is a file:// reference). Re-run `make install-kiro` only if\n" + "you move the checkout." + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/clients/kiro/sdpm-composer.json.tmpl b/clients/kiro/sdpm-composer.json.tmpl new file mode 100644 index 00000000..bf7a43d9 --- /dev/null +++ b/clients/kiro/sdpm-composer.json.tmpl @@ -0,0 +1,31 @@ +{ + "name": "sdpm-composer", + "description": "sdpm slide composer (dispatched by the sdpm-vibe skill). Composes assigned slides from approved specs via the sdpm MCP server. No user interaction.", + "prompt": "file://{{CHECKOUT}}/agents/sdpm-composer.md", + "mcpServers": { + "sdpm": { + "command": "uv", + "args": [ + "run", + "--directory", + "{{CHECKOUT}}/mcp-local", + "python", + "server.py" + ], + "timeout": 120000 + } + }, + "tools": [ + "read", + "glob", + "grep", + "@sdpm" + ], + "allowedTools": [ + "read", + "glob", + "grep", + "@sdpm" + ], + "useLegacyMcpJson": false +} diff --git a/skills/sdpm-vibe/SKILL.md b/skills/sdpm-vibe/SKILL.md index cebeaa89..79286aa6 100644 --- a/skills/sdpm-vibe/SKILL.md +++ b/skills/sdpm-vibe/SKILL.md @@ -16,7 +16,8 @@ server** (its tools are exposed as `mcp__plugin_sdpm_sdpm__*` when installed as hearing and without per-step approval. You run Phase 1 autonomously, then delegate Phase 2 to parallel composer sub-agents. -> Ported from the ACP `vibe-agent.md`. Only Claude Code reads this file. The shared workflows +> Ported from the ACP `vibe-agent.md`. Only SKILL.md-driven clients (Claude Code, Kiro CLI) +> read this file. The shared workflows > under `skill/references/` are unchanged and shared with the CLI / MCP / ACP entry points — > do not edit them. @@ -129,8 +130,10 @@ one message. After Step 5, **`specs/art-direction.html` and `deck.json` are FROZEN.** ### Step 6 — Compose (DELEGATE to parallel composer sub-agents) -Prerequisite: Steps 2–5 complete. Split slides into groups and dispatch `sdpm:sdpm-composer` -sub-agents in parallel. +Prerequisite: Steps 2–5 complete. Split slides into groups and dispatch composer +sub-agents in parallel. How you dispatch depends on your client (see **Dispatch** below): +- **Task tool available (Claude Code):** dispatch `sdpm:sdpm-composer` sub-agents. +- **subagent tool available (Kiro CLI):** dispatch stages with role `sdpm-composer`. **Group assignment (small groups, keep design-coupled slides together):** - **Step 1 — keep design-coupled slides in ONE group:** override-inherited slides (same slug @@ -142,8 +145,17 @@ sub-agents in parallel. - **Parallelism cap: up to 10 agents in parallel** (Claude Code handles 10 concurrent Tasks safely). If there are more than 10 groups, dispatch in successive waves of ≤10. -**Dispatch:** invoke one `sdpm:sdpm-composer` per group, **all in a single message** (up to 10) -so they run in parallel. Per-group prompt template: +**Dispatch — client with the Task tool (Claude Code):** invoke one `sdpm:sdpm-composer` per +group, **all in a single message** (up to 10) so they run in parallel. + +**Dispatch — client with the subagent tool (Kiro CLI):** invoke the `subagent` tool once with +one stage per group (no `depends_on`, so all stages start in parallel), each stage using +`role: "sdpm-composer"` and the same per-group prompt template below as its `prompt_template`. +If role `sdpm-composer` is **not** in the subagent tool's role list, STOP — do not improvise +with another role and do not retry: tell the user to run `make install-kiro` in the +repository checkout and restart the session (the role list is fixed at session start). + +Per-group prompt template (common to both clients): ``` deck_id=.