Not in the top 1,000 by GitHub stars

jasonku09/grill-with-ui

Agent-agnostic skill: run a grilling design interview on a local browser page instead of the terminal

History

Review weekly changes to SKILL.md before updating. Dates mark the weeks in which we observed each version; checks apply to the recorded commits.

View weekly changes

SKILL.md

Week of Nov 2, 2026 · +0 / −0 lines

Before Network or broad shell → After Network or broad shell

fc0de407ff3c → fc0de407ff3c

Compare on GitHub

Week of Oct 26, 2026 · Line counts unavailable

Before Not checked yet → After Network or broad shell

First observation → fc0de407ff3c

No earlier version was observed for comparison.

Compare on GitHub

View on GitHubAdd to my skills

Verified commit fc0de407ff3ca5c981304afba0ecf059ed5ce6cd

327GitHub stars
5stars in 7 days
1skill in this repository
28commits in 30 days

Heat

6.2
  • Star growth, last 7 daysNot available
  • Star growth, last 24 hoursNot available
  • Rank movement, last 7 daysNot available
  • Commits, last 30 daysNot available
  • Accounts asking about it through MCP, last 7 daysNot available
  • Hot votes this weekNot available

Heat never changes the order by stars, and sponsorship never touches it. How heat works

Stars over time

Oct 25, 2026Nov 6, 2026327

30-day change not yet available: not enough history.

Individual skills in this repository

How it works

grill-with-ui

When your agent uses it

Run a grilling interview on a local browser page instead of the terminal. Every question is laid out with its recommendation, answerable in any order, with a per-question discussion thread and one "Send to Agent" button. Use when the user says "grill with ui", invokes /grill-with-ui with a topic, or says "/grill-with-ui resume".

What it loads into context

Frontmatter

82 tokens

Always available to the agent

SKILL.md

9,638 tokens

When the skill is activated

Reference documents

16,817 tokens

Loaded on request

Token counts are estimates. Scripts run separately; they are not counted as reference context.

