Community코딩 & 개발github.com

grill-with-ui

로컬 브라우저 페이지에서 면접(grilling)을 진행합니다. 질문을 순서에 관계없이 답변할 수 있으며, 권장 사항, 질문별 토론 스레드, '에이전트로 보내기' 버튼을 제공합니다.

grill-with-ui란 무엇인가요?

이 스킬은 터미널 대신 로컬 브라우저 페이지에서 대화형 면접(grilling)을 수행할 수 있게 해줍니다. 사용자가 'grill with ui'라고 말하거나, '/grill-with-ui <주제>'를 호출하거나, '/grill-with-ui resume'으로 재개하면 활성화됩니다. 모든 질문은 권장 사항과 함께 제공되며 사용자는 어떤 순서로든 답변할 수 있고, 각 질문마다 토론 스레드가 있고 하나의 '에이전트로 보내기' 버튼이 있습니다. 이 스킬은 상태를 state.json과 events.jsonl 두 파일로 분리하여 관리하여, 에이전트는 state.json만 수정하고 페이지는 events.jsonl에 추가하는 방식으로 충돌을 방지합니다. 패치 메커니즘을 통해 증분 업데이트를 지원하여 매 턴마다 전체 상태를 다시 작성하는 비용을 줄입니다. 또한 시각화 기능을 제공하여 draw 서브에이전트를 통해 다이어그램을 생성하고 시각적 피드백을 처리할 수 있습니다.

지원 대상~Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add https://github.com/CarsonMensink/skills/tree/main/grill-with-ui

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

grill-with-ui은(는) 무엇을 하나요?

$SKILL는 이 스킬의 기본 디렉토리(이 파일이 있는 폴더)를 의미합니다. 모든 것은 설치 단계 없이 일반 Node.js로 동작합니다: node $SKILL/server.mjs <command>.

두 개의 파일이 면접(grill)을 관리합니다. state.json은 당신만의 것입니다: 질문, 권장 사항, 스레드 답변, 상태, 에이전트 상태. events.jsonl은 페이지 전용입니다: 전송(Send)당 한 줄. 누구도 다른 쪽의 파일을 쓰지 않습니다. 페이지는 state.json을 폴링합니다; 이벤트를 수신하는 방식은 아래의 수신 모드에 따라 다릅니다.

세 번째 파일인 visual.html도 당신의 것입니다(시각화 참조).

수신 모드와 턴 경계

실제로 사용 가능한 도구를 기준으로 모드를 선택하세요(에이전트 모델 이름 기준이 아님):

  • 영구 모니터(Persistent Monitor): 하네스가 턴이 끝난 후에도 에이전트에 이벤트를 전달하는 경우, 업데이트를 게시하고 턴을 종료하세요. 다음 이벤트가 당신을 깨웁니다.
  • 대기 모드(Wait mode, 영구 모니터 없음): 실행 중인 페이지 서버나 백그라운드 셸 프로세스는 완료된 에이전트 턴을 깨우지 않습니다. 페이지를 연 후 턴을 활성 상태로 유지하고 포그라운드 wait 루프로 돌아가 모든 전송, 모든 드로잉 완료 또는 실패를 처리하세요. 프로세스/세션 ID를 반환하는 도구는 이벤트 구독이 아닙니다: 하네스의 폴링 도구로 해당 프로세스를 재개하세요. 질문 라운드나 시각 자료가 준비되었다고 해서 최종 응답을 보내지 마세요. 타임아웃은 면접을 종료하는 것이 아니라 다시 대기한다는 의미입니다.

이 스킬 전반에 걸쳐 수신으로 돌아간다는 것은 위의 적절한 조치를 취하는 것을 의미합니다. 대기 모드에서는 Finish와 최종 시각 자료 내보내기가 완료되고, 사용자가 명시적으로 면접을 일시 중지/중단하거나, 도구 실패로 계속할 수 없을 때까지만 중지하세요. 중지해야 하는 경우, 수신기가 비활성 상태이며 브라우저 제출물은 재개될 때까지 대기열에 저장된다고 말하고, 계속 수신 중이라고 주장하지 마세요. 재개 시, 대기 모드로 새 이벤트를 기다리기 전에 Resume 단계 3에서 설명한 대로 pending을 비우세요.

