feat: 브리프 인터뷰 단계와 브라우저 도구를 넣고 온보딩 말투를 고친다
All checks were successful
ci / build (push) Successful in 25s
release / release (push) Successful in 46s

세 가지를 고친다. 전부 실제 사용에서 드러난 것이다.

1) 0단계에서 가정으로 채우던 것을 인터뷰로 바꾼다

   "꽃집 사이트 만들어보자"를 받고 업종 성격·목표 행동·톤·이름을 혼자 정했다.
   물어보니 넷 중 넷이 달랐다(일상 구독 → 하이엔드 스튜디오, 문의 하나 → 넷 다,
   톤 미정 → 에디토리얼, 이름 지어냄 → 목요일의 화원).
   그대로 갔으면 레퍼런스 세 개를 전부 틀린 방향에서 골랐다.

   SKILL.md 0단계에 질문 도구로 한 번에 묻는 절차를 넣었다. 무엇을 묻고
   무엇을 묻지 않는지, 답이 모순될 때 어떻게 정리하는지까지 적었다.

2) 레퍼런스가 막히면 브라우저를 띄운다

   Aesop(403) → Kinto(404) → Hasami(DNS) → MUJI(타임아웃)로 네 번 왕복하고
   톤 레퍼런스를 하나도 못 얻었다. 좋은 레퍼런스일수록 봇을 막는다.
   galleries.md 에 headed 브라우저로 직접 열어 스크린샷과 실측값을 받는
   방법을 넣고, 접근 실패 2회면 바로 전환하도록 규칙을 세웠다.

   Playwright 를 선택 의존성으로 잡았다(core/tools.ts).
   - 온보딩 마지막에 설치 여부를 묻는다. 건너뛰어도 스킬은 동작한다
   - `designpaca tools` 로 상태 확인, `--yes` 로 설치
   - 수백 MB 라 --yes 없이는 상태만 보여준다
   - MCP 서버는 설치만 하고 등록 명령은 안내만 한다(에이전트 설정을
     대신 건드리지 않는다)

3) 온보딩 말투

   "이제 브리프를 던져라", "설치해라", "건너뛴다" — 사용자를 향한 문구가
   명령조였다. 스킬 문서의 단정한 반말은 의도지만 CLI UI 는 다르다.
   전부 존댓말로 바꾸고, 각 단계가 왜 필요한지 설명을 붙였다.
   명령조가 다시 섞이지 않도록 검사하는 테스트를 넣었다(테스트 22 → 25개).
This commit is contained in:
Yun Chan 2026-08-21 03:05:06 +09:00
parent eb9f61e907
commit 4e1e5f0073
15 changed files with 486 additions and 39 deletions

View file

