@fasaled/muin
v0.3.0
Published
Explore a PDF’s internals from the terminal — objects, streams, page tree, and cross-references — as a navigable graph. Interactive TUI, one-shot commands, and an MCP server for agents.
Readme
Muin
Explore a PDF’s internals from the terminal — indirect objects, streams, page tree, and cross-references — as a navigable graph.
npm install -g @fasaled/muin
# or
bun add -g @fasaled/muin
muin document.pdf
muin document.pdf check
muin document.pdf ls 3 0 R
muin document.pdf find --type Stream --where "/Filter == /FlateDecode"
muin --mcp
muin --mcp document.pdf # optional: pre-open this file
muin --mcp --events live.jsonl # journal every operation; watch it with `muin --follow live.jsonl`
muin --follow live.jsonl # read-only observer of a journaled agent session
muin --max-bytes 10485760 document.pdf
muin help
muin --helpRequires Node.js 18+. Encrypted PDFs are not supported.
The package is @fasaled/muin; the command is still muin.
TUI
muin document.pdf opens a full-screen explorer: the current object’s neighborhood (incoming / current / outgoing refs), the object itself, and a command box. Those panes redraw in place on cd / back and never scroll.
Tabcompletes and cycles command, flag, and reference candidates.Shift+Tabchanges focus between the prompt, graph, and object.- With the graph focused,
←/→pick a pane,↑/↓pick a neighbor,Enteriscd <ref>. - Any other command (
find,tree,check,help,history,cat,stream, …) opens a dismissible overlay. If it is taller than the screen,↑/↓/PageUp/PageDownscroll it;Esccloses it. - The command panel shows the initial command list, file, cwd, active operation, queue, and last executed command (
last:). History is persistent in~/.config/muin/history.json; commands submitted while busy are queued and run in order.
If stdin is not a TTY, Muin uses a line-oriented REPL instead of the TUI.
One-shot commands (muin file.pdf check) open the file, run one verb, and exit. They do not keep cd state.
MCP
muin --mcp is a long-lived server. The agent calls open with a PDF path, then ls / cd / find / … on that session, then close. muin --mcp document.pdf pre-opens that file.
Observing an agent session
muin --mcp --events live.jsonl appends one JSON line per operation (tool, args, outcome, cwd snapshot, truncated preview) to the journal file — 0600, best-effort, never in the agent's way. Configure it once in the agent's MCP server args; the agent itself never sees it. Every successful open rotates a non-empty journal aside (live-<timestamp>.jsonl next to it) and starts fresh, so each opened PDF gets its own file; restarts and probes without an open never rotate. The follower detects the rotation and restarts its timeline. Backups are kept — delete them when done, or replay one with muin --follow <backup>.
muin --follow live.jsonl opens a read-only TUI on that journal: it opens its own session on the PDF and mirrors the agent's navigation, showing each operation as a scrubbable timeline. There is no prompt and no history — commands cannot be typed. ←/→ step through operations, Space auto-plays at a human pace (default 1 op/s, +/- adjust between 100 ms and 5 s), g/G jump to the first/last, overlay scrolling and Esc/Ctrl+C behave like the TUI. The header badge always shows the mode: ▶ play (green), ■ paused (yellow), … waiting (dim), ! diverged (red, mirror disagrees with the journal). Without a TTY, --follow prints operations line-by-line instead.
Shell completion
# bash (current session)
eval "$(muin completion bash)"
# zsh
mkdir -p ~/.zfunc
muin completion zsh > ~/.zfunc/_muin
# then in ~/.zshrc: fpath=(~/.zfunc $fpath) && autoload -Uz compinit && compinit
# fish
mkdir -p ~/.config/fish/completions
muin completion fish > ~/.config/fish/completions/muin.fish# PowerShell 5.1+ / pwsh — current session
muin completion powershell | Out-String | Invoke-ExpressionCompletes flags, *.pdf files, and one-shot command names. The interactive TUI also completes multi-token object references from the current neighborhood without opening another PDF.
Commands
| Command | TUI | MCP | One-shot |
|---|---|---|---|
| ls cd pwd back refs neighbors cat stream find tree check help | yes | yes | yes |
| open / close | no | yes | no |
| export_graph | JSON text | yes | yes |
| quit / exit | yes | no | no |
cd accepts O G R, O,G, a dictionary key (/Pages), or an array index.
Use Cases
- Batch integrity checking: Run
muin file.pdf checkacross files in scripts or invokecheckvia MCP. Powered by QPDF's validation engine in WebAssembly, it detects xref table corruption, stream syntax errors, format anomalies, and dangling object references. - Debugging PDF generators: Inspect documents generated by PDFKit, Typst, WeasyPrint, ReportLab, or custom engines. Navigate the internal object tree (
/Catalog->/Pages->/Contents) like a directory hierarchy to inspect keys, dictionaries, and resource dictionaries. - Resource and stream diagnostics: Query objects and stream attributes (e.g.
find --type Stream --where "/Length > 1000000") to inspect uncompressed payloads, font descriptors, and image XObjects (/Width,/Height,/Filter). - Metadata and structure inspection: Inspect document info dictionaries, XMP metadata streams, accessibility trees (
/StructTreeRoot), and dictionary action entries (/JavaScript,/Launch,/URI) without rendering the document. - Agent-assisted analysis (MCP): Provide LLM agents with structured, targeted access to navigate the object graph, inspect subgraphs (
export_graph), and extract specific streams on demand without loading raw binary files into the context window.
Limits (defaults)
- Max input file: 200 MiB (
--max-bytes). Size is checked withstatbefore the file is read. - Max objects: 200_000
- Max structure JSON: 512 MiB (object graph only; stream bodies are not loaded at open)
export_graph: default depth 2, max depth 8, max 5_000 nodes
License
MIT. Source: github.com/fasaled/muin
