A local HTTP server that turns an HTML (or Markdown) file into a live,
re-executable report. Any element with a data-cmd attribute becomes a
cell: the server runs the command in your shell and streams the output back
to the browser.
Zero npm dependencies. Node.js 18+. One file.
node sr.mjs report.htmlCoding agents build up a picture of a problem by running commands. They produce findings, but the human has no easy way to reproduce the context the agent had. A shell notebook solves this: the agent writes a single HTML file that is both a readable report and a self-re-executing document. Open it, click "Run all", and you have exactly the same view the agent had — verifiable, repeatable, shareable.
The snapshot export turns the live notebook into a static HTML file you can attach to a bug report or share with a colleague.
shell-report is packaged as a skill for Claude Code and OpenAI Codex. Install it into your agent's skills directory and it will be automatically available in every project.
# Claude Code
git clone https://github.com/callumalpass/shell-report ~/.claude/skills/shell-report
# OpenAI Codex
git clone https://github.com/callumalpass/shell-report ~/.codex/skills/shell-reportOnce installed, the agent will suggest using shell-report when you ask it to
document findings, generate a reproducible report, or validate a hypothesis.
Run sr.mjs from the skill directory:
node ~/.claude/skills/shell-report/sr.mjs report.html
node ~/.codex/skills/shell-report/sr.mjs report.htmlOr clone anywhere and run directly — no install required.
# Serve an HTML notebook
node sr.mjs report.html
# Serve a Markdown notebook
node sr.mjs report.md
# Fixed port, skip browser open
node sr.mjs report.html --port 8080 --no-open
# Override working directory (all commands run here)
node sr.mjs report.html --cwd /path/to/project
# Override shell
node sr.mjs report.html --shell /bin/zshThe server binds to 127.0.0.1 only and prints the URL on startup. Edit the
source file and the browser reloads automatically.
Add data-cmd to any element. The attribute value is the shell command.
<pre> is recommended because it preserves whitespace.
<!DOCTYPE html>
<html>
<body>
<h1>System report</h1>
<h2>Node</h2>
<pre data-cmd="node --version"></pre>
<h2>Disk</h2>
<pre data-cmd="df -h"></pre>
<h2>Git log</h2>
<pre data-cmd="git log --oneline -10"></pre>
</body>
</html>Files with a .md extension are converted to HTML on each request.
Fenced code blocks with cmd, sh, bash, zsh, or shell as the
language tag become cells.
# Build diagnosis
## TypeScript errors
```bash
pnpm tsc --noEmit 2>&1 | head -40
```
## Recent changes
```sh
git log --oneline -5
```Put the command on the same line as the fence:
```sh git log --oneline -10
```Add attributes after a | separator:
```sh git log --oneline -10 | autorun timeout=30 var=log
```| Attribute | Description |
|---|---|
data-cmd="command" |
Shell command to run. Required. |
data-cmd-autorun |
Run automatically on page load (boolean, no value). |
data-cmd-timeout="N" |
Per-cell timeout in seconds. Default: 30. |
data-cmd-var="name" |
Capture trimmed stdout into window._vars.name on success. |
data-cmd-transform="fn" |
Call window.fn(el, {stdout, stderr, exitCode}) instead of default rendering. |
data-cmd-shell="bash" |
Per-cell shell override. Enables polyglot notebooks. |
Pass one cell's output into another cell's command with {{name}}.
<!-- capture the current git SHA -->
<pre data-cmd="git rev-parse HEAD" data-cmd-var="sha"></pre>
<!-- use it in the next command -->
<pre data-cmd="gh run list --commit {{sha}} --limit 5"></pre>Variables are interpolated just before the command is sent to the server. If a placeholder can't be resolved, the cell enters the error state and lists which variables are currently available.
data-cmd-transform names a window function that receives the element
and the full result object. The function owns the element — it can render
a chart, build a table, or do anything else.
<canvas data-cmd="cat metrics.json" data-cmd-transform="renderChart"></canvas>
<script>
function renderChart(el, { stdout }) {
const data = JSON.parse(stdout);
// render with Chart.js, D3, or the raw canvas API
}
</script>Because transforms are plain JavaScript in the same document, an agent can generate both the command and its visualisation in one file.
data-cmd-shell runs a specific cell under a different interpreter:
<pre data-cmd="import json, sys; print(json.dumps({'x': 42}))"
data-cmd-shell="python3"></pre>
<pre data-cmd=". ~/.jq; echo '[1,2,3]' | jq 'add'"
data-cmd-shell="bash"></pre>Click Save snapshot to download a static HTML file. The snapshot captures all current cell output, removes the runtime and play buttons, and produces a standalone document that opens in any browser with no server.
- Commands run as child processes:
spawn($SHELL, ['-c', cmd]) - Environment: full
process.envis inherited — credentials,$PATH, shell functions, everything - Working directory: defaults to the directory you ran
node sr.mjsfrom (override with--cwd) - Commands are serialised — one at a time — so output never interleaves
- Streaming: stdout/stderr chunks arrive in the browser in real time via SSE
- Timeout: each cell has a configurable timeout (default 30s); on expiry the
process receives
SIGTERMand the cell enters the error state
This is a single-user local tool. There is no sandboxing. The document author owns the commands and the credentials.
examples/system.html— environment overview with autorun, variables, and a transformexamples/git-report.md— Markdown notebook for git analysis
node sr.mjs examples/system.html
node sr.mjs examples/git-report.md