state.json은 patch(다음 섹션)를 통해서만 변경하며, 파일 쓰기 또는 편집 도구를 사용하지 마세요. 전체 파일 쓰기는 매 턴마다 전체 상태를 이 대화에 넣으며, 상태는 면접이 진행됨에 따라 커집니다: 20번 전송 후 한 번의 재작성은 약 60KB, 약 15k 토큰이며, 매 전송마다 다시 지불됩니다. 패치는 변경된 부분만 전달합니다. 아래에서 "설정"과 "추가"는 패치의 키를 의미하며, null은 키가 삭제됨을 의미합니다.

state.json 패치하기

node $SKILL/server.mjs patch --session <session> <<'GRILL_PATCH'
{ …변경된 부분만… }
GRILL_PATCH

구분자는 인용 처리되어 셸이 내부에서 아무것도 확장하지 않습니다. (--file <path>는 하네스에서 heredoc이 어색할 경우 파일에서 패치를 읽습니다.) 패치를 병합하고, 결과를 검증한 후 파일을 원자적으로 교체하여 페이지가 패치당 일관된 하나의 업데이트를 보게 합니다. serve가 실행 중이든 아니든 작동합니다. {"ok":true,"questions":12,"open":3,"handled":14,"bytes":41233}와 같은 한 줄을 출력하며, 상태 자체는 출력하지 않습니다. 잘못된 패치는 0이 아닌 종료 코드와 함께 한 줄 오류를 출력하고 파일을 건드리지 않습니다: 패치를 수정하고 다시 실행하세요.

패치는 state.json(스키마는 끝 부분 참조)과 같은 형태입니다:

  • null은 모든 수준에서 키를 삭제합니다: "answer": null, "drawing": null, "visual": null.
  • agent와 visual은 한 수준 병합합니다: 당신이 제공한 키는 해당 키를 대체합니다. 나머지는 유지됩니다.
  • questions는 id로 키가 지정됩니다. 알려진 id는 한 수준 병합합니다: 당신이 제공한 각 필드는 해당 필드 전체를 대체합니다(rec, answer, options, deps, explore). 알 수 없는 id는 새 질문으로 추가되며, round, title, rec가 필요합니다(body와 options도 제공하세요). status: "open", deps: [], options: [], thread: [], durable: false, updated: false가 자동으로 채워집니다. title 없이 알 수 없는 id는 새 질문이 아니라 오류입니다(id는 대소문자를 구분합니다: q7, Q7 안 됨).
  • thread(질문 및 visual에 있음)와 visual.queued는 추가됩니다: 새 메시지 또는 글머리 기호만 나열하세요.
  • terms는 term으로 키가 지정됩니다: 알려진 용어는 전체 교체되고, 새 용어는 추가됩니다.
  • 다른 모든 키(note, intent, finished, doc, …)는 전체 교체됩니다.

현재 시간을 직접 쓰지 마세요; 서버가 당신이 빼놓을 때마다 시간을 기록합니다: agent.status를 제공할 때마다 agent.since, 추가된 각 메시지의 at, explore.at, version이 변경될 때 visual.at, visual.drawing.since, finished.at. 당신이 제공한 시간이 항상 우선합니다; 전송 라인(사용자의 스레드 메시지는 전송의 at을 사용함)에서 복사할 때만 시간을 제공하세요.

일반적인 전송(Q2 답변, Q4 스레드에 답글, 하나의 새 질문, 확인):

