import { ADAPTERS, type Scope, type TargetId } from "@designpaca/core"; import { getVersion } from "./skill.ts"; import { checkForUpdate } from "./update-check.ts"; import { printNextSteps, printOutcomes, runInstall } from "./commands/install.ts"; import { runDoctor } from "./commands/doctor.ts"; import { runTools } from "./commands/tools.ts"; import { runUpdate } from "./commands/update.ts"; import { runUninstall } from "./commands/uninstall.ts"; import { runList } from "./commands/list.ts"; import { onboard } from "./tui/onboard.ts"; import { accent, bad, bold, dim, warn } from "./ui.ts"; const VALID_TARGETS = ADAPTERS.map((a) => a.id) as string[]; interface Args { command: string; targets: TargetId[]; scope: Scope; yes: boolean; force: boolean; dryRun: boolean; help: boolean; version: boolean; } function parse(argv: string[]): Args { const out: Args = { command: "", targets: [], scope: "user", yes: false, force: false, dryRun: false, help: false, version: false, }; for (let i = 0; i < argv.length; i++) { const a = argv[i] as string; if (a === "--help" || a === "-h") out.help = true; else if (a === "--version" || a === "-v") out.version = true; else if (a === "--yes" || a === "-y") out.yes = true; else if (a === "--force" || a === "-f") out.force = true; else if (a === "--dry-run") out.dryRun = true; else if (a === "--no-update-check") process.env["DESIGNPACA_NO_UPDATE_CHECK"] = "1"; else if (a === "--target" || a === "-t") { const v = argv[++i]; if (v) out.targets.push(...(v.split(",") as TargetId[])); } else if (a.startsWith("--target=")) { out.targets.push(...(a.slice(9).split(",") as TargetId[])); } else if (a === "--scope" || a === "-s") { const v = argv[++i]; if (v) out.scope = v as Scope; } else if (a.startsWith("--scope=")) { out.scope = a.slice(8) as Scope; } else if (!a.startsWith("-") && !out.command) { out.command = a; } } return out; } function usage(version: string): string { return ` ${accent("designpaca")} ${dim(`v${version}`)} — 웹 디자인 파이프라인 스킬 설치기 ${bold("사용법")} npx designpaca 대화형 온보딩 (권장) npx designpaca install 설치 npx designpaca update 최신 스킬로 갱신 npx designpaca uninstall 제거 npx designpaca doctor 설치 상태 진단 npx designpaca list 설치 가능 대상과 현재 설치 목록 npx designpaca tools 브라우저 도구 상태 확인 ${dim("(--yes 로 설치)")} ${bold("옵션")} -t, --target 설치 대상: ${VALID_TARGETS.join(", ")} -s, --scope <범위> user | project ${dim("(기본: user)")} -y, --yes 확인 없이 진행합니다 -f, --force 직접 수정한 파일도 덮어씁니다 ${dim("(.orig 로 백업)")} --dry-run 쓰지 않고 계획만 보여줍니다 --no-update-check 새 버전 확인을 건너뜁니다 -h, --help 이 도움말 -v, --version 버전 ${bold("예시")} ${dim("npx designpaca install -t claude-code,codex -s user -y")} ${dim("npx designpaca install -t cursor -s project")} ${dim("npx designpaca update --force")} ${dim("npx designpaca tools --yes")} `.trimStart(); } async function main(): Promise { const args = parse(process.argv.slice(2)); const version = await getVersion(); if (args.version) { console.log(version); return 0; } if (args.help) { console.log(usage(version)); return 0; } const bad_ = args.targets.filter((t) => !VALID_TARGETS.includes(t)); if (bad_.length > 0) { console.error(bad(`알 수 없는 대상: ${bad_.join(", ")}`)); console.error(dim(` 가능한 값: ${VALID_TARGETS.join(", ")}`)); return 2; } if (args.scope !== "user" && args.scope !== "project") { console.error(bad(`알 수 없는 범위: ${args.scope} (user | project)`)); return 2; } // 대화형으로 쓸 수 있는 환경이면 인자 없이 실행했을 때 온보딩으로 보낸다 const interactive = process.stdout.isTTY && !args.yes; if (!args.command || args.command === "onboard") { if (interactive) return onboard(); console.log(usage(version)); return 0; } let code = 0; switch (args.command) { case "install": { const targets = args.targets.length > 0 ? args.targets : (["claude-code"] as TargetId[]); if (interactive && args.targets.length === 0) return onboard(); const outcomes = await runInstall({ targets, scope: args.scope, force: args.force, dryRun: args.dryRun, }); printOutcomes(outcomes, args.dryRun); if (!args.dryRun) printNextSteps(targets); code = outcomes.some((o) => o.blocked) ? 1 : 0; break; } case "update": code = await runUpdate({ force: args.force }); break; case "uninstall": case "remove": code = await runUninstall({ targets: args.targets, force: args.force }); break; case "doctor": code = await runDoctor(); break; case "tools": // 수백 MB 를 받는 일이라 --yes 없이는 상태만 보여준다 code = await runTools({ install: args.yes }); break; case "list": case "ls": code = await runList(); break; default: console.error(bad(`알 수 없는 명령입니다: ${args.command}`)); console.log(usage(version)); return 2; } // 명령을 마친 뒤에만 알린다 — 시작을 지연시키지 않는다 const latest = await checkForUpdate(version); if (latest) { console.log( "\n" + warn(`새 버전 v${latest} 이 나왔습니다 — ${dim("npx designpaca@latest update")}`), ); } return code; } main() .then((code) => { process.exitCode = code; }) .catch((err: unknown) => { console.error(bad(err instanceof Error ? err.message : String(err))); if (process.env["DESIGNPACA_DEBUG"] === "1" && err instanceof Error) console.error(err.stack); process.exitCode = 1; });