The playbook

  1. Listening mode and turn boundaries
  2. Patching state.json
  3. Start (`/grill-with-ui <topic>`, `$grill-with-ui <topic>`, or "grill with ui: <topic>")
  4. Resume (`/grill-with-ui resume`)
  5. The event rule (this overrides the Monitor tool's own notice)
  6. Handling a send
  7. Interview method (frontier per round)
  8. Visualize
  9. Terminal input
  10. Finish
  11. Wait mode (agents without a Monitor tool)
  12. state.json

Scripts executed separately

  • design/mockups/data.js
  • design/record-demo.mjs
  • markdown.mjs
  • server.mjs
  • test/listener.e2e.mjs
  • test/manual-visual.e2e.mjs
  • test/markdown.test.mjs
  • test/page-markdown-parity.test.mjs
  • test/page.e2e.mjs
  • test/server.test.mjs
  • test/subagent-toggle.e2e.mjs

Files

  • .github/dependabot.ymlasset163 bytes
  • .github/workflows/ci.ymlasset506 bytes
  • .github/workflows/e2e.ymlasset532 bytes
  • .github/workflows/static.ymlasset803 bytes
  • .gitignoreasset10 bytes
  • .no-mistakes.yamlasset639 bytes
  • LICENSEasset1,065 bytes
  • README.mddoc16,034 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • SKILL.mdcore37,734 bytes
  • design/index.htmlasset1,805 bytes
  • design/mockups-codex/PROMPT.mddoc3,334 bytes
  • design/mockups-codex/a-inbox.htmlasset43,163 bytes
  • design/mockups-codex/a-inbox.pngasset113,890 bytes
  • design/mockups-codex/b-scroll.htmlasset43,167 bytes
  • design/mockups-codex/b-scroll.pngasset122,621 bytes
  • design/mockups-codex/c-tree.htmlasset43,246 bytes
  • design/mockups-codex/c-tree.pngasset161,634 bytes
  • design/mockups/a-inbox-clean-staged.pngasset131,778 bytes
  • design/mockups/a-inbox-clean.htmlasset28,696 bytes
  • design/mockups/a-inbox-clean.pngasset138,447 bytes
  • design/mockups/a-inbox-clean.template.htmlasset14,272 bytes
  • design/mockups/a-inbox.htmlasset30,212 bytes
  • design/mockups/a-inbox.pngasset548,272 bytes
  • design/mockups/a-inbox.template.htmlasset15,788 bytes
  • design/mockups/b-scroll.htmlasset31,043 bytes
  • design/mockups/b-scroll.pngasset190,775 bytes
  • design/mockups/b-scroll.template.htmlasset16,619 bytes
  • design/mockups/c-tree.htmlasset32,674 bytes
  • design/mockups/c-tree.pngasset264,647 bytes
  • design/mockups/c-tree.template.htmlasset18,250 bytes
  • design/mockups/data.jsruns14,436 bytes
  • design/record-demo.mjsruns21,533 bytes
  • docs/demo.gifasset641,740 bytes
  • docs/design.mddoc11,061 bytes
  • docs/importance-and-ratings-design.mddoc25,129 bytes
  • docs/importance-and-ratings-visual.htmlasset31,792 bytes
  • docs/visualize-design.mddoc9,007 bytes
  • markdown.mjsruns2,079 bytes
  • page.htmlasset71,631 bytes
  • server.mjsruns45,723 bytes
  • test/listener.e2e.mjsruns6,642 bytes
  • test/manual-visual.e2e.mjsruns9,397 bytes
  • test/markdown.test.mjsruns3,461 bytes
  • test/page-markdown-parity.test.mjsruns3,385 bytes
  • test/page.e2e.mjsruns79,656 bytes
  • test/server.test.mjsruns81,517 bytes
  • test/subagent-toggle.e2e.mjsruns7,145 bytes
  • visual-brief.mddoc4,349 bytes
Read SKILL.md

# grill-with-ui

`$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](#visualize) for drawing).

## Listening mode and turn boundaries

Choose the mode from the tools actually available, not the agent's model name:
View the complete source on GitHub

Skills included

  • grill-with-ui

Repository facts

License
MIT
Created
Sep 16, 2026
Last pushed
Oct 22, 2026
Forks
54
Open issues
4
Project activity
Active

Before you install

Network or broad shell

Automated signals, not a guarantee

Checked Nov 6, 2026

Not fully analyzed: some files were too long, unreadable or missing. This never lowers the level.

Show 21 notes
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • SKILL.md:1 Analysis incomplete: Referenced executable target unavailable; not analyzed
  • design/mockups-codex/a-inbox.html:4 Analysis incomplete: physical or joined line exceeds character limit
  • design/mockups-codex/a-inbox.html:2 Analysis incomplete: physical or joined line exceeds character limit
  • design/mockups-codex/a-inbox.html:3 Analysis incomplete: physical or joined line exceeds character limit
  • design/mockups-codex/b-scroll.html:4 Analysis incomplete: physical or joined line exceeds character limit
  • design/mockups-codex/b-scroll.html:2 Analysis incomplete: physical or joined line exceeds character limit
  • design/mockups-codex/b-scroll.html:3 Analysis incomplete: physical or joined line exceeds character limit
  • design/mockups-codex/c-tree.html:4 Analysis incomplete: physical or joined line exceeds character limit
  • design/mockups-codex/c-tree.html:2 Analysis incomplete: physical or joined line exceeds character limit
Show all 235 signals by file

.no-mistakes.yaml

  • Network accessline 1
    # no-mistakes per-repo config — gates a branch through # review → test → document → lint → push → PR → CI in a disposable worktree, # then opens a clean PR.  Docs: https://kunchenguid.github.io/no-mistakes/
  • Network accessline 2
    # review → test → document → lint → push → PR → CI in a disposable worktree, # then opens a clean PR.  Docs: https://kunchenguid.github.io/no-mistakes/ #
  • Network accessline 3
    # then opens a clean PR.  Docs: https://kunchenguid.github.io/no-mistakes/

README.md

  • Network accessline 33
    The skill is a plain [Agent Skills](https://agentskills.io) folder (`SKILL.md` plus one
  • Network accessline 39
    git clone https://github.com/jasonku09/grill-with-ui ~/Projects/grill-with-ui
  • Network accessline 220
    a loopback request never gets that pass. The port is required: avoid `--port 80`, since browsers omit that default port from Host and are refused. Local clients such as curl remain trusted: POSTs without an Origin header are allowed after t

design/mockups/a-inbox-clean.html

  • Network accessline 5
    <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>grill · grill-with-ui skill · Inbox (clean)</title> <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 6
    <title>grill · grill-with-ui skill · Inbox (clean)</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500&family=IBM+Plex+Sans:wght
  • Network accessline 7
    <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 8
    <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet" />

design/mockups/a-inbox-clean.template.html

  • Network accessline 5
    <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>grill · grill-with-ui skill · Inbox (clean)</title> <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 6
    <title>grill · grill-with-ui skill · Inbox (clean)</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500&family=IBM+Plex+Sans:wght
  • Network accessline 7
    <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 8
    <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet" />

design/mockups/a-inbox.html

  • Network accessline 5
    <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>grill · grill-with-ui skill · Inbox</title> <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 6
    <title>grill · grill-with-ui skill · Inbox</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..700;1,9..144,300..700&family=IBM+Ple
  • Network accessline 7
    <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 8
    <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..700;1,9..144,300..700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
  • Network accessline 18
    body {     font-family: var(--sans); color: var(--ink); background: var(--paper);     background-image: radial-gradient(1200px 600px at 10% -10%, #fbf7ee 0%, transparent 60%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
  • Network accessline 19
    font-family: var(--sans); color: var(--ink); background: var(--paper);     background-image: radial-gradient(1200px 600px at 10% -10%, #fbf7ee 0%, transparent 60%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16
  • Network accessline 20
    background-image: radial-gradient(1200px 600px at 10% -10%, #fbf7ee 0%, transparent 60%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' ba

design/mockups/a-inbox.template.html

  • Network accessline 5
    <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>grill · grill-with-ui skill · Inbox</title> <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 6
    <title>grill · grill-with-ui skill · Inbox</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..700;1,9..144,300..700&family=IBM+Ple
  • Network accessline 7
    <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 8
    <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..700;1,9..144,300..700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
  • Network accessline 18
    body {     font-family: var(--sans); color: var(--ink); background: var(--paper);     background-image: radial-gradient(1200px 600px at 10% -10%, #fbf7ee 0%, transparent 60%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
  • Network accessline 19
    font-family: var(--sans); color: var(--ink); background: var(--paper);     background-image: radial-gradient(1200px 600px at 10% -10%, #fbf7ee 0%, transparent 60%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16
  • Network accessline 20
    background-image: radial-gradient(1200px 600px at 10% -10%, #fbf7ee 0%, transparent 60%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' ba

design/mockups/b-scroll.html

  • Network accessline 5
    <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>grill · grill-with-ui skill · Scroll</title> <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 6
    <title>grill · grill-with-ui skill · Scroll</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap
  • Network accessline 7
    <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 8
    <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />

design/mockups/b-scroll.template.html

  • Network accessline 5
    <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>grill · grill-with-ui skill · Scroll</title> <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 6
    <title>grill · grill-with-ui skill · Scroll</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap
  • Network accessline 7
    <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 8
    <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />

design/mockups/c-tree.html

  • Network accessline 5
    <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>grill · grill-with-ui skill · Tree + Focus</title> <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 6
    <title>grill · grill-with-ui skill · Tree + Focus</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300..800&family=Atkinson+Hyper
  • Network accessline 7
    <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 8
    <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300..800&family=Atkinson+Hyperlegible:wght@400;700&family=Fira+Code:wght@400;600&display=swap" rel="stylesheet" />

design/mockups/c-tree.template.html

  • Network accessline 5
    <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>grill · grill-with-ui skill · Tree + Focus</title> <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 6
    <title>grill · grill-with-ui skill · Tree + Focus</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300..800&family=Atkinson+Hyper
  • Network accessline 7
    <link rel="preconnect" href="https://fonts.googleapis.com" />
  • Network accessline 8
    <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300..800&family=Atkinson+Hyperlegible:wght@400;700&family=Fira+Code:wght@400;600&display=swap" rel="stylesheet" />

design/mockups/data.js

  • Executable scriptline 1
    Executable file

design/record-demo.mjs

  • Executable scriptline 1
    Executable file

docs/importance-and-ratings-design.md

  • Network accessline 295
    need a valid `t` token, or get a 403), then #8's `__GRILL_THEME__` placeholder replacement   on `GET /`. - **Extra fix:** #8's `fetch("/theme", …)` in `page.html` is wrapped with #25's
  • Network accessline 296
    on `GET /`. - **Extra fix:** #8's `fetch("/theme", …)` in `page.html` is wrapped with #25's   `withToken(…)`.
  • Network accessline 297
    - **Extra fix:** #8's `fetch("/theme", …)` in `page.html` is wrapped with #25's

markdown.mjs

  • Executable scriptline 1
    Executable file
  • Network accessline 29
    // Build links next, formatting the label but keeping the URL literal.   // The finished <a> tags are stashed like code spans so bold/italic   // never rewrites the href (e.g. https://example.com/_docs_/x).
  • Network accessline 31
    // never rewrites the href (e.g. https://example.com/_docs_/x).

page.html

  • Network accessline 410
    async function poll() {   try {     const r = await fetch(withToken("/state"), { cache: "no-store" });
  • Network accessline 411
    try {     const r = await fetch(withToken("/state"), { cache: "no-store" });     if (!r.ok) throw new Error(String(r.status));
  • Network accessline 412
    const r = await fetch(withToken("/state"), { cache: "no-store" });
  • Network accessline 416
    if (gone) { gone = false; render(); }     const was = unheard();     listener = await fetch(withToken("/listener"), { cache: "no-store" }).then((x) => (x.ok ? x.json() : null)).catch(() => null);
  • Network accessline 417
    const was = unheard();     listener = await fetch(withToken("/listener"), { cache: "no-store" }).then((x) => (x.ok ? x.json() : null)).catch(() => null);     if (text !== raw) { raw = text; S = JSON.parse(text); onState(); } else if (unhear
  • Network accessline 418
    listener = await fetch(withToken("/listener"), { cache: "no-store" }).then((x) => (x.ok ? x.json() : null)).catch(() => null);
  • Network accessline 529
    async function post(actions) {   let r, j = {};   try { r = await fetch(withToken("/send"), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ actions }) }); j = await r.json().catch(() => ({})); } cat
  • Network accessline 530
    let r, j = {};   try { r = await fetch(withToken("/send"), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ actions }) }); j = await r.json().catch(() => ({})); } catch { r = null; }   if (!j.ok) $("
  • Network accessline 531
    try { r = await fetch(withToken("/send"), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ actions }) }); j = await r.json().catch(() => ({})); } catch { r = null; }
  • Network accessline 856
    theme = THEMES[(THEMES.indexOf(theme) + 1) % THEMES.length];   applyTheme();   fetch(withToken("/theme"), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ theme }) }).catch(() => {});
  • Network accessline 857
    applyTheme();   fetch(withToken("/theme"), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ theme }) }).catch(() => {}); };
  • Network accessline 858
    fetch(withToken("/theme"), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ theme }) }).catch(() => {});

server.mjs

  • Executable scriptline 1
    #!/usr/bin/env node
  • Network accessline 321
    // Browsers set Origin on every POST, same-origin or not; reject a mismatch so another tab (or   // the sandboxed visual iframe, whose Origin is "null") can't forge a request. No Origin at all —   // curl, wait mode, this project's own test
  • Network accessline 322
    // the sandboxed visual iframe, whose Origin is "null") can't forge a request. No Origin at all —   // curl, wait mode, this project's own tests — is still allowed. http://localhost:<port> is this   // same server, so that spelling passes t
  • Network accessline 323
    // curl, wait mode, this project's own tests — is still allowed. http://localhost:<port> is this
  • Network accessline 333
    const srv = http.createServer(async (req, res) => {     const u = new URL(req.url, "http://x");
  • Network accessline 334
    const u = new URL(req.url, "http://x");
  • Network accessline 337
    // Without the token a LAN neighbor could curl a send into the agent's
  • Network accessline 393
    const { port } = srv.address();     const display = anyInterface ? "127.0.0.1" : host;     let url = `http://${display}:${port}/`;
  • Network accessline 394
    const display = anyInterface ? "127.0.0.1" : host;     let url = `http://${display}:${port}/`;     let lanUrl = null;
  • Network accessline 395
    let url = `http://${display}:${port}/`;
  • Network accessline 397
    if (lanToken && (needLanUrl || anyInterface)) {       const lan = lanIPv4();       if (lan) lanUrl = `http://${lan}:${port}/?t=${lanToken}`;
  • Network accessline 398
    const lan = lanIPv4();       if (lan) lanUrl = `http://${lan}:${port}/?t=${lanToken}`;     } else if (lanToken) {
  • Network accessline 399
    if (lan) lanUrl = `http://${lan}:${port}/?t=${lanToken}`;
  • Network accessline 402
    url = `http://${display}:${port}/?t=${lanToken}`;
  • Network accessline 409
    selfHosts.push(`${h.includes(":") && !h.startsWith("[") ? `[${h}]` : h}:${port}`);     }     selfOrigins = selfHosts.map((h) => `http://${h}`);
  • Network accessline 410
    }     selfOrigins = selfHosts.map((h) => `http://${h}`);     writeJson(serverFile, { url, port, pid: process.pid, started: new Date().toISOString(), host, ...(lanUrl ? { lanUrl } : {}) });
  • Network accessline 411
    selfOrigins = selfHosts.map((h) => `http://${h}`);
  • Network accessline 432
    try { // the pid may have been reused: trust it only if the server at that url answers for this session     const probe = new URL(info.url); probe.pathname = "/listener"; // keeps a --host URL's ?t= token     const r = await fetch(probe, { 
  • Network accessline 433
    const probe = new URL(info.url); probe.pathname = "/listener"; // keeps a --host URL's ?t= token     const r = await fetch(probe, { signal: AbortSignal.timeout(2000) });     return r.ok && (await r.json()).session === session ? info : null;
  • Network accessline 434
    const r = await fetch(probe, { signal: AbortSignal.timeout(2000) });

test/listener.e2e.mjs

  • Executable scriptline 1
    Executable file

test/manual-visual.e2e.mjs

  • Executable scriptline 1
    Executable file
  • Network accessline 94
    const put = (id, v) => { document.getElementById(id).textContent = v; }; put("ran", "scripts run"); fetch("/state").then((r) => r.text()).then((t) => (t.includes("Manual visuals") ? "read" : "empty"), () => "blocked").then((v) => put("read"
  • Network accessline 95
    put("ran", "scripts run"); fetch("/state").then((r) => r.text()).then((t) => (t.includes("Manual visuals") ? "read" : "empty"), () => "blocked").then((v) => put("read", v)); fetch("/send", { method: "POST", body: JSON.stringify({ actions: [
  • Network accessline 96
    fetch("/state").then((r) => r.text()).then((t) => (t.includes("Manual visuals") ? "read" : "empty"), () => "blocked").then((v) => put("read", v));
  • Network accessline 97
    fetch("/send", { method: "POST", body: JSON.stringify({ actions: [{ type: "visualize" }] }) }).then((r) => "status " + r.status, () => "blocked").then((v) => put("sent", v));

test/markdown.test.mjs

  • Executable scriptline 1
    Executable file
  • Network accessline 33
    test("links render as anchors, unsafe URLs stay literal", () => {   assert.equal(     md("[docs](https://example.com)"),
  • Network accessline 34
    assert.equal(     md("[docs](https://example.com)"),     '<a href="https://example.com" target="_blank" rel="noopener noreferrer">docs</a>'
  • Network accessline 35
    md("[docs](https://example.com)"),
  • Network accessline 36
    '<a href="https://example.com" target="_blank" rel="noopener noreferrer">docs</a>'
  • Network accessline 49
    );   assert.equal(     md("`[x](https://example.com)`"),
  • Network accessline 50
    assert.equal(     md("`[x](https://example.com)`"),     '<code>[x](https://example.com)</code>'
  • Network accessline 51
    md("`[x](https://example.com)`"),
  • Network accessline 52
    '<code>[x](https://example.com)</code>'
  • Network accessline 56
    test("only absolute http and https links render; everything else stays plain text", () => {   assert.equal(     md("[docs](https://example.com/a?b=1)"),
  • Network accessline 57
    assert.equal(     md("[docs](https://example.com/a?b=1)"),     '<a href="https://example.com/a?b=1" target="_blank" rel="noopener noreferrer">docs</a>'
  • Network accessline 58
    md("[docs](https://example.com/a?b=1)"),
  • Network accessline 59
    '<a href="https://example.com/a?b=1" target="_blank" rel="noopener noreferrer">docs</a>'
  • Network accessline 60
    );   assert.equal(     md("[docs](http://example.com)"),
  • Network accessline 61
    assert.equal(     md("[docs](http://example.com)"),     '<a href="http://example.com" target="_blank" rel="noopener noreferrer">docs</a>'
  • Network accessline 62
    md("[docs](http://example.com)"),
  • Network accessline 63
    '<a href="http://example.com" target="_blank" rel="noopener noreferrer">docs</a>'
  • Network accessline 79
    test("formatting never rewrites link URLs", () => {   assert.equal(     md("[guide](https://example.com/_docs_/x)"),
  • Network accessline 80
    assert.equal(     md("[guide](https://example.com/_docs_/x)"),     '<a href="https://example.com/_docs_/x" target="_blank" rel="noopener noreferrer">guide</a>'
  • Network accessline 81
    md("[guide](https://example.com/_docs_/x)"),
  • Network accessline 82
    '<a href="https://example.com/_docs_/x" target="_blank" rel="noopener noreferrer">guide</a>'
  • Network accessline 83
    );   assert.equal(     md("[**bold** guide](https://example.com)"),
  • Network accessline 84
    assert.equal(     md("[**bold** guide](https://example.com)"),     '<a href="https://example.com" target="_blank" rel="noopener noreferrer"><strong>bold</strong> guide</a>'
  • Network accessline 85
    md("[**bold** guide](https://example.com)"),
  • Network accessline 86
    '<a href="https://example.com" target="_blank" rel="noopener noreferrer"><strong>bold</strong> guide</a>'
  • Network accessline 90
    test("links open in a new tab", () => {   assert.match(     md("[docs](https://example.com)"),
  • Network accessline 91
    assert.match(     md("[docs](https://example.com)"),     /target="_blank" rel="noopener noreferrer"/
  • Network accessline 92
    md("[docs](https://example.com)"),

test/page-markdown-parity.test.mjs

  • Executable scriptline 1
    Executable file
  • Network accessline 50
    "a _real_ emphasis",   "2 * 3 * 4 equals 24",   "[docs](https://example.com)",
  • Network accessline 51
    "2 * 3 * 4 equals 24",   "[docs](https://example.com)",   "[x](javascript:alert(1))",
  • Network accessline 52
    "[docs](https://example.com)",
  • Network accessline 59
    "[x](#q3)",   "[x](mailto:a@example.com)",   "[docs](http://example.com)",
  • Network accessline 60
    "[x](mailto:a@example.com)",   "[docs](http://example.com)",   "[docs](https://example.com/a?b=1)",
  • Network accessline 61
    "[docs](http://example.com)",
  • Network accessline 62
    "[docs](https://example.com/a?b=1)",
  • Network accessline 63
    "\0LINK7\0 and \0CODE3\0",   "`[x](https://example.com)`",   "[guide](https://example.com/_docs_/x)",
  • Network accessline 64
    "`[x](https://example.com)`",
  • Network accessline 65
    "[guide](https://example.com/_docs_/x)",
  • Network accessline 66
    "[**bold** guide](https://example.com)",
  • Network accessline 85
    }   assert.equal(     pageMd("[docs](https://example.com)"),
  • Network accessline 86
    assert.equal(     pageMd("[docs](https://example.com)"),     '<a href="https://example.com" target="_blank" rel="noopener noreferrer">docs</a>'
  • Network accessline 87
    pageMd("[docs](https://example.com)"),
  • Network accessline 88
    '<a href="https://example.com" target="_blank" rel="noopener noreferrer">docs</a>'
  • Network accessline 92
    test("page.html links open in a new tab", () => {   assert.match(     pageMd("[docs](https://example.com)"),
  • Network accessline 93
    assert.match(     pageMd("[docs](https://example.com)"),     /target="_blank" rel="noopener noreferrer"/
  • Network accessline 94
    pageMd("[docs](https://example.com)"),

test/page.e2e.mjs

  • Executable scriptline 1
    Executable file
  • Network accessline 25
    terms: [{ term: "Send", def: "One press of Send to Agent.", avoid: ["submit", "reply"] }],   questions: [     { id: "q1", round: 1, deps: [], title: "Root question", body: "Answered earlier. Retries go through `retry_queue`; see [the guide]
  • Network accessline 26
    questions: [     { id: "q1", round: 1, deps: [], title: "Root question", body: "Answered earlier. Retries go through `retry_queue`; see [the guide](https://example.com/guide), not [the visual](/visual) or [elsewhere](//evil.example).", opti
  • Network accessline 27
    { id: "q1", round: 1, deps: [], title: "Root question", body: "Answered earlier. Retries go through `retry_queue`; see [the guide](https://example.com/guide), not [the visual](/visual) or [elsewhere](//evil.example).", options: [{ k: "A", t
  • Network accessline 135
    await page.reload();   await page.locator(".item").first().waitFor();   check("theme: the choice survives a reload, set before the script runs", (await mode()) === "dark" && await isDark() && (await (await fetch(url)).text()).includes('<htm
  • Network accessline 136
    await page.locator(".item").first().waitFor();   check("theme: the choice survives a reload, set before the script runs", (await mode()) === "dark" && await isDark() && (await (await fetch(url)).text()).includes('<html lang="en" data-theme=
  • Network accessline 137
    check("theme: the choice survives a reload, set before the script runs", (await mode()) === "dark" && await isDark() && (await (await fetch(url)).text()).includes('<html lang="en" data-theme="dark">'));
  • Network accessline 146
    check("markdown: code spans are readable in both themes", await page.locator(".card .body code").count() === 1 && cr.ok, cr.extra);   const mdLinks = await page.locator(".card .body a").evaluateAll((as) => as.map((a) => [a.getAttribute("hre
  • Network accessline 147
    const mdLinks = await page.locator(".card .body a").evaluateAll((as) => as.map((a) => [a.getAttribute("href"), a.target, a.rel]));   check("markdown: only the absolute https link renders, opening a new tab", JSON.stringify(mdLinks) === JSON
  • Network accessline 148
    check("markdown: only the absolute https link renders, opening a new tab", JSON.stringify(mdLinks) === JSON.stringify([["https://example.com/guide", "_blank", "noopener noreferrer"]]), JSON.stringify(mdLinks));
  • Network accessline 797
    [Credential-related source omitted]
  • Network accessline 798
    [Credential-related source omitted]
  • Network accessline 799
    const dotLocal = `http://grill-e2e.local:${lan.port}/?t=${token}`;
  • Network accessline 800
    const lp = await lanBrowser.newPage({ viewport: { width: 1440, height: 900 } });     const seen = []; const refused = [];     lp.on("request", (r) => { if (r.url().startsWith(`http://grill-e2e.local:${lan.port}/`)) seen.push(r.url()); });
  • Network accessline 801
    const seen = []; const refused = [];     lp.on("request", (r) => { if (r.url().startsWith(`http://grill-e2e.local:${lan.port}/`)) seen.push(r.url()); });     lp.on("response", (r) => { if (r.status() === 403) refused.push(`${r.request().met
  • Network accessline 802
    lp.on("request", (r) => { if (r.url().startsWith(`http://grill-e2e.local:${lan.port}/`)) seen.push(r.url()); });

test/server.test.mjs

  • Executable scriptline 1
    Executable file
  • Network accessline 17
    const run = (args, opts = {}) => execFileSync(process.execPath, [SERVER, ...args], { encoding: "utf8", env, ...opts }).trim(); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const post = (url, body) => fetch(url + "send", { me
  • Network accessline 19
    const post = (url, body) => fetch(url + "send", { method: "POST", headers: { "content-type": "application/json" }, body: typeof body === "string" ? body : JSON.stringify(body) });
  • Network accessline 72
    const html = await (await fetch(s.ready.url)).text();
  • Network accessline 73
    assert.match(html, /<textarea/);   const state = await (await fetch(s.ready.url + "state")).json();   assert.equal(state.topic, "Fonts");
  • Network accessline 74
    const state = await (await fetch(s.ready.url + "state")).json();
  • Network accessline 106
    const actions = [{ q: "q1", type: "defer" }];   const withOrigin = (origin, contentType = "application/json") =>     fetch(s.ready.url + "send", { method: "POST", headers: { "content-type": contentType, origin }, body: JSON.stringify({ acti
  • Network accessline 108
    fetch(s.ready.url + "send", { method: "POST", headers: { "content-type": contentType, origin }, body: JSON.stringify({ actions }) });
  • Network accessline 111
    assert.equal((await withOrigin(s.ready.url.slice(0, -1))).status, 200, "the page's own origin is allowed");   const port = Number(new URL(s.ready.url).port);   assert.equal((await withOrigin(`http://localhost:${port}`)).status, 200, "the sa
  • Network accessline 112
    const port = Number(new URL(s.ready.url).port);   assert.equal((await withOrigin(`http://localhost:${port}`)).status, 200, "the same server opened as localhost is allowed");   assert.equal((await withOrigin(`http://localhost:${port + 1}`)).
  • Network accessline 113
    assert.equal((await withOrigin(`http://localhost:${port}`)).status, 200, "the same server opened as localhost is allowed");
  • Network accessline 114
    assert.equal((await withOrigin(`http://localhost:${port + 1}`)).status, 403, "localhost on another port is still rejected");
  • Network accessline 115
    assert.equal((await withOrigin("https://evil.example", "text/plain")).status, 403, "a foreign origin is rejected even as a no-preflight content-type");
  • Network accessline 126
    const themeOf = async (url) => (await (await fetch(url)).text()).match(/<html[^>]*data-theme="([^"]+)"/)[1];
  • Network accessline 127
    assert.equal(await themeOf(a.ready.url), "system", "defaults to system");   const setTheme = (url, theme, origin) =>     fetch(url + "theme", { method: "POST", headers: { "content-type": "application/json", ...(origin ? { origin } : {}) }, 
  • Network accessline 129
    fetch(url + "theme", { method: "POST", headers: { "content-type": "application/json", ...(origin ? { origin } : {}) }, body: JSON.stringify({ theme }) });
  • Network accessline 135
    assert.equal((await setTheme(a.ready.url, "chartreuse")).status, 400, "an unknown theme is rejected");   assert.equal((await setTheme(a.ready.url, "light", "https://evil.example")).status, 403, "a foreign origin is rejected");
  • Network accessline 136
    assert.equal((await setTheme(a.ready.url, "light", "https://evil.example")).status, 403, "a foreign origin is rejected");
  • Network accessline 180
    const { session } = newSession(tmp("grill-v-"));   const s = await startServe(session); t.after(s.stop);   const miss = await fetch(s.ready.url + "visual");
  • Network accessline 181
    const s = await startServe(session); t.after(s.stop);   const miss = await fetch(s.ready.url + "visual");   assert.equal(miss.status, 404);
  • Network accessline 182
    const miss = await fetch(s.ready.url + "visual");
  • Network accessline 185
    const html = "<!doctype html><title>v</title><h1>Prototype</h1>";   writeFileSync(join(session, "visual.html"), html);   const hit = await fetch(s.ready.url + "visual?v=1");
  • Network accessline 186
    writeFileSync(join(session, "visual.html"), html);   const hit = await fetch(s.ready.url + "visual?v=1");   assert.equal(hit.status, 200);
  • Network accessline 187
    const hit = await fetch(s.ready.url + "visual?v=1");
  • Network accessline 248
    writeFileSync(join(session, "visual.html"), "<!doctype html><p>v</p>");   const s = await startServe(session); t.after(s.stop);   const hit = await fetch(s.ready.url + "visual?v=1");
  • Network accessline 249
    const s = await startServe(session); t.after(s.stop);   const hit = await fetch(s.ready.url + "visual?v=1");   assert.equal(hit.status, 200);
  • Network accessline 250
    const hit = await fetch(s.ready.url + "visual?v=1");
  • Network accessline 251
    assert.equal(hit.status, 200);   assert.equal(hit.headers.get("content-security-policy"), "sandbox allow-scripts");   const page = await fetch(s.ready.url);
  • Network accessline 252
    assert.equal(hit.headers.get("content-security-policy"), "sandbox allow-scripts");   const page = await fetch(s.ready.url);   assert.equal(page.headers.get("content-security-policy"), null, "the page itself is not sandboxed");
  • Network accessline 253
    const page = await fetch(s.ready.url);
  • Network accessline 332
    assert.equal(JSON.parse(readFileSync(join(session, "server.json"), "utf8")).pid, ready.pid);   assert.equal(run(["url", "--session", session]), ready.url);   assert.equal((await fetch(ready.url + "state")).status, 200);
  • Network accessline 334
    assert.equal((await fetch(ready.url + "state")).status, 200);
  • Network accessline 399
    assert.equal(ready.lanUrl, JSON.parse(readFileSync(join(session, "server.json"), "utf8")).lanUrl);   const lan = new URL(ready.lanUrl);   assert.equal((await fetch(`${lan.origin}/state${lan.search}`)).status, 200, "the LAN URL is served");
  • Network accessline 400
    const lan = new URL(ready.lanUrl);   assert.equal((await fetch(`${lan.origin}/state${lan.search}`)).status, 200, "the LAN URL is served");   assert.equal((await fetch(`${lan.origin}/listener`)).status, 403, "LAN /listener without the token 
  • Network accessline 401
    assert.equal((await fetch(`${lan.origin}/state${lan.search}`)).status, 200, "the LAN URL is served");
  • Network accessline 402
    assert.equal((await fetch(`${lan.origin}/listener`)).status, 403, "LAN /listener without the token is refused");
  • Network accessline 411
    const hr = JSON.parse(h.stdout.trim());   t.after(() => killPid(hr.pid)); // a second server, if one was wrongly started, is the one server.json names   assert.match(hr.url, new RegExp(`^http://${ip.address.replace(/\./g, "\\.")}:\\d+/\\?t=
  • Network accessline 412
    t.after(() => killPid(hr.pid)); // a second server, if one was wrongly started, is the one server.json names   assert.match(hr.url, new RegExp(`^http://${ip.address.replace(/\./g, "\\.")}:\\d+/\\?t=[0-9a-f]{32}$`));   const again = JSON.par
  • Network accessline 413
    assert.match(hr.url, new RegExp(`^http://${ip.address.replace(/\./g, "\\.")}:\\d+/\\?t=[0-9a-f]{32}$`));
  • Network accessline 419
    const listener = async (url) => (await fetch(url + "listener")).json();
  • Network accessline 473
    const s3 = await startServe(session); t.after(s3.stop);   assert.notEqual(new URL(s3.ready.url).port, String(port), "falls back when the port is taken");   assert.equal((await fetch(s3.ready.url + "state")).status, 200);
  • Network accessline 474
    assert.notEqual(new URL(s3.ready.url).port, String(port), "falls back when the port is taken");   assert.equal((await fetch(s3.ready.url + "state")).status, 200);   await s3.stop(); await new Promise((r) => blocker.close(r));
  • Network accessline 475
    assert.equal((await fetch(s3.ready.url + "state")).status, 200);
  • Network accessline 1036
    const s = await startServe(session); t.after(s.stop);   let st = await (await fetch(s.ready.url + "state")).json();
  • Network accessline 1037
    let st = await (await fetch(s.ready.url + "state")).json();
  • Network accessline 1045
    applied(session, { agent: { status: "working" } });   const working = (await (await fetch(s.ready.url + "state")).json()).agent;
  • Network accessline 1046
    const working = (await (await fetch(s.ready.url + "state")).json()).agent;
  • Network accessline 1061
    st = await (await fetch(s.ready.url + "state")).json();
  • Network accessline 1098
    const s = await startServe(session, ["--lan"]); t.after(s.stop);   assert.match(s.ready.url, /^http:\/\/127\.0\.0\.1:\d+\/$/, "the primary URL stays localhost");   assert.match(s.ready.lanUrl, new RegExp(`^http://${expected.replace(/\./g, "
  • Network accessline 1099
    assert.match(s.ready.url, /^http:\/\/127\.0\.0\.1:\d+\/$/, "the primary URL stays localhost");   assert.match(s.ready.lanUrl, new RegExp(`^http://${expected.replace(/\./g, "\\.")}:\\d+/\\?t=[0-9a-f]{32}$`), "the LAN URL carries a 128-bit to
  • Network accessline 1100
    assert.match(s.ready.lanUrl, new RegExp(`^http://${expected.replace(/\./g, "\\.")}:\\d+/\\?t=[0-9a-f]{32}$`), "the LAN URL carries a 128-bit token and prefers RFC 1918");
  • Network accessline 1110
    assert.equal((await fetch(s.ready.url + "state")).status, 200, "localhost still serves without a token");
  • Network accessline 1111
    assert.equal((await fetch(lan("state"))).status, 200, "the LAN URL serves with the token");
  • Network accessline 1112
    assert.equal((await fetch(lanBase + "state")).status, 403, "the LAN address without a token is rejected");
  • Network accessline 1113
    assert.equal((await fetch(lan("state", "wrong"))).status, 403, "a wrong token is rejected");
  • Network accessline 1114
    const actions = [{ q: "q1", type: "defer" }];   const sendAs = (url, origin) => fetch(url, { method: "POST", headers: { "content-type": "application/json", origin }, body: JSON.stringify({ actions }) });   assert.equal((await sendAs(lan("se
  • Network accessline 1115
    const sendAs = (url, origin) => fetch(url, { method: "POST", headers: { "content-type": "application/json", origin }, body: JSON.stringify({ actions }) });
  • Network accessline 1117
    assert.equal((await sendAs(lanBase + "send", lanOrigin)).status, 403, "a tokenless LAN send is rejected, curl included");
  • Network accessline 1120
    const port = new URL(s.ready.url).port;   const dotLocal = `http://my-mac.local:${port}`;
  • Network accessline 1121
    const dotLocal = `http://my-mac.local:${port}`;
  • Network accessline 1125
    assert.equal((await sendAs(lan("send"), "null")).status, 403, "the sandboxed visual iframe has the token in its src, so an opaque origin stays rejected");   assert.equal((await sendAs(s.ready.url + "send", dotLocal)).status, 403, "a token-l
  • Network accessline 1126
    assert.equal((await sendAs(s.ready.url + "send", dotLocal)).status, 403, "a token-less request keeps the Origin allowlist");   assert.equal((await sendAs(s.ready.url + "send", "https://evil.example")).status, 403, "a foreign origin is still
  • Network accessline 1127
    assert.equal((await sendAs(s.ready.url + "send", "https://evil.example")).status, 403, "a foreign origin is still rejected on loopback");   const themeAs = (url, origin) => fetch(url, { method: "POST", headers: { "content-type": "applicatio
  • Network accessline 1128
    const themeAs = (url, origin) => fetch(url, { method: "POST", headers: { "content-type": "application/json", origin }, body: JSON.stringify({ theme: "system" }) });
  • Network accessline 1130
    assert.equal((await themeAs(lan("theme"), "null")).status, 403, "and rejects the iframe's opaque origin");   assert.equal((await themeAs(lanBase + "theme", lanOrigin)).status, 403, "a tokenless LAN theme write is rejected");   assert.equal(
  • Network accessline 1131
    assert.equal((await themeAs(lanBase + "theme", lanOrigin)).status, 403, "a tokenless LAN theme write is rejected");   assert.equal((await fetch(lanBase)).status, 403, "the LAN page without a token is rejected");   assert.equal((await fetch(
  • Network accessline 1132
    assert.equal((await fetch(lanBase)).status, 403, "the LAN page without a token is rejected");
  • Network accessline 1133
    assert.equal((await fetch(lan("", token))).status, 200, "the LAN page serves with the token");
  • Network accessline 1134
    assert.equal((await fetch(lanBase + "events")).status, 403, "LAN /events without a token is rejected");
  • Network accessline 1135
    assert.equal((await fetch(lan("events"))).status, 200, "LAN /events serves with the token");
  • Network accessline 1136
    assert.equal((await fetch(lanBase + "visual")).status, 403, "LAN /visual without a token is rejected");
  • Network accessline 1137
    assert.equal((await fetch(lan("visual"))).status, 404, "LAN /visual with the token passes the gate (404, no visual yet)");
  • Network accessline 1150
    [Credential-related source omitted]
  • Network accessline 1151
    const port = lan.port;   const lanBase = `http://${lan.host}/`;   const dotLocal = `my-mac.local:${port}`;
  • Network accessline 1152
    const lanBase = `http://${lan.host}/`;
  • Network accessline 1158
    assert.equal((await raw(lanBase, { path: `${p}?t=${token}`, host: dotLocal })).status, 200, `LAN ${p} under a .local Host with the token is served`);   }   assert.equal((await raw(lanBase, { method: "POST", path: `/send?t=${token}`, host: d
  • Network accessline 1159
    }   assert.equal((await raw(lanBase, { method: "POST", path: `/send?t=${token}`, host: dotLocal, body: send, headers: { "content-type": "application/json", origin: `http://${dotLocal}` } })).status, 200, "a .local Send with the token lands"
  • Network accessline 1160
    assert.equal((await raw(lanBase, { method: "POST", path: `/send?t=${token}`, host: dotLocal, body: send, headers: { "content-type": "application/json", origin: `http://${dotLocal}` } })).status, 200, "a .local Send with the token lands");
  • Network accessline 1254
    assert.match(s.stderr(), new RegExp(`^grill: could not open ${s.ready.url.replace(/[.?/]/g, "\\$&")} automatically; open it by hand\n$`), `${why}: exactly one grill: line, naming the URL`);     assert.equal(s.child.exitCode, null, `${why}: 
  • Network accessline 1255
    assert.equal(s.child.exitCode, null, `${why}: the server keeps running`);     assert.equal((await fetch(s.ready.url + "state")).status, 200, `${why}: and keeps serving`);     await post(s.ready.url, { actions: [{ q: "q1", type: "defer" }] }
  • Network accessline 1256
    assert.equal((await fetch(s.ready.url + "state")).status, 200, `${why}: and keeps serving`);

test/subagent-toggle.e2e.mjs

  • Executable scriptline 1
    Executable file

How the signals fit together

Facts from a static read of the files at the pinned commit. They describe what the code does, not why.

Some files could not be read; the facts below still hold.

Signal confidence: 182 facts

and 132 more

Works with

Agent compatibility, from the files in this repository
AgentStatusEvidenceInstall to
Claude CodeCompatibleStandard SKILL.md SKILL.md.claude/skills/<skill-name>/
CodexCompatibleStandard SKILL.md SKILL.md.agents/skills/<skill-name>/
CursorCompatibleStandard SKILL.md SKILL.md.cursor/skills/<skill-name>/
Gemini CLICompatibleStandard SKILL.md SKILL.md.gemini/skills/<skill-name>/
GitHub CopilotCompatibleStandard SKILL.md SKILL.md.github/skills/<skill-name>/
OpenCodeCompatibleStandard SKILL.md SKILL.md.opencode/skills/<skill-name>/
  • Native The repository ships a manifest, folder or frontmatter field for this agent.
  • Compatible A standard SKILL.md that this agent can read.
  • Unknown No evidence either way.

Derived automatically from paths, manifests and frontmatter. Not tested on each agent.