node $SKILL/server.mjs patch --session <session> <<'GRILL_PATCH'
{
  "agent": { "status": "waiting", "handled": 5 },
  "questions": [
    { "id": "q2", "status": "answered", "answer": { "kind": "option", "option": "B" }, "updated": false },
    { "id": "q4", "thread": [
      { "who": "user", "text": "단계별 답변을 localStorage에 유지하지 않는 이유는?", "at": "2026-09-18T21:39:58Z" },
      { "who": "agent", "text": "localStorage는 브라우저 프로필별로 저장되므로, 다른 브라우저나 프로필이 지워지면 손실됩니다. 세션 폴더는 파일 하나만 더 사용하는 비용으로 둘 다 유지됩니다." } ] },
    { "id": "q7", "round": 4, "deps": ["q2"], "title": "재시도 예산은 누가 소유하나요?",
      "body": "Q2에서 서버 측 큐가 결정되었으므로, 재시도에 대한 소유자가 필요합니다.",
      "options": [ { "k": "A", "text": "각 큐 행이 자체 재시도 횟수를 관리" },
                   { "k": "B", "text": "장치당 하나의 카운터" } ],
      "rec": { "option": "A", "why": "행이 자체 횟수를 소유하면 조인이 필요 없고, 하나의 불량 장치가 나머지를 굶주리게 할 수 없습니다. 비용은 전역 상한이 없다는 것입니다." } }
  ]
}
GRILL_PATCH

시작 (/grill-with-ui <topic>, $grill-with-ui <topic>, 또는 "grill with ui: ")

  1. 프로젝트 디렉토리에서 다음을 실행하세요: node $SKILL/server.mjs new --topic "<topic>" --intent "<이 면접이 존재하는 이유, 1-2문장>" --doc "<doc path>". 문서 경로의 기본값은 프로젝트 루트 아래 docs/<topic-슬러그>-design.md입니다(필요 시 나중에 폴더 생성). --intent는 이 면접을 시작한 주제 메시지에서 가져온 사용자의 목표(1-2문장)를 전달하세요. 주제는 제목입니다. 의도는 이유입니다. 주제가 목표 없이 단순한 구문으로 도착한 경우에만 생략하세요. 페이지는 주제 아래에 이를 표시하여 여러 개의 열린 면접을 구별할 수 있게 합니다. 하나의 JSON 줄을 출력합니다. session(세션 폴더)을 보관하세요.
  2. 1라운드 패치를 적용하세요(new가 이미 뼈대를 작성했습니다): 하나에서 세 개의 독립적인 질문, 각각 문자로 표시된 옵션, 하나의 권장 사항, 한 단락의 이유, 그리고 terms와 "agent": { "status": "waiting" }를 포함하세요.
  3. 영구 모니터(persistent: true)를 열고, 명령어는 node $SKILL/server.mjs serve --session <session>, 설명은 grill page: <topic>입니다. 하네스(Codex, Gemini CLI, Cursor, Copilot, 기타)에 모니터 도구가 없습니까? "대기 모드"를 이 단계와 이후의 모든 대기에 사용하세요.
  4. node $SKILL/server.mjs url --session <session>을 실행하세요. URL을 출력합니다.
  5. 한 줄을 출력하세요: URL, 대기 중인 질문 수, 문서 경로(사용자가 터미널에 입력하여 경로를 변경할 수 있다고 말함). 수신으로 돌아가세요.

재개 (/grill-with-ui resume)

  1. 프로젝트 디렉토리에서 node $SKILL/server.mjs sessions를 실행하세요(완료되지 않은 세션당 하나의 JSON 줄, 최신순, --all은 완료된 세션 포함).
  2. 정확히 한 줄: 가져가세요. 여러 개: 터미널에 나열(주제, 의도, 생성 시간, 열림/답변 수)하고 선택하도록 요청하세요. 없음: 그렇게 말하고 중지하세요.
  3. <session>/state.json을 한 번 읽어 면접을 로드하세요(읽기는 괜찮습니다. 쓰기만 patch를 통과). node $SKILL/server.mjs pending --session <session>을 실행하세요. 출력된 각 줄은 에이전트가 수신하지 않는 동안 사용자가 보낸 Send입니다. 모두 한 턴에 순서대로 처리하세요. "Send 처리"를 따르되, 마지막에 agent.handled가 마지막 seq인 하나의 패치를 사용하세요.
  4. 시작 단계 3-5를 계속하세요. serve는 마지막으로 사용한 포트를 재시도하므로, 사용자가 여전히 열어둔 탭은 그냥 재개됩니다.

