Struckdown CLI Usage Guide
Reference for the sd command-line interface. Run sd <command> --help for the option list of the version you have installed.
Table of Contents
Installation
# Using uv (recommended)
uv tool install git+https://github.com/benwhalley/struckdown/
# Or install in current environment
uv pip install git+https://github.com/benwhalley/struckdown/
Environment Setup:
export LLM_API_KEY="your-api-key" # e.g., from openai.com
export LLM_API_BASE="https://api.openai.com/v1"
export DEFAULT_LLM="gpt-4.1-mini"
If DEFAULT_LLM is not set, the library falls back to gpt-4.1-mini. Model names use the provider:model form (openai:gpt-4o, anthropic:claude-sonnet-4-20250514, ollama:llama3). A bare name with no prefix defaults to OpenAI. When LLM_API_BASE is set, every request goes through that proxy and the provider prefix is stripped.
sd test checks that these settings work; see Other commands.
Commands
| Command | Purpose |
|---|---|
sd chat | Run one prompt, or an interactive conversation |
sd batch | Run one prompt over many inputs |
sd explain | Show a prompt’s structure and execution plan |
sd graph | Print a section dependency graph as Mermaid text |
sd preview | Render a .sd file with syntax highlighting |
sd flat | Resolve {% include %} directives and print the result |
sd edit | Open the browser-based playground on local files |
sd serve | Run the playground as a hosted service |
sd test | Check LLM and embedding connections |
sd setup | Install the Claude Code skill and the VS Code extension |
sd install-skill | Install the Claude Code skill only |
sd install-vscode | Install the VS Code extension only |
sd chat
Run a single prompt. Useful for testing prompts and quick experiments.
Syntax:
sd chat [OPTIONS] [PROMPT]...
The prompt can be given inline, read from a file with -p, or piped on stdin (cat prompt.sd | sd chat).
Options:
| Option | Description |
|---|---|
-p, --prompt-file PATH | Read the prompt from a file |
-s, --source TEXT | File or URL whose content is available as {{source}} |
--raw | When --source is a URL, fetch raw HTML instead of extracted markdown |
-c, --context KEY=VALUE | Set a context variable; repeatable |
-d, --data TEXT | Load a JSON file as context. file.json gives {{data.key}}; key=file.json gives {{data.key.subkey}} |
--model-name TEXT | Model as provider:model. Default: $DEFAULT_LLM |
--seed INT | Random seed, where the model supports one |
--thinking TEXT | Reasoning mode: on, off, minimal, low, medium, high, xhigh |
--show-context / --no-show-context | Print the resolved context after the run (default: off) |
-v, --verbose | Counted: -v shows messages, -vv adds detail and info logs, -vvv debug logs |
-I, --include-path PATH | Extra directory to search for includes; repeatable |
--type PATH | YAML type definition file or directory; repeatable |
--tools PATH | Python tools file or directory; repeatable |
--history PATH | Conversation history file for the @history action (one line per turn, alternating roles) |
-i, --interactive | Keep the conversation going after the first response |
-o, --output PATH | Write slot outputs as a JSON object to a file |
--debug-api | Log full API requests (messages and tool schema) as JSON |
--dump PATH | Save each API call as JSON in this directory, one file per slot ({slot_name}.json) |
--no-stream | Disable streaming output, for models that struggle with structured streaming |
--strict-params | Raise an error on unsupported LLM parameters instead of warning |
Examples:
# Simple completion
sd chat "Tell me a joke: [[joke]]"
# Multiple slots
sd chat "Name a colour: [[pick:colour|red,blue,green]] Describe it: [[description]]"
# With context display
sd chat "Pick a number: [[int:number]]" --show-context
# Prompt file, source document and context variables
sd chat -p prompt.sd -s input.txt -c topic=sun -o result.json
# Different model
sd chat "joke [[joke]]" --model-name anthropic:claude-sonnet-4-20250514
sd batch
Run one prompt over many inputs, concurrently. Inputs come from files, globs, URLs or stdin.
Syntax:
sd batch [OPTIONS] [PROMPT]
Arguments:
PROMPT- Inline prompt with slots. Omit it when using-p.
sd batch takes one positional argument. Input files go through -i, which can be repeated. Passing a file as a second positional argument is a usage error (exit code 2).
Input Sources:
- Files:
sd batch -i file1.txt -i file2.txt "[[summary]]" - Globs:
sd batch -i 'inputs/*.txt' "[[summary]]"(quote the pattern;**is recursive) - Stdin:
cat data.txt | sd batch "[[summary]]" - JSON stdin:
echo '{"name":"Alice"}' | sd batch "Hello {{name}} [[greeting]]"
How each input becomes one or more items:
| Input | Items | Fields |
|---|---|---|
Text file or document (.txt, .pdf, .docx, …) | One | input, content, source (all the text), filename, basename |
.json | One per object (a list gives several) | The object’s keys, plus filename if absent |
.csv, .xlsx | One per row | The column names |
| URL | Fetched and read as a document | As for text |
| Plain text on stdin | One | input, content, filename (<stdin>) |
| JSON on stdin | One per object | The object’s keys |
If the prompt references no template variables at all, {{input}} is prepended to it, so sd batch -i '*.txt' "[[summary]]" sees each file’s text.
Output Formats:
- JSON (
.json) - Structured data - CSV (
.csv) - Flattened tabular format - Excel (
.xlsx) - Spreadsheet format - Markdown (
.md,.txt) - Markdown tables - stdout - JSON to stdout if no
-ois given
The full option list is under Batch Processing Options.
Other commands
sd explain
sd explain PROMPT_FILE [-o OUTPUT]
Parses a prompt and shows the external inputs it needs, its sections and completion slots, the dependencies of each completion, source line numbers, and any parsing errors. With -o plan.html it writes HTML; any other extension gets plain text.
sd check is a hidden, deprecated alias for sd explain.
sd graph
sd graph PROMPT_FILE [-o diagram.mmd]
Prints Mermaid diagram text showing section nodes with their slot names, the dependencies between sections, and external inputs. It does not render an image; for a rendered view use sd explain -o plan.html.
sd preview
sd preview [PROMPT_FILE] [-o out.html] [-r] [-f]
Renders a .sd file with syntax highlighting and opens it in the browser, with includes resolved.
| Option | Description |
|---|---|
-o, --output PATH | Save HTML to a file instead of opening the browser |
-r, --raw | Do not resolve includes |
-f, --fragment | Print an HTML fragment (no page wrapper) to stdout; reads stdin if no file is given |
sd flat
sd flat PROMPT_FILE [-o flattened.sd]
Prints the template with every {% include %} expanded, to stdout or to the -o file.
sd edit
sd edit [PATH]
Starts a local web server with a browser-based editor for writing and testing prompts. PATH is a file to edit or a directory to open as a workspace (default: the current directory).
| Option | Description |
|---|---|
-p, --port INT | Port (default: first free port from 9000) |
--no-browser | Do not open a browser |
-I, --include PATH | Extra include path for actions and types |
-r, --reload | Restart the server when files change |
-m, --models TEXT | Comma-separated list of models offered in the selector |
sd serve
sd serve [OPTIONS]
Runs the playground without local file access, for hosting as a web service. Users supply their own API keys in the settings panel unless a server-side key is given.
| Option | Description |
|---|---|
-p, --port INT | Port (default: PORT env var, else 8000) |
-h, --host TEXT | Host to bind to (default 0.0.0.0) |
--api-key TEXT | Server-side API key |
-m, --models TEXT | Comma-separated allowed models; falls back to STRUCKDOWN_ALLOWED_MODELS |
For production the help suggests gunicorn: gunicorn -w 4 -b 0.0.0.0:8000 "struckdown.playground:create_app(remote_mode=True)".
sd test
sd test [-v]
Checks that LLM_API_KEY and LLM_API_BASE are set, then makes a simple completion call and an embedding call. If credentials are missing it asks for them and saves them to a .env file. -v shows detailed output.
sd setup
sd setup [-f] [--skip-skill] [--skip-vscode]
Installs the Claude Code skill (the /struckdown command, into ~/.claude/commands/) and the VS Code extension for .sd/.soak syntax highlighting. -f overwrites existing installations.
sd install-skill
sd install-skill [-f]
Copies struckdown.md to ~/.claude/commands/ so Claude Code can run it as /struckdown. -f overwrites an existing file.
sd install-vscode
sd install-vscode [-f]
Copies the extension to ~/.vscode/extensions/. -f overwrites an existing installation.
Global Options
| Option | Description |
|---|---|
-V, --version | Show the version and exit |
--install-completion | Install shell completion |
--show-completion | Print the completion script |
--help | Show help for any command |
sd --help
sd chat --help
sd batch --help
Output: stdout Exit code: 0
Note that in sd batch, -h means --head, not help; use --help.
Batch Processing Options
Summary:
| Option | Description |
|---|---|
-i, --input TEXT | Input file, glob or URL; repeatable |
-p, --prompt PATH | Read the prompt from a file |
-o, --output PATH | Output file, format from extension; repeatable |
-k, --keep-inputs | Include input fields in the output |
--template PATH | Jinja2 template applied to non-JSON outputs |
-m, --model TEXT | Model name (default: $DEFAULT_LLM) |
--seed INT | Random seed, where the model supports one |
-j, --concurrency INT | Maximum concurrent API requests (default 20) |
-v, --verbose | Debug logging |
--debug-api | Log full API requests and error details |
-q, --quiet | Hide the progress bar |
-I, --include-path PATH | Extra directory to search for includes; repeatable |
-c, --compare TEXT | Compare an input column with a completion; repeatable |
--statsonly | Print only the comparison statistics, as JSON |
-h, --head INT | Process only the first N inputs |
-e, --classification-errors [INT] | Show misclassified examples |
--min-n-compare INT | Minimum ground-truth count for a category to enter aggregate metrics (default 1) |
-t, --type PATH | YAML type definition file or directory; repeatable |
--tools PATH | Python tools file or directory; repeatable |
-M, --map TEXT | Map input columns to template variable names; repeatable |
types/ and actions/ directories next to the prompt file (or in the current directory) are also discovered automatically. A templates/ directory in the current directory is always on the include path.
-i / --input TEXT
Input file, glob pattern or URL. Repeat it for several sources.
sd batch -i '*.txt' -i 'extra/*.pdf' "[[summary]]"
sd batch -i data.csv "Classify: {{text}} [[pick:label|yes,no]]"
A pattern that matches nothing logs a warning (No files matched pattern) and is skipped. If no -i value matches any file or URL, the command prints Error: No input files or URLs found and exits with code 1.
-o / --output PATH
Output file path. Format is detected from the extension. Repeat -o to write several files from one run.
sd batch -i '*.txt' "[[name]]" -o results.json # JSON output
sd batch -i '*.txt' "[[name]]" -o results.csv # CSV output
sd batch -i '*.txt' "[[name]]" -o results.xlsx # Excel output
sd batch -i '*.txt' "[[name]]" -o results.md # Markdown table
sd batch -i '*.txt' "[[name]]" -o results.json -o results.csv
Default: Outputs JSON to stdout if omitted.
--template PATH
A Jinja2 template applied to every non-JSON output. JSON outputs are always written as standard JSON. Requires at least one non-JSON -o.
sd batch -i '*.txt' -p prompt.sd -o results.json -o report.html --template report.j2
The command’s own --help text shows this example with -t, but -t is the short form of --type; use --template.
-p / --prompt PATH
Load the prompt from a file instead of inline.
# prompt.sd contains: "Extract name: [[name]]"
sd batch -i '*.txt' -p prompt.sd -o results.json
Cannot be combined with an inline prompt. Includes are resolved relative to the prompt file.
-k / --keep-inputs
Include input fields in the output.
sd batch -i '*.txt' "[[summary]]" -k -o results.json
Output for a text file includes:
{
"filename": "input.txt",
"input": "original text...",
"content": "original text...",
"source": "original text...",
"basename": "input",
"summary": "extracted summary"
}
If a slot has the same name as an input column, the input is stored as name.data and the completion as name.predicted.
Default: Only the slot outputs plus filename, for traceability.
Every row also carries _error, _error_class and _error_text columns; see Exit Codes.
-m / --model TEXT
Model for this run, as provider:model or a bare name.
sd batch -i '*.txt' "[[summary]]" -m openai:gpt-4o
Default: $DEFAULT_LLM.
-j / --concurrency INT
Maximum number of API requests in flight at once. Default 20.
sd batch -i '*.txt' "[[summary]]" -j 5
--seed INT
Random seed passed to the API, for models that support it.
-h / --head INT
Process only the first N inputs. After the run, the cost summary adds an estimate for the full input set, scaled by input size for text files or by item count for tabular data.
sd batch -i data.csv -p prompt.sd -h 10
-M / --map TEXT
Copy an input column to a template variable name. target=source makes {{target}} hold the source column; a bare name is a same-name copy.
sd batch -i data.csv -p prompt.sd -M input=content -o results.json
-I / --include-path PATH, -t / --type PATH, --tools PATH
Extra directories for includes, extra YAML type definitions, and extra Python actions. All three can be repeated.
sd batch -i '*.txt' -p prompt.sd -I ./includes -t ./types --tools ./actions
Comparing against ground truth: -c, --statsonly, -e, --min-n-compare
When the input already holds a correct answer (a labelled CSV, for example), -c compares it with a completion and prints agreement statistics.
-c labelcompares thelabelcolumn with thelabelslot.-c label=predictedcompares thelabelcolumn with thepredictedslot.-c allpairs every slot with the input column of the same name.
Statistics go to stdout when there is no -o, otherwise to stderr.
sd batch -i labelled.csv -p classify.sd -k -c label -o results.csv
--statsonly prints only the statistics, as JSON, and writes no results. It requires at least one -c.
-e shows examples of misclassifications: -e alone shows all of them, -e 3 shows up to three per error type.
--min-n-compare N excludes categories with fewer than N ground-truth cases from the macro and weighted F1 figures (default 1).
-q / --quiet
Hide the progress bar.
sd batch -i '*.txt' "[[name]]" -o results.json --quiet
Behaviour:
- No progress bar
- The cost summary and the list of failed items are still printed to stderr
--verboseoutput is still printed if--verboseis also given
Use case: Scripting, cron jobs, CI pipelines.
-v / --verbose
Enable debug logging to stderr. Unlike sd chat, this is a plain flag, not counted.
sd batch -i '*.txt' "[[name]]" --verbose 2> debug.log
Output includes:
- Debug-level log messages from struckdown
- The types and actions loaded or discovered
- One line per processed item
- A traceback for each failed item
Destination: stderr
--debug-api
Log full API requests and error details.
Progress Bars
sd batch shows a progress bar by default during processing:
⠋ 47/100 completions ━━━━━━━━━━━━━━━━━━━━━━ 47% 0:00:23
The total is an estimate: number of inputs times number of slots in the prompt.
Display includes:
- Spinner animation
- Completions so far / estimated total
- Progress bar
- Percentage
- Estimated time remaining
Automatic Behaviour
Shown when stderr is a terminal and --quiet is not set.
Hidden when:
- stderr is redirected:
sd batch ... 2> errors.log --quietis used
Piping stdout (sd batch ... | jq .) does not hide it, because the bar is drawn on stderr.
Manual Control
# Shown (default in a terminal)
sd batch -i '*.txt' "[[name]]" -o out.json
# Hidden
sd batch -i '*.txt' "[[name]]" -o out.json --quiet
# Discard all diagnostics
sd batch -i '*.txt' "[[name]]" -o out.json 2>/dev/null
Output Streams
stdout (File Descriptor 1)
Machine-readable results, for piping or capture.
Contains:
- Results (JSON) when no
-ois given - Comparison statistics when
-cis used without-o --versionand--helpoutput
Example:
sd batch -i '*.txt' "[[name]]" > results.json # Only results captured
stderr (File Descriptor 2)
Human-readable status, errors and warnings.
Contains:
- Progress bars
- The cost summary (always printed at the end of a batch)
- The list of failed items
- Error messages and warnings
- Verbose debug logs
Example:
sd batch -i '*.txt' "[[name]]" 2> errors.log # Only diagnostics captured
Exit Codes
| Code | Meaning | Examples |
|---|---|---|
| 0 | Run finished | All items processed, including runs where some items failed |
| 1 | Error before or instead of processing | No input found, prompt file missing, both inline and -p prompt given, --statsonly without -c |
| 2 | Usage error | Unknown option, too many positional arguments |
A failure on one item does not stop the batch or change the exit code. The item gets an output row with _error set to true and the exception in _error_class and _error_text, and stderr ends with Completed with N error(s): and one line per failure. Check the _error column, not only the exit code.
Example:
sd batch --invalid-flag
# Exit code: 2 (usage error)
sd batch missing-file.txt "[[x]]"
# Exit code: 2 (two positional arguments)
sd batch -i missing-file.txt "[[x]]"
# Exit code: 1 ("No input files or URLs found")
sd batch -i '*.txt' "[[x]]" -o out.json
# Exit code: 0
Examples
Basic Batch Processing
Extract names from text files:
sd batch -i 'documents/*.txt' "Extract person's name: [[name]]" -o names.json
Output (trimmed; each row also has the _error columns):
[
{"filename": "documents/doc1.txt", "name": "Alice"},
{"filename": "documents/doc2.txt", "name": "Bob"}
]
Piping and Chaining
Chain multiple extraction steps:
sd batch -i '*.txt' "Purpose: [[purpose]] Name: [[name]]" | \
sd batch "{{name}}: {{purpose}}. Amazon equivalent? [[product]]" -k
Process and filter with jq:
sd batch -i '*.txt' "Price: [[number:price]]" | jq '.[] | select(.price > 100)'
Quiet Mode for Scripts
Silent execution, then check for failed items:
#!/bin/bash
sd batch -i 'data/*.txt' "[[summary]]" -o results.json --quiet 2> errors.log || exit 1
if jq -e 'any(.[]; ._error)' results.json > /dev/null; then
echo "Some items failed, check errors.log"
exit 1
fi
Trying a Prompt on a Sample
Run the first ten rows and see the projected cost of the full run:
sd batch -i data.csv -p prompt.sd -h 10 -o sample.csv
Reading from Stdin
Plain text:
echo "Hello world" | sd batch "Translate to Spanish: [[translation]]"
JSON input:
echo '[{"name":"Alice"},{"name":"Bob"}]' | \
sd batch "Hello {{name}}! [[greeting]]"
From file:
cat data.txt | sd batch "Summarise: [[summary]]" -o summary.json
Multiple Output Formats
CSV for spreadsheet import:
sd batch -i '*.txt' "Name: [[name]] Age: [[int:age]]" -o people.csv
Excel for reports:
sd batch -i '*.txt' "Product: [[product]] Price: [[number:price]]" -o report.xlsx
Markdown for documentation:
sd batch -i '*.txt' "Feature: [[feature]] Status: [[pick:status|done,wip,todo]]" -o status.md
Debugging Failures
sd batch -i failing-input.txt "[[x]]" --verbose
sd batch -i failing-input.txt "[[x]]" --debug-api
Tips
Performance
- Caching: repeated calls with the same prompt use cached results (see
$STRUCKDOWN_CACHE) - One command, many files:
sd batchruns items concurrently (-j); a shell loop does not - Sample first:
-h 10shows the cost of a small run and an estimate for the whole set
Composability
- Pipe results:
sd batch ... | sd batch ...for multi-stage extraction - Use jq: post-process JSON output with
jq - Redirect: capture results and diagnostics separately
Debugging
- Start with chat: test prompts with
sd chatbefore batch processing - Inspect structure:
sd explain prompt.sdshows inputs, slots and dependencies - Check
_error: failed items do not change the exit code
Related Documentation
- Getting Started - Quick start guide
- Template Syntax - Struckdown template syntax
- Model Overrides - Per-slot LLM configuration
- Number Extraction - Numeric validation
Migration from chatter
The legacy chatter CLI was removed in v0.1.6.
Old command:
chatter run "Tell me a joke: [[joke]]"
New command:
sd chat "Tell me a joke: [[joke]]"