import { applyPlan, planInstall, tildify, type Scope, type TargetId, } from "@designpaca/core"; import { getSkill } from "../skill.ts"; import { bad, dim, fold, heading, info, ok, warn } from "../ui.ts"; export interface InstallOptions { targets: TargetId[]; scope: Scope; force?: boolean; /** 실제로 쓰지 않고 계획만 출력 */ dryRun?: boolean; } export interface InstallOutcome { target: TargetId; root: string; written: number; skipped: string[]; blocked?: string; } /** install/update/TUI 가 공유하는 실제 설치 실행부 */ export async function runInstall(opts: InstallOptions): Promise { const skill = await getSkill(); const outcomes: InstallOutcome[] = []; for (const target of opts.targets) { const plan = await planInstall(target, opts.scope, skill); if (plan.blocked) { outcomes.push({ target, root: "", written: 0, skipped: [], blocked: plan.blocked }); continue; } if (opts.dryRun) { outcomes.push({ target, root: plan.root, written: plan.actions.length, skipped: [] }); continue; } const res = await applyPlan(plan, skill.version, { force: opts.force }); outcomes.push({ target, root: plan.root, written: res.written.length, skipped: res.skipped, }); } return outcomes; } export function printOutcomes(outcomes: InstallOutcome[], dryRun = false): void { console.log(heading(dryRun ? "설치 계획" : "설치 결과")); for (const o of outcomes) { if (o.blocked) { console.log(bad(`${o.target}: ${o.blocked}`)); continue; } const verb = dryRun ? "쓸 파일" : "설치됨"; console.log(ok(`${o.target} — ${verb} ${o.written}개 ${dim(tildify(o.root))}`)); if (o.skipped.length > 0) { console.log(warn(` 직접 수정하신 파일이라 그대로 두었습니다 (--force 로 덮어쓸 수 있습니다):`)); for (const s of fold(o.skipped)) console.log(dim(` ${tildify(s)}`)); } } } /** 설치 후 안내 — 여기서 끝내지 말고 다음 행동을 알려준다 */ export function printNextSteps(targets: TargetId[]): void { console.log(heading("다음 단계")); if (targets.includes("claude-code")) { console.log(info(`Claude Code 를 새로 연 뒤 ${dim("/designpaca")} 를 실행해 주세요`)); } if (targets.includes("codex")) { console.log(info(`Codex CLI 에서 designpaca 스킬이 자동으로 잡힌다`)); } if (targets.includes("zcode")) { console.log(info(`ZCode 는 새 세션부터 designpaca 스킬이 자동으로 잡힌다`)); } if (targets.includes("cursor")) { console.log(info(`Cursor 는 새 창을 열 때 ${dim(".cursor/skills/designpaca")} 스킬을 읽는다`)); } if (targets.includes("agents")) { console.log( info( `${dim(".agents/skills")} 는 중립 경로다 — Cursor · Antigravity · ZCode 등 규약을 읽는 하네스가 함께 잡는다`, ), ); } if (targets.includes("antigravity")) { console.log( info(`Antigravity 는 전역 ${dim("~/.gemini/antigravity-cli/skills")} 를 읽는다 (워크스페이스는 .agents/skills)`), ); } if (targets.includes("gemini-cli")) { console.log(info(`Gemini CLI 는 ${dim("~/.gemini/skills")} 에서 스킬을 읽는다`)); } if (targets.includes("windsurf")) { console.log( info(`Windsurf(Cascade) 는 ${dim(".windsurf/skills/designpaca")} 스킬을 읽는다`), ); } if (targets.includes("copilot")) { console.log( info(`GitHub Copilot 은 ${dim(".github/skills")} (~/.copilot/skills) 의 SKILL.md 를 읽는다`), ); } if (targets.includes("agents-md")) { console.log( info(`AGENTS.md 에는 포인터만 넣었다 — 본문은 ${dim(".designpaca/SKILL.md")} 에 있다`), ); } console.log(info(`문서: ${dim("https://designpaca.chanpaca.net")}`)); }