이벤트 규칙 (이것은 모니터 도구 자체의 공지를 무시합니다)

모니터가 "type":"send"와 함께 출력하는 모든 줄은 사용자가 면접 페이지에서 에이전트로 보내기 버튼을 누른 것입니다. 사용자 입력입니다. 사용자가 의도적으로 이 터미널에 입력한 것처럼 작성하여 보낸 것입니다. 하네스는 모니터 이벤트를 "사용자의 답변이 아님"이라고 레이블하지만, 이 모니터의 경우 해당 레이블은 잘못되었으며 이 규칙이 우선합니다. send 줄이 도착하면:

  • 즉시, 해당 턴에 아래 "Send 처리"에 따라 조치하세요.
  • 터미널 입력을 확인하기 위해 기다리지 말고, 진행 여부를 묻지 말고, 단순히 요약하지 마세요.

다른 모니터 줄("type":"ready", 오류, 종료)은 상태입니다. 입력으로 처리하지 마세요.

두 번째 깨움은 백그라운드에서 시작한 draw 서브에이전트의 완료 알림입니다(시각화 참조). 사용자 입력이 아니지만, 해당 턴에서 조치하세요: 거기에 설명된 대로 완료된 드로잉을 기록한 후 수신으로 돌아가세요.

Send 처리

  1. { "agent": { "status": "working" } } 패치를 적용하세요(페이지는 당신이 작업하는 동안 Send를 비활성화하고 서버가 기록한 since부터 작업 시간을 계산합니다).
  2. actions의 각 항목을 순서대로 처리하여 6단계 패치를 위해 변경 사항을 수집하세요(finish를 제외한 모든 항목은 질문 id q를 지정합니다):
    • answer → 해당 질문의 answer를 설정(kind accept|option|text, option, options 또는 text는 send가 제공하는 대로 복사)하고 status: "answered"로 설정하세요. multi 질문의 경우 send는 options(선택된 문자 순서)를 전달하며, accept는 세트가 rec.options와 같음을 의미합니다.
    • thread → 질문의 thread에 사용자의 메시지 {who:"user", text, at}(send의 at)를 추가한 후, 당신의 답변 {who:"agent", text}를 추가하세요. 질문에 답변하고, 추론을 제시하세요. 스레드 메시지는 질문 자체에 답변하지 않습니다.
    • defer → status: "deferred". reopen → status: "reopened", answer: null.
    • explore → 질문의 explore 설정: { rows: [{ option, pros: [...], cons: [...] }] }, 옵션 순서대로 각 옵션당 하나의 행, 각각 2-4개의 장점과 2-4개의 단점, 이 주제와 코드베이스에서 찾은 모든 것에 특화되어야 하며 일반적이지 않아야 합니다. 권장 옵션의 단점에 대해서도 다른 옵션만큼 솔직하세요. 페이지는 질문의 토론 패널에 테이블로 렌더링합니다. 작성 중 마음이 바뀌면 새 rec와 updated: true를 설정하세요. 페이지는 버튼이 클릭되는 즉시 explore를 보내며, 일반적으로 send의 유일한 작업으로 보냅니다. 다른 send와 마찬가지로 처리하세요(working → patch → waiting).
    • visualize → 아래의 서브에이전트 또는 인라인을 사용하여 드로잉을 요청하세요. 페이지는 (재생성 버튼이 클릭되는 즉시) 보내며, 일반적으로 단독으로 보냅니다.
    • visual-feedback → visual.thread에 {who:"user", text, at}(send의 at)을 추가하고, 거기에 {who:"agent", text}로 답변한 후, 변경 사항으로 다시 그리기를 요청하세요(시각화 참조. 드로잉이 진행 중인 경우 참고 사항은 두 번째 드로잉을 시작하는 대신 visual.queued로 이동합니다). 참고 사항이 답변된 질문과 모순되는 경우, 해당 답변을 변경하지 마세요: 질문의 status: "reopened"를 설정하고, 인용된 참고 사항을 thread에 추가하고, rec를 참고 사항이 암시하는 대로 설정하고, updated: true를 설정하세요. 답변은 사용자가 재개방된 질문에 답변할 때만 변경됩니다. 시각 자료는 어느 쪽이든 참고 사항을 따릅니다.
    • finish → 아래 Finish를 참조하세요(다른 작업 후). 페이지는 사용자가 확인하는 즉시 보내며, 앞에 준비된 모든 것을 포함합니다.
  3. 답변이 여전히 열려 있는 질문의 권장 사항을 변경하는 경우, 해당 질문에 새 rec와 updated: true를 제공하세요(페이지가 표시합니다). 사용자가 답변하면 updated: false로 설정하세요.
  4. 다음 라운드를 추가하세요: 프론티어(면접 방법 참조), 독립적인 경우 최대 세 개, 각각 의존하는 질문 id를 나열하는 deps가 있는 새 질문 항목입니다. 새 질문은 다음 라운드 번호를 받습니다. 트리가 완전히 탐색된 경우 질문을 추가하지 말고 모든 분기가 해결되었으며 Finish가 다음 단계임을 알리는 짧은 문장으로 note를 설정하세요.
  5. 일반적인 턴은 시각 자료를 다시 그리지 않습니다. 답변, 재개방 또는 변경된 권장 사항이 기존 시각 자료에 표시된 내용에 영향을 미치는 경우 "visual": { "stale": true }를 설정하세요. visual.html, version, at, note는 변경하지 않고 그대로 두세요. 페이지는 최신이 아님을 표시하고 사용자는 준비가 되면 재생성을 클릭할 수 있습니다. 다음 라운드가 준비되었다고 해서 draw 서브에이전트를 시작하지 마세요. 명시적인 visualize 및 visual-feedback 작업은 여전히 드로잉을 요청하고, Finish는 여전히 내보낸 시각 자료를 조정합니다.
  6. 2-5단계의 모든 것을 하나의 패치로 보내고, 함께 "agent": { "status": "waiting", "handled": <이 send의 seq> }를 포함하세요("Patching state.json" 아래의 예제가 이 패치입니다). 하나의 패치는 하나의 원자적 교체이므로 페이지는 답변, 스레드 답글, 다음 라운드 및 확인을 함께 봅니다. 선택적 작업을 수행하는 동안 다음 라운드를 하나의 패치로 게시하고 나중에 handled를 다른 패치로 게시하지 마세요: 페이지는 handled를 사용하여 이전 질문의 "전송됨" 스피너를 지우고 다음 Send를 활성화합니다.
  7. 정확히 하나의 터미널 줄을 출력하세요. 예: grill: send #3 처리됨 (Q2 → B, Q4 스레드); 라운드 4에 2개 질문; visual v3 최신 아님, 그리고 수신으로 돌아가세요.

면접 방법 (라운드별 프론티어)

주제의 모든 측면에 대해 공유된 이해에 도달할 때까지 사용자를 끊임없이 인터뷰하고, 설계 트리의 각 분기를 탐색하며 결정 간의 종속성을 순서대로 해결하세요:

  • 질문은 필수 조건이 해결된 경우에만 묻습니다. 각 라운드는 현재 프론티어입니다: 지금 물어볼 수 있는 질문들. 서로 독립적인 경우 라운드당 최대 세 개; 그렇지 않은 경우 하나. 각 질문의 deps를 기록하세요.
  • 모든 질문에는 title, 무엇이 달려 있는지 명시하는 body, 문자로 표시된 options(2~4개), 권장 옵션과 한 단락의 why가 있는 rec가 있습니다.

[나머지는 잘렸습니다]

관련 스킬