designpaca/packages/cli/src/commands/install.ts
Yun Chan 8d02820fa5
Some checks are pending
ci / build (push) Waiting to run
feat(core): 어떤 하네스에서든 스킬을 발견하는 타깃 체계로 바꾼다
- ZCode(~/.zcode/skills) 타깃 추가
- 중립 경로 agents(~/.agents/skills · .agents/skills) 타깃 추가
- gemini-cli(.gemini/skills)·copilot(~/.copilot/skills · .github/skills) 타깃 추가
- Cursor·Windsurf 를 네이티브 스킬 경로로 전환 — 규칙 파일 12,000자 상한 때문에
  Windsurf 는 실제 스킬이 아예 설치되지 못했었다. 전역 설치도 가능해진다
- Antigravity 전역 경로를 공식 문서대로 ~/.gemini/antigravity-cli/skills 로 수정
2026-08-21 12:24:34 +09:00

115 lines
3.8 KiB
TypeScript

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<InstallOutcome[]> {
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")}`));
}