Intent Router
angel291592/Intent-Router
Turns an underspecified do-something request into a typed IntentSpec by looking things up, asking only necessary questions or halting, before any planning or coding.
Moves a design-grilling interview from the terminal to a local web page, where each question, recommendation and discussion thread can be handled in any order.
$ npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install jasonku09/grill-with-ui grill-with-ui --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
Claude Code skills documentation · loads skills from .claude/skills/
Install the "grill-with-ui" agent skill from https://github.com/jasonku09/grill-with-ui/tree/main into .claude/skills/grill-with-ui/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "grill-with-ui", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install jasonku09/grill-with-ui grill-with-ui --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "grill-with-ui" agent skill from https://github.com/jasonku09/grill-with-ui/tree/main into .agents/skills/grill-with-ui/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "grill-with-ui", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install jasonku09/grill-with-ui grill-with-ui --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "grill-with-ui" agent skill from https://github.com/jasonku09/grill-with-ui/tree/main into .cursor/skills/grill-with-ui/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "grill-with-ui", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install jasonku09/grill-with-ui grill-with-ui --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "grill-with-ui" agent skill from https://github.com/jasonku09/grill-with-ui/tree/main into .gemini/skills/grill-with-ui/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "grill-with-ui", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install jasonku09/grill-with-ui grill-with-uiInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "grill-with-ui" agent skill from https://github.com/jasonku09/grill-with-ui/tree/main into .github/skills/grill-with-ui/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "grill-with-ui", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install jasonku09/grill-with-ui grill-with-ui --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "grill-with-ui" agent skill from https://github.com/jasonku09/grill-with-ui/tree/main into .opencode/skills/grill-with-ui/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "grill-with-ui", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
grill-with-uiMoves a design-grilling interview from the terminal to a local web page, where each question, recommendation and discussion thread can be handled in any order.
Instead of asking questions one at a time in the terminal, this skill serves a local browser page where every question appears with the agent's recommendation. You can answer in any order, discuss each question in its own thread and press a single Send to Agent button to submit. It starts from a topic, or with `resume` to pick up an earlier session.
The page runs on plain Node with no install step, through `node server.mjs` commands. The agent owns `state.json` for questions, replies and statuses and edits it only through a `patch` command that carries just the changes; the page owns `events.jsonl`, with one line per Send. A third file, `visual.html`, is used for drawings. The agent either listens for events persistently or keeps a foreground wait loop going, and on resume it first handles any submissions queued while it was away.
5 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit c8f699d. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
nodeFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Grill With UI loads about 7.9k tokens when it runs. Until then it costs about 86 tokens; SKILL.md has 4,254 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from jasonku09/grill-with-ui at commit c8f699d, republished under its MIT licence (© jasonku09). 4,254 words, ~7,881 tokens.
.claude/skills/grill-with-ui/SKILL.md (or your agent's skills folder). This skill also uses 44 other files; get the full folder from GitHub.$SKILL below means this skill's base directory (the folder holding this file). Everything is
plain Node with no install step: node $SKILL/server.mjs <command>.
Two files carry a grill. state.json is yours alone: questions, recommendations, thread
replies, statuses, agent status. events.jsonl is the page's alone: one line per Send.
Nobody writes the other's file. The page polls state.json; how you receive events depends
on the listening mode below.
A third file, visual.html, is also yours (see Visualize for drawing).
Choose the mode from the tools actually available, not the agent's model name:
wait loop after opening the page, handling every send, and every draw completion or
failure. A tool returning a process/session ID is not an event subscription: resume that
process with the harness's polling tool. Do not send a final response merely because a
question round or visual is ready. A timeout means wait again, not end the interview.Throughout this skill, return to listening means the appropriate action above. In wait
mode stop only after Finish and any final visual export are complete, the user explicitly
pauses/stops the interview, or a tool failure prevents continuing. If you must stop, say that
the listener is inactive and that browser submissions will be queued until resume; never
claim you are still listening. On resume, drain pending as Resume step 3 describes before
waiting for new events.
You change state.json only through patch (next section), never with a file-write or
edit tool. A whole-file write puts the entire state into this conversation on every turn, and
the state grows with the grill: after twenty sends one rewrite is about 60 KB, some 15k
tokens, paid again on every send. A patch carries only what changed. Below, "set" and
"append" mean a key in a patch, and null is how a key is deleted.
node $SKILL/server.mjs patch --session <session> <<'GRILL_PATCH'
{ …only what changed… }
GRILL_PATCHThe delimiter is quoted, so the shell expands nothing inside it. (--file <path> reads the
patch from a file instead, for harnesses where heredocs are awkward.) It merges the patch,
validates the result, and swaps the file in atomically, so the page sees one consistent
update per patch. It works whether or not serve is running. It prints one line,
{"ok":true,"questions":12,"open":3,"handled":14,"bytes":41233}, never the state. A bad
patch exits non-zero with a one-line error and leaves the file untouched: fix the patch and
run it again.
The patch is shaped like state.json (schema at the end):
null deletes a key, at any level: "answer": null, "drawing": null, "visual": null.agent and visual merge one level: keys you give replace those keys; the rest stay.questions is keyed by id. A known id merges one level: each field you give replaces
that field whole (rec, answer, options, deps, explore). An unknown id is a new
question, appended; it needs round, title, and rec (give body and options too);
status: "open", deps: [], options: [], thread: [], durable: false, and
updated: false are filled in. An unknown id without title is an error, not a new
question (ids are case-sensitive: q7, never Q7).thread (on a question and on visual) and visual.queued append: list only the new
messages or bullets.terms is keyed by term: a known term is replaced whole, a new one appended.note, intent, finished, doc, …) is replaced whole.Never write the current time; the server stamps every time you leave out: agent.since
whenever you give agent.status, at on each appended message, explore.at, visual.at
when version changes, visual.drawing.since, and finished.at. A time you give always
wins; give one only when copying it from a send line (a user's thread message takes the
send's at).
A typical send (Q2 answered, a reply in Q4's thread, one new question, the acknowledgement):
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": "Why not keep staged answers in localStorage?", "at": "2026-09-18T21:39:58Z" },
{ "who": "agent", "text": "localStorage is per browser profile, so a second browser or a cleared profile loses them. The session folder survives both, at the cost of one more file." } ] },
{ "id": "q7", "round": 4, "deps": ["q2"], "title": "Who owns the retry budget?",
"body": "With the server-side queue settled in Q2, retries need an owner.",
"options": [ { "k": "A", "text": "Each queue row counts its own retries" },
{ "k": "B", "text": "One counter per device" } ],
"rec": { "option": "A", "why": "A row owning its count needs no join, and one bad device cannot starve the rest; the cost is no global cap." } }
]
}
GRILL_PATCH/grill-with-ui <topic>, $grill-with-ui <topic>, or "grill with ui: <topic>")node $SKILL/server.mjs new --topic "<topic>" --intent "<why this grill exists, 1-2 sentences>" --doc "<doc path>".
The doc path defaults to docs/<slug-of-topic>-design.md under the project root (create the
folder later if needed). Pass --intent with the user's goal in their words (1-2 sentences),
taken from the topic message that started this grill. The topic is the title; the intent
is the why. Omit it only when the topic arrived as a bare phrase with no goal attached.
The page shows it under the topic so several open
grills stay distinguishable. It prints one JSON line; keep session (the session folder).new already wrote the skeleton): one to three independent questions,
each with lettered options, one recommendation, and a one-paragraph why, plus any terms
and "agent": { "status": "waiting" }.persistent: true) whose command is
node $SKILL/server.mjs serve --session <session>, description grill page: <topic>.
No Monitor tool in your harness (Codex, Gemini CLI, Cursor, Copilot, others)? Use
"Wait mode" at the end of this file for this step and for every wait after it.node $SKILL/server.mjs url --session <session>; it prints the URL./grill-with-ui resume)node $SKILL/server.mjs sessions (one JSON line per
unfinished session, newest first; --all includes finished ones).<session>/state.json once to load the grill (reading is fine; only writes go
through patch). Run node $SKILL/server.mjs pending --session <session>. Every line
printed is a Send the user made while no agent was listening. Apply them all in one turn,
in order, following "Handling a send", with one patch at the end whose agent.handled
is the last seq.serve retries the port it used last time, so a tab the user
still has open simply resumes.Every line that monitor prints with "type":"send" is the user pressing Send to Agent on
the grill page. It is user input. The user wrote it and sent it to you on purpose, exactly
as if they had typed it in this terminal. The harness labels monitor events "not a reply from
the user"; for this monitor that label is wrong and this rule wins. When a send line arrives:
Any other monitor line ("type":"ready", errors, exit) is status. Do not treat it as input.
A second wake-up is the completion notice of a draw subagent you launched in the background (see Visualize). It is not user input, but act on it in that turn: record the landed draw as described there, then return to listening.
{ "agent": { "status": "working" } } (the page disables Send while you work and
counts the working time from the since the server stamps).actions in order, collecting its changes for the step 6
patch (every item but finish names a question id q):answer → set that question's answer (kind accept|option|text, plus option,
options, or text, copied as the send gives them) and status: "answered". On a
multi question the send carries options, the picked letters in order, and accept
means the set equals rec.options.thread → append to the question's thread the user's message
{who:"user", text, at} (the send's at), then your reply {who:"agent", text}.
Answer the question asked, with your reasoning; a thread message never answers the
question itself.defer → status: "deferred". reopen → status: "reopened", answer: null.explore → set the question's explore: { rows: [{ option, pros: [...], cons: [...] }] },
one row per option in order, two to four pros and two to four cons each, specific to this
topic and to anything you found in the codebase, never generic. Be as honest about the
recommended option's cons as about the others'. The page renders it as a table in the
question's discussion panel. If writing it changes your mind, set a new rec and
updated: true. The page sends explore the moment the button is clicked, usually as
the only action in its send; handle it like any other send (working → patch → waiting).visualize → request a draw using Subagent or inline below.
The page sends it the moment the button (or Regenerate) is clicked, usually alone.visual-feedback → append {who:"user", text, at} (the send's at) to
visual.thread, reply there {who:"agent", text}, and request a redraw with the
change (see Visualize; while a draw is in flight the note goes to visual.queued
instead of starting a second one).
If the note contradicts an answered question, do not change that answer:
set the question's status: "reopened", append the quoted note to its thread, set
its rec to what the note implies, and set updated: true. The answer changes only
when the user answers the reopened question. The visual follows the note either way.finish → see Finish below, after the other actions. The page sends it the moment the
user confirms, with everything they had staged in front of it.rec and updated: true (the page marks it). Set updated: false once the user
answers it.deps listing the question ids it depends on. New
questions get the next round number. If the tree is fully walked, add no questions and
set note to a short sentence saying every branch is settled and Finish is the next step."visual": { "stale": true }.
Leave visual.html, version, at, and note unchanged; the page marks it out of date
and the user can click Regenerate when ready. Do not launch a draw subagent merely
because the next round is ready. Explicit visualize and visual-feedback actions
still request a draw, and Finish still reconciles the exported visual."agent": { "status": "waiting", "handled": <seq of this send> }
(the example under "Patching state.json" is this patch). One patch is one atomic swap,
so the page sees the answers, thread replies, next round, and acknowledgement together.
Never publish the next round in one patch and handled in a later one while you do
optional work: the page uses handled to clear the previous question's "sent" spinner
and enable the next Send.grill: handled send #3 (Q2 → B, Q4 thread); round 4 has 2 questions; visual v3 out of date,
and return to listening.Interview the user relentlessly about every aspect of the topic until you share an understanding, walking each branch of the design tree and resolving dependencies between decisions in order:
deps.title, a body that states what hangs on it, lettered options
(two to four), and rec with the recommended option and a one-paragraph why that names
the trade-off. A question with no sensible options has options: [] and rec.text.multi: true and
recommend a set: rec.options: ["A","C"] instead of rec.option. The page lets the user
toggle any number of options. Keep single choice whenever picking one rules out the
others; never use multi to dodge a real trade-off, and never offer an "all of the above"
option on a multi question.terms as vocabulary settles: term, one-sentence def, and avoid (words
not to use for it). Use the terms consistently in later questions.durable: true on a question whose decision passes all three gates: hard to reverse,
surprising without context, a real trade-off. Everything else is a routine choice.note; do not pad with filler questions.The header's Visualize button asks for one artifact for the whole grill, the visual:
a prototype when the topic is a user interface (a page, a panel, a flow the user clicks
through), a diagram otherwise (architecture, data flow, sequence, state). Decide from the
topic and the questions so far; say which in visual.kind; switch when feedback asks
("make this a diagram"). Questions are the source of truth and the visual is derived from
them, never the other way round. When the topic is an improvement or a feature in an
existing app, the prototype is drawn in the context of that app: the real page it lands
on, with the app's own chrome and styling, so it looks like what will actually ship. You
know where it lands from the grill; include that context in any subagent brief.
By default you never write visual.html yourself; a subagent draws it. The file runs to hundreds
of lines and is redrawn many times over a grill. Drawing it here would fill this session's
context with markup and slow every later send. You stay the interviewer: you pick the kind,
write the brief, and record the result with patch. The rules for the file itself live
in $SKILL/visual-brief.md; the subagent reads them, you do not repeat them. See
Subagent or inline for when you draw the file yourself.
Draw only for the first Visualize click, Regenerate, explicit visual feedback, or the Finish reconcile. A requested redraw brings the visual up to date with all current questions, including changes accumulated since its last version, not just the triggering send. Ordinary interview turns only mark an affected visual stale.
For subagent draws, follow these steps. The draw runs in the background and the interview goes on: the send that requested it is handled the moment the brief is out, so the user keeps answering and sending while the subagent draws.
Stat <session>/visual.html (do not read it) and note its modification time; a first
draw has none. Then launch ONE subagent with the Agent tool (it runs in the background
and you get a completion notice later), general-purpose type, prompt filled in from this
template (use the absolute path of $SKILL):
Draw the visual for a grill-with-ui design interview. Read
$SKILL/visual-brief.mdfirst and follow it exactly. Session folder:<session>. Project root:<project>. Kind: prototype | diagram. Context: change to an existing app, landing in<route, page, or component>; match that page's real look and surroundings. | New UI, nothing to match. | Diagram of existing code in<modules>. | Diagram of a new system. First cut from the questions instate.json. — or — Redraw of the existingvisual.html. Change only what follows; keep everything else stable:
- Q3 answered B: the discussion panel moves to the right third
- feedback: "make the sidebar collapsible" Write
<session>/visual.htmland reply with ONE line saying what the visual now shows (or what changed).
One send with several triggers (feedback plus answers that change the visual) is one draw with all of them in the list.
Put the draw into the send's one step-6 patch. On a first draw:
"visual": { "kind": …, "version": 0, "thread": [], "stale": false, "drawing": { "seq": <seq> } }.
On a redraw: "visual": { "stale": false, "drawing": { "seq": <seq> } };
the merge keeps version, at, note, and thread, and the file stays as it is. The
same patch finishes the send as usual (agent.handled, "status": "waiting"); print
the terminal line with "visual drawing" in it and return to listening. The page reads
drawing: on a first draw it stays on the questions with the header button reading
Visualizing… and flips to the visual by itself when v1 lands; on a redraw it keeps the
current version on screen with regenerating… in the strip. Send stays enabled
throughout.
While a draw is in flight, handle sends normally. An answer, reopen, or changed
recommendation that affects the visual sets "stale": true as usual (the in-flight
draw did not see it). A new draw request (Visualize, Regenerate, or visual feedback)
does not start a second subagent: reply in the thread now and append the request as
one bullet, "visual": { "queued": ["feedback: …"] }. Never run two draws at once;
both would write the same file.
When the draw lands (its completion notice wakes you): stat <session>/visual.html
again (do not read it) and confirm it exists with a modification time later than the one
you noted at launch. Then patch "visual": { "version": <version + 1>, "note": …, "drawing": null }
(0 → 1 on a first draw; note is one line naming what changed, taken from the
subagent's reply: "v3: discussion panel moved to the right per Q3"). Leave stale out
of it. If visual.queued is non-empty, use those bullets as the next draw's change list
and choose its mode under Subagent or inline. For a background
draw, repeat step 1 and in the same patch give
"drawing": { "seq": <last handled seq> } instead of null, plus
"queued": null. Print one line ("grill: visual v3 landed", or "… landed; drawing v4
from 2 queued notes") and return to listening. Never bump without a new file and never let a
new file land without a bump; the page reloads the iframe only on a bump.
If the subagent fails or the file did not change: on a first draw patch
"visual": null and a note (the sentence above the question list) saying the draw
failed and Visualize can be clicked again; on a redraw patch
"visual": { "drawing": null, "thread": [{ "who": "agent", "text": … }] } saying so. Do
not bump either way. Return to listening so the user can retry.
The page has a Use subagent checkbox beside Visualize, checked by default. Every draw
request carries its value as subagent: the visualize and visual-feedback actions, and
the finish action when a visual exists. A missing subagent means true (an older page).
subagent: true → the background subagent draw above. Background draws require a
top-level session: a subagent's own background tasks are dropped when its turn ends.
If you are yourself a subagent or have no subagent tool, use the inline procedure below.subagent: false → the user wants a faster draw. Draw it
inline, in this turn: read $SKILL/visual-brief.md and write <session>/visual.html
yourself, from the same kind, context, and change list you would have put in the brief.
Before you start, patch the first draw's visual (kind, "version": 0, thread: [],
"stale": false) or, on a redraw, "stale": false, together with
"drawing": { "seq": <seq> }, so the page shows Visualizing… (or regenerating…) while
agent.status stays working. When the file is written, the send's one step-6 patch
carries "version": <version + 1>, the note, "drawing": null, and the usual
agent.handled and "status": "waiting". Send stays disabled while you draw; that is the
trade the user chose.visual.queued as usual; when the in-flight draw lands, draw the queued bullets the way
the newest queued request asked (inline when its subagent was false). Recover that
flag from the request in events.jsonl if needed; visual.queued holds only text bullets.
For queued inline work, set agent.status to working and clear queued when marking
drawing, then publish the completed version and return to waiting; leave handled
unchanged because the queued sends were already acknowledged.Feedback arrives as visual-feedback actions (see Handling a send); sending visual feedback
explicitly requests a redraw. Answers and question discussions do not. On Finish the visual
is reconciled with the decisions and copied next to the doc.
Text the user types in the terminal during a grill answers the current question when that is
unambiguous (one open question, or the text names one): record it exactly as a page send would
(answer.kind: "text", or "option" when it is a letter, and status: "answered"), then
continue as in "Handling a send" from step 3. Its step 6 patch leaves agent.handled as it is:
there was no send. Otherwise ask which question it answers, in one line. The doc path may
also be changed this way ("write the doc to …" → patch "doc").
On a finish action, or when the user says finish in the terminal:
doc (relative to the project root). It is exhaustive and
self-contained, in this order: a one-paragraph summary (linking the visual at
docs/<slug>-visual.html when there is one, see step 3); Terms (each with its
Avoid list); Why (the problem in the user's words); Locked decisions (every
durable question: the decision, the rejected options and why each lost); Routine
choices (every other answered question, one bullet each); Verified facts (anything
you established by exploring rather than asking, if any); Risks; Deferred
(deferred questions, with what would reopen them); Open threads (discussion points
that ended without a decision). Do not compress: a reader with no access to the session
must be able to build from it."finished": { "doc": … } and "agent": { "status": "waiting" }
(after a page Finish this is the send's one patch, with handled); the page shows the
finished banner and locks staging.state.visual exists, it must be reconciled with every answered question before it
is exported. If no draw is in flight and it is not stale and nothing disagrees, copy
<session>/visual.html to docs/<slug>-visual.html next to the doc (same folder, same
slug, -visual.html) and add "visual": <that path> to finished (it is replaced
whole, so give doc again, or fold it into the step 2 patch). Otherwise request one
reconciling draw (or let the in-flight one land), return to listening, and when it lands
copy the file and patch finished with visual then. The reconciling draw uses the
finish action's subagent under Subagent or inline: if no draw
is in flight and inline drawing is selected, draw before step 2 and export it in the same patch. An in-flight
draw must land first; queue any further reconciliation under those same rules.Start the server detached with its output going to a log:
nohup node $SKILL/server.mjs serve --session <session> > <session>/serve.log 2>&1 &.
Verify it with url as in Start. If the harness terminates detached children, keep serve
in a harness-managed running shell session instead and verify url again. A live page
confirms the server is running, not that the agent is listening.
Keep this loop active in the current agent turn:
node $SKILL/server.mjs pending --session <session> and drain
it exactly as Resume step 3 says: every printed line in one turn, in order, following
"Handling a send", with one patch at the end whose agent.handled is the last seq — not
a patch and a new question round per queued send.node $SKILL/server.mjs wait --session <session> --after <agent.handled>.
Use the last acknowledged handled from your patch, not the last sequence merely seen.
--timeout (default 480) bounds how long that wait process sits idle before exiting 3;
it prints the send and exits the moment one lands, so a long idle timeout never delays
delivery. It is not how long one tool call should block: if the shell tool yields a
running process or session ID, keep that single wait alive and poll it in bounded steps
(60 seconds or less, within the harness's limits) so user input and draw completions are
still handled promptly. Never start a second waiter for the same session. Pass a short
--timeout only when the harness cannot keep a yielded process between calls and each
wait must run to completion in the foreground.handled. Exit 3 is an idle timeout: re-issue the wait. Other failures need inspection;
recover if possible, otherwise report the inactive listener rather than silently exit.The user does not need to type "continue" in the terminal to deliver a browser Send.
Only stop under the turn-boundary conditions above. On Finish, once the doc and any final
visual are saved, stop the server using the verified pid in <session>/server.json.
What each field means. You write it only through patch.
{
"topic": "…", "intent": "why this grill exists, 1-2 sentences", "doc": "docs/x-design.md", "project": "/abs/path", "created": "ISO",
"agent": { "status": "waiting|working", "since": "ISO", "handled": 3 },
"note": "optional short sentence shown above the question list",
"finished": { "doc": "docs/x-design.md", "visual": "docs/x-visual.html", "at": "ISO" }, // only after Finish
"visual": { // only after a visualize action
"kind": "prototype|diagram", "version": 3, "at": "ISO",
"note": "v3: discussion panel moved to the right per Q3",
"stale": false, // true after relevant ordinary decisions; no redraw or version bump
"drawing": { "since": "ISO", "seq": 12 }, // while a draw runs; version is 0 before the first lands
"queued": ["feedback: make the sidebar collapsible"], // draw requests that arrived during a draw; next draw takes them
"thread": [{ "who": "user|agent", "text": "…", "at": "ISO" }]
},
"terms": [{ "term": "…", "def": "…", "avoid": ["…"] }],
"questions": [{
"id": "q7", "round": 4, "deps": ["q2"], "title": "…", "body": "…",
"options": [{ "k": "A", "text": "…" }],
"multi": false, // true: any number of options may be picked
"rec": { "option": "A", "why": "…" }, // or { "text": "…", "why": "…" }; multi: { "options": ["A","C"], "why": "…" }
"status": "open|answered|deferred|reopened", "durable": false, "updated": false,
"answer": { "kind": "accept|option|text", "option": "A", "text": "…" }, // multi: { "kind": "accept|option", "options": ["A","C"] }
"explore": { "at": "ISO", "rows": [{ "option": "A", "pros": ["…"], "cons": ["…"] }] }, // after an explore action
"thread": [{ "who": "user|agent", "text": "…", "at": "ISO" }]
}]
}Send lines (events.jsonl, also printed by serve):
{ "type": "send", "seq": 12, "at": "ISO", "session": "/abs/session/folder", "actions": [
{ "q": "q15", "type": "answer", "kind": "accept|option|text", "option": "A", "text": "…" },
{ "q": "q16", "type": "answer", "kind": "accept|option", "options": ["A", "C"] }, // a multi question
{ "q": "q8", "type": "thread", "text": "…" },
{ "q": "q17", "type": "defer" }, { "q": "q3", "type": "reopen" }, { "q": "q9", "type": "explore" },
{ "type": "visualize", "subagent": true }, { "type": "visual-feedback", "text": "…", "subagent": true },
{ "type": "finish", "subagent": true } ] } // subagent: the page's Use subagent checkbox; absent means true© jasonku09, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 44 other files in the repository root of jasonku09/grill-with-ui.
Open the folder on GitHubat commit c8f699d
Grill With UI next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Grill With UI this skilljasonku09/grill-with-ui | 258 | — | ~7.9k | Automated safety check: Pass | MIT | |
| Intent Routerangel291592/Intent-Router | 1k | — | ~7.9k | Automated safety check: Pass | MIT | |
| Interview Meaddyosmani/agent-skills | 104k | 6 repos | ~3.8k | Automated safety check: Pass | MIT | |
| Ask User QuestionMemTensor/MemOS | 12k | — | ~1k | Automated safety check: Pass | Apache-2.0 | |
| Brainstorming Before BuildingjnMetaCode/superpowers-zh | 8.3k | — | ~1.8k | Automated safety check: Pass | MIT | |
| Plannotator Planning Analysisbacknotprop/plannotator | 9.3k | — | ~6.7k | Automated safety check: Pass | Apache-2.0 |
angel291592/Intent-Router
Turns an underspecified do-something request into a typed IntentSpec by looking things up, asking only necessary questions or halting, before any planning or coding.
addyosmani/agent-skills
Asks one question at a time, each with a best guess attached, until the agent is about 95 percent sure what you really want, before any plan, spec or code.
MemTensor/MemOS
Shows a question as a modal in the interface to clarify a task, collect a preference or get approval, since the user cannot see terminal output.
jnMetaCode/superpowers-zh
Turns a rough idea into an approved design before any code is written, sorting the request into spike, bounded or architectural and enforcing an approval gate.
backnotprop/plannotator
Mines a Plannotator archive of denied plans for feedback patterns and prompt improvements, then writes an HTML dashboard report, with a Claude Code fallback.
code-yeongyu/oh-my-openagent
Explore-first planning that turns a vague or large request into one decision-complete work plan, written only after your approval and executed by a separate worker.
Categories
Moves a design-grilling interview from the terminal to a local web page, where each question, recommendation and discussion thread can be handled in any order. Instead of asking questions one at a time in the terminal, this skill serves a local browser page where every question appears with the agent's recommendation. You can answer in any order, discuss each question in its own thread and press a single Send to Agent button to submit.
Grill With UI fits situations like: stress-testing a plan or design through a long list of probing questions; answering interview questions out of order, with notes on each one; resuming an unfinished design interview from an earlier session.
Run `npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a claude-code`. Or copy the skill folder (the jasonku09/grill-with-ui repository) into .claude/skills/grill-with-ui in your project. Claude Code loads it when a task matches its description.
Run `npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a codex`. Or copy the skill folder (the jasonku09/grill-with-ui repository) into .agents/skills/grill-with-ui in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add jasonku09/grill-with-ui --skill grill-with-ui -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/grill-with-ui, .gemini/skills/grill-with-ui, .github/skills/grill-with-ui and .opencode/skills/grill-with-ui in your project.
Going by SKILL.md and its folder, Grill With UI needs the command-line tools its instructions call (node). Our summary lists: Node.js; A local web browser.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Grill With UI is published under the MIT licence (from the LICENSE file in the skill folder). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.9k tokens (SKILL.md is roughly 32k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Grill With UI: Intent Router (angel291592/Intent-Router, 1k stars), Interview Me (addyosmani/agent-skills, 104k stars), Ask User Question (MemTensor/MemOS, 12k stars) and Brainstorming Before Building (jnMetaCode/superpowers-zh, 8.3k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
jasonku09 (a GitHub user) maintains it in jasonku09/grill-with-ui, which has 258 GitHub stars. The repository was last updated on October 8, 2026.
Source: jasonku09/grill-with-ui on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.