@ -18,7 +18,7 @@ export async function runDoctor(): Promise<number> {
if (pruned > 0) console.log(info(`사라진 설치 기록 ${pruned}건을 정리했다`));
if (manifest.installs.length === 0) {
console.log(warn("설치된 곳이 없다. `npx designpaca install` 로 설치해라."));
console.log(warn("아직 설치된 곳이 없습니다. `npx designpaca install` 로 설치하실 수 있습니다."));
return 0;
}
@ -40,7 +40,7 @@ export async function runDoctor(): Promise<number> {
if (rec.version !== version) problems++;
if (missing.length > 0) {
problems++;
console.log(bad(`파일 ${missing.length}개가 사라졌다 — \`designpaca update --force\` 로 복구해라`));
console.log(bad(`파일 ${missing.length}개가 보이지 않습니다 — \`designpaca update --force\` 로 복구하실 수 있습니다`));
for (const m of missing.slice(0, 5)) console.log(dim(` ${tildify(m.path)}`));
}
if (modified.length > 0) {

View file

@ -60,7 +60,7 @@ export function printOutcomes(outcomes: InstallOutcome[], dryRun = false): void
const verb = dryRun ? "쓸 파일" : "설치됨";
console.log(ok(`${o.target} — ${verb} ${o.written}개 ${dim(tildify(o.root))}`));
if (o.skipped.length > 0) {
console.log(warn(` 직접 수정한 파일이라 건드리지 않았다 (--force 로 덮어쓴다):`));
console.log(warn(` 직접 수정하신 파일이라 그대로 두었습니다 (--force 로 덮어쓸 수 있습니다):`));
for (const s of fold(o.skipped)) console.log(dim(` ${tildify(s)}`));
}
}
@ -70,7 +70,7 @@ export function printOutcomes(outcomes: InstallOutcome[], dryRun = false): void
export function printNextSteps(targets: TargetId[]): void {
console.log(heading("다음 단계"));
if (targets.includes("claude-code")) {
console.log(info(`Claude Code 를 새로 열고 ${dim("/designpaca")} 를 실행해라`));
console.log(info(`Claude Code 를 새로 연 뒤 ${dim("/designpaca")} 를 실행해 주세요`));
}
if (targets.includes("codex")) {
console.log(info(`Codex CLI 에서 designpaca 스킬이 자동으로 잡힌다`));

View file

@ -0,0 +1,67 @@
import { checkTools } from "@designpaca/core";
import { bad, dim, heading, info, ok, table, warn } from "../ui.ts";
/**
* 선택 도구의 상태를 보여주고, 없는 것을 설치한다.
*
* 온보딩에서 건너뛴 사람과, 나중에 필요해진 사람 모두를 위한 자리다.
* `--yes` 없이 비대화형으로 부르면 **상태만 보여주고 아무것도 설치하지 않는다** —
* 도구 설치는 수백 MB 를 받는 일이라 조용히 시작하면 안 된다.
*/
export async function runTools(opts: { install: boolean }): Promise<number> {
const status = await checkTools();
console.log(heading("브라우저 도구"));
console.log(
table(
status.map(({ tool, installed }) => [
tool.label,
installed ? "설치됨" : dim("설치되지 않음"),
]),
),
);
const missing = status.filter((s) => !s.installed);
if (missing.length === 0) {
console.log(ok("필요한 도구가 모두 준비돼 있습니다."));
return 0;
}
console.log();
for (const { tool } of missing) {
console.log(`${tool.label} ${dim(`· ${tool.size}`)}`);
console.log(` ${dim(tool.why)}`);
}
if (!opts.install) {
console.log();
console.log(info("설치하시려면 `designpaca tools --yes` 를 실행해 주세요."));
return 0;
}
console.log();
let failed = 0;
const followUps: string[] = [];
for (const { tool } of missing) {
process.stdout.write(`${tool.label} 설치 중… `);
try {
await tool.install();
console.log("완료");
if (tool.followUp) followUps.push(tool.followUp);
} catch (err) {
failed++;
console.log("실패");
const first = err instanceof Error ? (err.message.split("\n")[0] ?? err.message) : String(err);
console.log(` ${bad(first)}`);
}
}
if (followUps.length > 0) {
console.log();
console.log(warn("아래는 에이전트 설정을 바꾸는 것이라 직접 실행해 주세요."));
for (const f of followUps) console.log(` ${f}`);
}
return failed > 0 ? 1 : 0;
}

View file

@ -11,7 +11,7 @@ export async function runUninstall(opts: {
);
if (targets.length === 0) {
console.log(warn("제거할 설치 기록이 없다"));
console.log(warn("제거할 설치 기록이 없습니다"));
return 0;
}

View file

@ -12,7 +12,7 @@ export async function runUpdate(opts: { force?: boolean } = {}): Promise<number>
const manifest = await readManifest();
if (manifest.installs.length === 0) {
console.log(warn("설치 기록이 없다. `npx designpaca install` 을 먼저 실행해라."));
console.log(warn("설치 기록이 없습니다. `npx designpaca install` 을 먼저 실행해 주세요."));
return 1;
}
@ -20,7 +20,7 @@ export async function runUpdate(opts: { force?: boolean } = {}): Promise<number>
const stale = manifest.installs.filter((i) => i.version !== version);
if (stale.length === 0) {
console.log(ok("모든 설치가 이미 최신이다"));
console.log(ok("설치된 스킬이 모두 최신 버전입니다"));
// 버전이 같아도 파일이 사라졌거나 수정됐을 수 있다. 조용히 넘기면 사용자가 모른다.
let broken = 0;