### Example Workflow Snippet Source: https://github.com/simonw/showboat/blob/main/README.md A partial example of initializing and populating a demo document. ```bash # Create a demo showboat init demo.md "Setting Up a Python Project" # Add commentary showboat note demo.md "First, let's create a virtual environment." # Run a command and capture output showboat exec demo.md bash "python3 -m venv .venv && echo 'Done'" # Run Python and capture output showboat exec demo.md python "print('Hello from Python')" ``` -------------------------------- ### Install Showboat via Go Source: https://github.com/simonw/showboat/blob/main/README.md Install the Go binary directly from the repository. ```bash go install github.com/simonw/showboat@latest ``` -------------------------------- ### Full Workflow Example Source: https://github.com/simonw/showboat/blob/main/README.md A complete sequence of commands to initialize, populate, and verify a demo document. ```bash # Create a demo showboat init demo.md "Setting Up a Python Project" # Add commentary showboat note demo.md "First, let's create a virtual environment." # Run a command and capture output (output is printed to stdout) showboat exec demo.md bash "python3 -m venv .venv && echo 'Done'" # Run Python and capture output showboat exec demo.md python "print('Hello from Python')" # Oops, wrong command — remove the last entry from the document showboat pop demo.md # Redo it correctly showboat exec demo.md python3 "print('Hello from Python')" # Add a screenshot showboat image demo.md screenshot.png # Add a screenshot with alt text showboat image demo.md '![Homepage screenshot](screenshot.png)' # Verify the demo still works showboat verify demo.md # See what commands built the demo showboat extract demo.md ``` -------------------------------- ### Complete workflow example Source: https://context7.com/simonw/showboat/llms.txt A full sequence demonstrating initialization, execution, image management, verification, and extraction. ```bash # 1. Initialize document showboat init demo.md "API Integration Demo" # 2. Add introduction showboat note demo.md "This demo shows how to interact with our REST API." # 3. Execute and capture commands showboat exec demo.md bash "curl -s https://api.example.com/health | jq ." # Output: { "status": "ok" } showboat exec demo.md python3 " import json data = {'name': 'test', 'value': 42} print(json.dumps(data, indent=2)) " # Output: { "name": "test", "value": 42 } # 4. Add screenshot showboat image demo.md '![API response in browser](response.png)' # 5. Oops, wrong screenshot - remove and redo showboat pop demo.md showboat image demo.md '![Correct API response](correct-response.png)' # 6. Verify everything works showboat verify demo.md # 7. Extract recreation commands for portability showboat extract demo.md > recreate.sh chmod +x recreate.sh ``` -------------------------------- ### Install Showboat via package managers Source: https://github.com/simonw/showboat/blob/main/README.md Install the tool globally using uv or pip. ```bash uv tool install showboat # or pip install showboat ``` -------------------------------- ### Execute Shell Command and Capture Output (Example) Source: https://github.com/simonw/showboat/blob/main/help.txt Demonstrates executing a shell command to create a virtual environment and capture the 'Done' output. ```bash showboat exec demo.md bash "python3 -m venv .venv && echo 'Done'" ``` -------------------------------- ### Run Showboat with uvx Source: https://github.com/simonw/showboat/blob/main/README.md Execute the tool directly without prior installation using uvx. ```bash uvx showboat --help ``` -------------------------------- ### Run Showboat via Go Source: https://github.com/simonw/showboat/blob/main/README.md Execute the tool directly using the Go runtime without installation. ```bash go run github.com/simonw/showboat@latest --help ``` -------------------------------- ### Markdown Format Example Source: https://github.com/simonw/showboat/blob/main/help.txt Illustrates the resulting markdown structure for a Showboat document, including commentary, code blocks, captured output, and image references. ```markdown # Setting Up a Python Project *2026-02-06T15:30:00Z* First, let's create a virtual environment. ```bash python3 -m venv .venv && echo 'Done' ``` ```output Done ``` ```python3 print('Hello from Python') ``` ```output Hello from Python ``` ```bash {image} screenshot.png ``` ![screenshot](screenshot.png) ```bash {image} ![Homepage screenshot](screenshot.png) ``` ![Homepage screenshot](screenshot.png) ``` -------------------------------- ### Create Python Virtual Environment Source: https://github.com/simonw/showboat/blob/main/README.md Create a Python virtual environment using the 'venv' module. This command is typically used at the beginning of a project setup. ```bash python3 -m venv .venv && echo 'Done' ``` -------------------------------- ### Minimal main.go for Showboat CLI Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Sets up the main entry point for the Showboat CLI tool. It handles basic command routing and prints usage information if no command is provided or an unknown command is given. Currently, all subcommands are placeholders. ```go package main import ( "fmt" "os" ) func main() { if len(os.Args) < 2 { printUsage() os.Exit(1) } switch os.Args[1] { case "init": fmt.Fprintln(os.Stderr, "init: not yet implemented") os.Exit(1) case "build": fmt.Fprintln(os.Stderr, "build: not yet implemented") os.Exit(1) case "verify": fmt.Fprintln(os.Stderr, "verify: not yet implemented") os.Exit(1) case "extract": fmt.Fprintln(os.Stderr, "extract: not yet implemented") os.Exit(1) case "--help", "-h", "help": printUsage() os.Exit(0) default: fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1]) printUsage() os.Exit(1) } } func printUsage() { fmt.Print(`showboat - Create executable demo documents that show and prove an agent's work. Showboat helps agents build markdown documents that mix commentary, executable code blocks, and captured output. These documents serve as both readable documentation and reproducible proof of work. A verifier can re-execute all code blocks and confirm the outputs still match. Usage: showboat init Create a new demo document showboat build <file> commentary [text] Append commentary (text or stdin) showboat build <file> run <lang> [code] Run code and capture output showboat build <file> image [script] Run script, capture image output showboat verify <file> [--output <new>] Re-run and diff all code blocks showboat extract <file> Emit build commands to recreate file Global Options: --workdir <dir> Set working directory for code execution (default: current) --help, -h Show this help message Stdin: The build subcommands accept input from stdin when the text/code argument is omitted. For example: echo "Hello world" | showboat build demo.md commentary cat script.sh | showboat build demo.md run bash Example: # Create a demo showboat init demo.md "Setting Up a Python Project" # Add commentary showboat build demo.md commentary "First, let's create a virtual environment." # Run a command and capture output showboat build demo.md run bash "python3 -m venv .venv && echo 'Done'" # Run Python and capture output showboat build demo.md run python "print('Hello from Python')" # Capture a screenshot showboat build demo.md image "python screenshot.py http://localhost:8000" # Verify the demo still works showboat verify demo.md # See what commands built the demo showboat extract demo.md Resulting markdown format: # Setting Up a Python Project *2026-02-06T15:30:00Z* First, let's create a virtual environment. ```bash python3 -m venv .venv && echo 'Done' ``` ```output Done ``` ```python print('Hello from Python') ``` ```output Hello from Python ``` `) } ``` -------------------------------- ### Initialize a New Demo Document Source: https://github.com/simonw/showboat/blob/main/help.txt Create a new markdown file for a demo document with a specified title. ```bash showboat init demo.md "Setting Up a Python Project" ``` -------------------------------- ### Implement Init Command Logic Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Implements the `Init` function which creates a new showboat document. It checks if the file exists, generates a timestamp, and writes the title block to the file. Ensure the `markdown` package is imported. ```go package cmd import ( "fmt" "os" "time" "github.com/simonw/showboat/markdown" ) // Init creates a new showboat document with a title and timestamp. // Returns an error if the file already exists. func Init(file, title string) error { if _, err := os.Stat(file); err == nil { return fmt.Errorf("file already exists: %s", file) } timestamp := time.Now().UTC().Format(time.RFC3339) blocks := []markdown.Block{ markdown.TitleBlock{Title: title, Timestamp: timestamp}, } f, err := os.Create(file) if err != nil { return fmt.Errorf("creating file: %w", err) } defer f.Close() return markdown.Write(f, blocks) } ``` -------------------------------- ### Build and Test Init Command Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Builds the showboat application and manually tests the `init` command by creating a file, then displays its content to verify the title and timestamp were correctly added. ```bash go build -o showboat . && ./showboat init /tmp/test-demo.md "Test Demo" && cat /tmp/test-demo.md ``` -------------------------------- ### Initialize and Build Run in Showboat Source: https://github.com/simonw/showboat/blob/main/demos/shell-output-and-pop.md Initializes a new document and runs a bash command, displaying its output. ```bash rm -f /tmp/demo.md /tmp/showboat init /tmp/demo.md "My Demo" /tmp/showboat build /tmp/demo.md run bash "echo Hello from the inner document" ``` ```output Hello from the inner document ``` -------------------------------- ### Showboat CLI Help Output Source: https://github.com/simonw/showboat/blob/main/README.md Displays the available commands and global options for the Showboat CLI. ```text showboat - Create executable demo documents that show and prove an agent's work. Showboat helps agents build markdown documents that mix commentary, executable code blocks, and captured output. These documents serve as both readable documentation and reproducible proof of work. A verifier can re-execute all code blocks and confirm the outputs still match. Usage: showboat init <file> <title> Create a new demo document showboat note <file> [text] Append commentary (text or stdin) showboat exec <file> <lang> [code] Run code and capture output showboat image <file> <path> Copy image into document showboat image <file> '![alt](path)' Copy image with alt text showboat pop <file> Remove the most recent entry showboat verify <file> [--output <new>] Re-run and diff all code blocks showboat extract <file> [--filename <name>] Emit commands to recreate file Global Options: --workdir <dir> Set working directory for code execution (default: current) --version Print version and exit --help, -h Show this help message ``` -------------------------------- ### Initialize a New Document Source: https://context7.com/simonw/showboat/llms.txt Creates a new markdown file with an H1 title and a timestamp. Fails if the file already exists. ```bash # Create a new demo document showboat init demo.md "Setting Up a Python Project" # The resulting markdown: # # Setting Up a Python Project # # *2026-02-06T15:30:00Z* # # <!-- showboat-id: 550e8400-e29b-41d4-a716-446655440000 --> ``` -------------------------------- ### Wire Init Command into Main Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Integrates the `cmd.Init` function into the main application logic. This involves checking command-line arguments for usage and handling potential errors from the `Init` function. Remember to add the necessary import for the `cmd` package. ```go case "init": if len(os.Args) < 4 { fmt.Fprintln(os.Stderr, "usage: showboat init <file> <title>") os.Exit(1) } if err := cmd.Init(os.Args[2], os.Args[3]); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } ``` -------------------------------- ### Build and Test Showboat CLI Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Compiles the Go project into an executable named 'showboat' and tests its help functionality and basic command execution. ```bash go build -o showboat . ./showboat --help ``` ```bash ./showboat ``` ```bash ./showboat init ``` -------------------------------- ### Initialize Go Module Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Initializes a new Go module with the specified path. This command creates the go.mod file. ```bash go mod init github.com/simonw/showboat ``` -------------------------------- ### Integrate into main.go Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md CLI argument handling logic for the image command. ```go case "image": script, err := getTextArg(os.Args[4:]) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } if err := cmd.BuildImage(file, script, workdir); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } ``` -------------------------------- ### Commit Initial Project Files Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Stages and commits the initial Go module and main application file to version control. ```bash git add go.mod main.go git commit -m "feat: scaffold showboat CLI with help text and command routing" ``` -------------------------------- ### CLI Integration in main.go Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Logic to handle the build subcommand and parse commentary text from arguments or stdin. ```go case "build": if len(os.Args) < 4 { fmt.Fprintln(os.Stderr, "usage: showboat build <file> <subcommand> [args...]") os.Exit(1) } file := os.Args[2] sub := os.Args[3] switch sub { case "commentary": text, err := getTextArg(os.Args[4:]) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } if err := cmd.BuildCommentary(file, text); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } case "run": fmt.Fprintln(os.Stderr, "build run: not yet implemented") os.Exit(1) case "image": fmt.Fprintln(os.Stderr, "build image: not yet implemented") os.Exit(1) default: fmt.Fprintf(os.Stderr, "unknown build subcommand: %s\n", sub) os.Exit(1) } ``` ```go func getTextArg(args []string) (string, error) { if len(args) > 0 { return args[0], nil } // Read from stdin data, err := io.ReadAll(os.Stdin) if err != nil { return "", fmt.Errorf("reading stdin: %w", err) } text := strings.TrimRight(string(data), "\n") if text == "" { return "", fmt.Errorf("no text provided (pass as argument or pipe to stdin)") } return text, nil } ``` -------------------------------- ### Implement BuildImage function Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Core logic for capturing images from scripts and updating the markdown document structure. ```go import "path/filepath" // BuildImage appends an image code block, runs the script, captures the image. func BuildImage(file, script, workdir string) error { if _, err := os.Stat(file); os.IsNotExist(err) { return fmt.Errorf("file not found: %s", file) } destDir := filepath.Dir(file) if destDir == "" { destDir = "." } filename, err := execpkg.RunImage(script, destDir, workdir) if err != nil { return fmt.Errorf("image capture failed: %w", err) } // Parse existing document f, err := os.Open(file) if err != nil { return fmt.Errorf("reading file: %w", err) } blocks, err := markdown.Parse(f) f.Close() if err != nil { return fmt.Errorf("parsing file: %w", err) } blocks = append(blocks, markdown.CodeBlock{Lang: "bash", Code: script, IsImage: true}, markdown.ImageOutputBlock{AltText: "Screenshot", Filename: filename}, ) out, err := os.Create(file) if err != nil { return fmt.Errorf("writing file: %w", err) } defer out.Close() return markdown.Write(out, blocks) } ``` -------------------------------- ### Test Init Creates File Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Tests if the `Init` function correctly creates a new markdown file with the specified title and a timestamp. It also checks for the presence of an ISO 8601 timestamp in the content. ```go package cmd import ( "os" "path/filepath" "strings" testing "testing" ) func TestInitCreatesFile(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "demo.md") err := Init(file, "My Demo") if err != nil { t.Fatal(err) } content, err := os.ReadFile(file) if err != nil { t.Fatal(err) } s := string(content) if !strings.HasPrefix(s, "# My Demo\n\n*") { t.Errorf("unexpected content: %q", s) } if !strings.Contains(s, "T") && !strings.Contains(s, "Z") { t.Error("expected ISO 8601 timestamp") } } ``` -------------------------------- ### Execute commands in demo files Source: https://context7.com/simonw/showboat/llms.txt Run shell or language-specific commands within a demo document. ```bash # showboat exec demo.md bash 'python3 -m venv .venv && echo '\''Done'\''' # showboat exec demo.md python 'print('\''Hello from Python'\'')' ``` -------------------------------- ### Execute Command and Capture Output Source: https://github.com/simonw/showboat/blob/main/README.md Demonstrates running a shell command and capturing its output, including exit codes. ```bash $ showboat exec demo.md bash "echo hello && exit 1" hello $ echo $? 1 ``` -------------------------------- ### Handle Command Failures with Build Run Source: https://github.com/simonw/showboat/blob/main/demos/shell-output-and-pop.md Demonstrates how `build run` captures output and reflects non-zero exit codes when a command fails. ```bash /tmp/showboat build /tmp/demo.md run bash "echo about to fail && exit 1" echo "showboat exit code: $?" ``` ```output about to fail showboat exit code: 1 ``` -------------------------------- ### View project directory structure Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-design.md The file organization of the Showboat Go project. ```text showboat/ ├── main.go # Entry point, top-level CLI routing ├── cmd/ │ ├── init.go # showboat init │ ├── build.go # showboat build (commentary, run, image) │ ├── verify.go # showboat verify │ └── extract.go # showboat extract ├── markdown/ │ ├── parser.go # Parse a showboat markdown file into structured blocks │ ├── writer.go # Serialize structured blocks back to markdown │ └── blocks.go # Block types: commentary, code, output, output-image ├── exec/ │ ├── runner.go # Execute code blocks, capture stdout/stderr │ └── image.go # Run image scripts, copy output file, generate filename ├── go.mod └── go.sum ``` -------------------------------- ### Implement Full Workflow Integration Test Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md A comprehensive Go integration test that builds the binary and verifies initialization, command execution, file structure, and tamper detection. ```go package main import ( "os" "os/exec" "path/filepath" "strings" "testing" ) func TestFullWorkflow(t *testing.T) { // Build the binary tmpBin := filepath.Join(t.TempDir(), "showboat") build := exec.Command("go", "build", "-o", tmpBin, ".") if out, err := build.CombinedOutput(); err != nil { t.Fatalf("build failed: %s\n%s", err, out) } dir := t.TempDir() file := filepath.Join(dir, "demo.md") // Init run(t, tmpBin, "init", file, "Integration Test Demo") // Commentary run(t, tmpBin, "build", file, "commentary", "This demo tests the full workflow.") // Run bash run(t, tmpBin, "build", file, "run", "bash", "echo 'Hello from bash'") // Run python run(t, tmpBin, "build", file, "run", "python3", "print(2 + 2)") // More commentary run(t, tmpBin, "build", file, "commentary", "Everything works.") // Read the file and check structure content, err := os.ReadFile(file) if err != nil { t.Fatal(err) } s := string(content) checks := []string{ "# Integration Test Demo", "This demo tests the full workflow.", "```bash\necho 'Hello from bash'\n```", "```output\nHello from bash\n```", "```python3\nprint(2 + 2)\n```", "```output\n4\n```", "Everything works.", } for _, check := range checks { if !strings.Contains(s, check) { t.Errorf("missing expected content: %q\n\nFull file:\n%s", check, s) } } // Verify should pass run(t, tmpBin, "verify", file) // Extract should produce commands out := runOutput(t, tmpBin, "extract", file) if !strings.Contains(out, "showboat init") { t.Errorf("extract missing init: %s", out) } if !strings.Contains(out, "run bash") { t.Errorf("extract missing run bash: %s", out) } // Tamper and verify should fail tampered := strings.Replace(s, "Hello from bash\n", "TAMPERED\n", 1) os.WriteFile(file, []byte(tampered), 0644) cmd := exec.Command(tmpBin, "verify", file) if err := cmd.Run(); err == nil { t.Error("expected verify to fail after tampering") } // Verify with --output should produce corrected file outputFile := filepath.Join(dir, "fixed.md") cmd = exec.Command(tmpBin, "verify", file, "--output", outputFile) cmd.Run() // may exit non-zero, that's fine fixed, err := os.ReadFile(outputFile) if err != nil { t.Fatal(err) } if !strings.Contains(string(fixed), "Hello from bash") { t.Error("expected fixed file to have correct output") } } func run(t *testing.T, bin string, args ...string) { t.Helper() cmd := exec.Command(bin, args...) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("command failed: %s %v\n%s\n%s", bin, args, err, out) } } func runOutput(t *testing.T, bin string, args ...string) string { t.Helper() cmd := exec.Command(bin, args...) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("command failed: %s %v\n%s\n%s", bin, args, err, out) } return string(out) } ``` -------------------------------- ### Test Init Errors If File Exists Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Tests that the `Init` function returns an error when attempting to create a file that already exists. ```go package cmd import ( "os" "path/filepath" testing "testing" ) func TestInitErrorsIfExists(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "demo.md") os.WriteFile(file, []byte("existing"), 0644) err := Init(file, "My Demo") if err == nil { t.Error("expected error when file exists") } } ``` -------------------------------- ### Wire BuildRun into main.go Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Integrates the `BuildRun` command into the `main.go` file's command handling logic. Parses arguments for language and code, and calls `BuildRun` to execute the command. ```go case "run": if len(os.Args) < 5 { fmt.Fprintln(os.Stderr, "usage: showboat build <file> run <lang> [code]") os.Exit(1) } lang := os.Args[4] code, err := getTextArg(os.Args[5:]) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } if err := cmd.BuildRun(file, lang, code, workdir); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } ``` -------------------------------- ### Build Python Wheels with Go-to-Wheel Source: https://github.com/simonw/showboat/blob/main/README.md Build Python wheels for a project using the 'go-to-wheel' command. This command requires several arguments to define the package metadata. ```bash uvx go-to-wheel . \ --readme README.md \ --description "Create executable documents that demonstrate an agent's work" \ --author 'Simon Willison' \ --license Apache-2.0 \ --url https://github.com/simonw/showboat \ --set-version-var main.version \ --version 0.1.0 ``` -------------------------------- ### Wire showboat Verify Command into main.go Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Integrates the `verify` command into the main application logic. This snippet handles command-line arguments for the input file and an optional output file, then calls the `cmd.Verify` function and handles potential errors or diffs. ```go case "verify": if len(os.Args) < 3 { fmt.Fprintln(os.Stderr, "usage: showboat verify <file> [--output <newfile>]") os.Exit(1) } file := os.Args[2] outputFile := "" for i, arg := range os.Args[3:] { if arg == "--output" && i+3+1 < len(os.Args) { outputFile = os.Args[i+3+1] } } diffs, err := cmd.Verify(file, outputFile) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } if len(diffs) > 0 { for _, d := range diffs { fmt.Fprintln(os.Stderr, d.String()) } os.Exit(1) } ``` -------------------------------- ### Test showboat Verify Command Passes Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Tests the `Verify` function to ensure it passes when no changes have been made to the output. It initializes a file, builds and runs a command, and then verifies that no differences are detected. ```go package cmd import ( "os" "path/filepath" "strings" "testing" ) func TestVerifyPasses(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "demo.md") Init(file, "Test") BuildRun(file, "bash", "echo hello", "") diffs, err := Verify(file, "") if err != nil { t.Fatal(err) } if len(diffs) > 0 { t.Errorf("expected no diffs, got: %v", diffs) } } ``` -------------------------------- ### Test Image Script Execution Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Tests the `RunImage` function by creating a temporary PNG, executing a script to generate it, and verifying the output filename and existence in the destination directory. Requires the `RunImage` function to be defined. ```go package exec import ( "os" "path/filepath" "strings" "testing" ) func TestRunImageScript(t *testing.T) { // Create a temp dir for the test tmpDir := t.TempDir() imgPath := filepath.Join(tmpDir, "test.png") // Script that creates a tiny valid PNG and prints its path script := `printf '\x89PNG\r\n\x1a\n' > ` + imgPath + ` && echo ` + imgPath + ` destDir := t.TempDir() filename, err := RunImage(script, destDir, "") if err != nil { t.Fatal(err) } // Filename should match <uuid>-<date>.<ext> pattern if !strings.HasSuffix(filename, ".png") { t.Errorf("expected .png suffix, got %q", filename) } // File should exist in destDir destPath := filepath.Join(destDir, filename) if _, err := os.Stat(destPath); os.IsNotExist(err) { t.Errorf("expected file at %s", destPath) } } func TestRunImageScriptBadPath(t *testing.T) { script := `echo /nonexistent/file.png` destDir := t.TempDir() _, err := RunImage(script, destDir, "") if err == nil { t.Error("expected error for nonexistent image path") } } ``` -------------------------------- ### BuildCommentary Implementation Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Core logic to parse existing markdown blocks and append a new commentary block to the file. ```go package cmd import ( "fmt" "os" "github.com/simonw/showboat/markdown" ) // BuildCommentary appends a commentary block to an existing showboat document. func BuildCommentary(file, text string) error { if _, err := os.Stat(file); os.IsNotExist(err) { return fmt.Errorf("file not found: %s", file) } f, err := os.Open(file) if err != nil { return fmt.Errorf("reading file: %w", err) } blocks, err := markdown.Parse(f) f.Close() if err != nil { return fmt.Errorf("parsing file: %w", err) } blocks = append(blocks, markdown.CommentaryBlock{Text: text}) out, err := os.Create(file) if err != nil { return fmt.Errorf("writing file: %w", err) } defer out.Close() return markdown.Write(out, blocks) } ``` -------------------------------- ### Implement BuildRun Function Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Implements the `BuildRun` function which appends a code block to a markdown file, executes it using the specified language, and appends the output. Handles file not found and execution errors. ```go import ( execpkg "github.com/simonw/showboat/exec" ) // BuildRun appends a code block, executes it, and appends the output. func BuildRun(file, lang, code, workdir string) error { if _, err := os.Stat(file); os.IsNotExist(err) { return fmt.Errorf("file not found: %s", file) } // Execute the code output, err := execpkg.Run(lang, code, workdir) if err != nil { return fmt.Errorf("execution failed: %w", err) } // Parse existing document f, err := os.Open(file) if err != nil { return fmt.Errorf("reading file: %w", err) } blocks, err := markdown.Parse(f) f.Close() if err != nil { return fmt.Errorf("parsing file: %w", err) } // Append code and output blocks blocks = append(blocks, markdown.CodeBlock{Lang: lang, Code: code}, markdown.OutputBlock{Content: output}, ) out, err := os.Create(file) if err != nil { return fmt.Errorf("writing file: %w", err) } defer out.Close() return markdown.Write(out, blocks) } ``` -------------------------------- ### Extract demo commands Source: https://context7.com/simonw/showboat/llms.txt Generate a portable script from a demo document using a custom filename. ```bash showboat extract demo.md --filename copy.md ``` -------------------------------- ### Add an Image to a Demo Document Source: https://github.com/simonw/showboat/blob/main/help.txt Copy an image file into the demo document's directory and append a markdown image reference. The alt text can be specified. ```bash showboat image demo.md screenshot.png ``` ```bash showboat image demo.md '![Homepage screenshot](screenshot.png)' ``` -------------------------------- ### Implement Image Handling Function Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Implements the `RunImage` function which executes a bash script, extracts the image path from its output, validates the image format, and copies it to a destination directory with a new filename. Requires `github.com/google/uuid` and `io` package. ```go package exec import ( "fmt" "io" "os" "path/filepath" "strings" "time" "github.com/google/uuid" ) // validImageExts lists recognized image file extensions. var validImageExts = map[string]bool{ ".png": true, ".jpg": true, ".jpeg": true, ".gif": true, ".svg": true, } // RunImage runs a bash script that is expected to produce an image file. // The last line of stdout is treated as the path to the image. // The image is copied to destDir with a <uuid>-<date>.<ext> filename. // Returns the new filename (not the full path). func RunImage(script, destDir, workdir string) (string, error) { output, err := Run("bash", script, workdir) if err != nil { return "", fmt.Errorf("running image script: %w", err) } // Last non-empty line of output is the image path lines := strings.Split(strings.TrimSpace(output), "\n") if len(lines) == 0 { return "", fmt.Errorf("image script produced no output") } srcPath := strings.TrimSpace(lines[len(lines)-1]) // Verify file exists info, err := os.Stat(srcPath) if err != nil { return "", fmt.Errorf("image file not found: %s", srcPath) } if info.IsDir() { return "", fmt.Errorf("image path is a directory: %s", srcPath) } // Check extension ext := strings.ToLower(filepath.Ext(srcPath)) if !validImageExts[ext] { return "", fmt.Errorf("unrecognized image format: %s", ext) } // Generate destination filename id := uuid.New().String()[:8] date := time.Now().UTC().Format("2006-01-02") newFilename := fmt.Sprintf("%s-%s%s", id, date, ext) // Copy file src, err := os.Open(srcPath) if err != nil { return "", fmt.Errorf("opening image: %w", err) } defer src.Close() dstPath := filepath.Join(destDir, newFilename) dst, err := os.Create(dstPath) if err != nil { return "", fmt.Errorf("creating destination: %w", err) } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { return "", fmt.Errorf("copying image: %w", err) } return newFilename, nil } ``` -------------------------------- ### Implement showboat Verify Function Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md The `Verify` function re-executes code blocks in a markdown file and compares their output to stored results. It can detect differences and optionally write an updated file with fresh outputs. Requires `execpkg` and `markdown` packages. ```go package cmd import ( "fmt" "os" "strings" execpkg "github.com/simonw/showboat/exec" "github.com/simonw/showboat/markdown" ) // Diff represents a mismatch between stored and actual output. type Diff struct { BlockIndex int Expected string Actual string } func (d Diff) String() string { return fmt.Sprintf("Block %d:\n--- stored\n%s\n+++ actual\n%s", d.BlockIndex, d.Expected, d.Actual) } // Verify re-executes all code blocks and compares outputs. // Returns a list of diffs. If outputFile is non-empty, writes an updated // copy of the document with fresh outputs. func Verify(file, outputFile string) ([]Diff, error) { f, err := os.Open(file) if err != nil { return nil, fmt.Errorf("reading file: %w", err) } blocks, err := markdown.Parse(f) f.Close() if err != nil { return nil, fmt.Errorf("parsing file: %w", err) } var diffs []Diff updatedBlocks := make([]markdown.Block, len(blocks)) copy(updatedBlocks, blocks) for i, block := range blocks { code, ok := block.(markdown.CodeBlock) if !ok { continue } // Skip image blocks for now (image verification is more complex) if code.IsImage { continue } // Execute output, err := execpkg.Run(code.Lang, code.Code, "") if err != nil { return nil, fmt.Errorf("executing block %d: %w", i, err) } // Find the corresponding output block (should be next) if i+1 < len(blocks) { if outBlock, ok := blocks[i+1].(markdown.OutputBlock); ok { if outBlock.Content != output { diffs = append(diffs, Diff{ BlockIndex: i, Expected: strings.TrimRight(outBlock.Content, "\n"), Actual: strings.TrimRight(output, "\n"), }) } updatedBlocks[i+1] = markdown.OutputBlock{Content: output} } } } if outputFile != "" { out, err := os.Create(outputFile) if err != nil { return diffs, fmt.Errorf("writing output file: %w", err) } defer out.Close() if err := markdown.Write(out, updatedBlocks); err != nil { return diffs, fmt.Errorf("writing output: %w", err) } } return diffs, nil } ``` -------------------------------- ### Stdin Input for Commands Source: https://github.com/simonw/showboat/blob/main/README.md Shows how to pipe input into Showboat commands. ```bash echo "Hello world" | showboat note demo.md cat script.sh | showboat exec demo.md bash ``` -------------------------------- ### Unit Tests for BuildCommentary Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Tests for verifying commentary appending, multiple block handling, and error conditions for missing files. ```go package cmd import ( "os" "path/filepath" "strings" "testing" ) func TestBuildCommentary(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "demo.md") Init(file, "Test") err := BuildCommentary(file, "Hello world.") if err != nil { t.Fatal(err) } content, err := os.ReadFile(file) if err != nil { t.Fatal(err) } if !strings.Contains(string(content), "Hello world.") { t.Errorf("expected commentary in file: %s", content) } } func TestBuildCommentaryMultiple(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "demo.md") Init(file, "Test") BuildCommentary(file, "First.") BuildCommentary(file, "Second.") content, err := os.ReadFile(file) if err != nil { t.Fatal(err) } s := string(content) if !strings.Contains(s, "First.") || !strings.Contains(s, "Second.") { t.Errorf("expected both commentaries: %s", s) } } func TestBuildCommentaryNoFile(t *testing.T) { err := BuildCommentary("/nonexistent/demo.md", "Hello") if err == nil { t.Error("expected error for nonexistent file") } } ``` -------------------------------- ### Implement Markdown Parser Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Core logic for parsing Markdown content into structured blocks. ```go package markdown import ( "bufio" "fmt" "io" "strings" ) // Parse reads showboat markdown from r and returns a slice of Blocks. func Parse(r io.Reader) ([]Block, error) { scanner := bufio.NewScanner(r) var blocks []Block var lines []string firstBlock := true flushCommentary := func() { text := strings.Join(lines, "\n") text = strings.TrimRight(text, "\n") text = strings.TrimLeft(text, "\n") if text != "" { blocks = append(blocks, CommentaryBlock{Text: text}) } lines = nil } for scanner.Scan() { line := scanner.Text() // Detect title at start of document if firstBlock && strings.HasPrefix(line, "# ") { firstBlock = false title := strings.TrimPrefix(line, "# ") // Read blank line then timestamp timestamp := "" for scanner.Scan() { tl := scanner.Text() if tl == "" { continue } if strings.HasPrefix(tl, "*") && strings.HasSuffix(tl, "*") { timestamp = strings.Trim(tl, "*") } break } blocks = append(blocks, TitleBlock{Title: title, Timestamp: timestamp}) continue } firstBlock = false // Detect fenced code blocks if strings.HasPrefix(line, "```") { flushCommentary() fence := strings.TrimPrefix(line, "```") fence = strings.TrimSpace(fence) ``` -------------------------------- ### Global CLI options Source: https://context7.com/simonw/showboat/llms.txt Apply global flags to control the execution environment or retrieve version and help information. ```bash # Set working directory for code execution showboat --workdir /path/to/project exec demo.md bash "ls" # Print version and exit showboat --version # Show help showboat --help showboat -h ``` -------------------------------- ### Test BuildRun Command Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Tests the `BuildRun` function to ensure it correctly appends code and output blocks to a markdown file. Verifies that both successful execution and non-zero exit codes are handled. ```go func TestBuildRun(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "demo.md") Init(file, "Test") err := BuildRun(file, "bash", "echo hello", "") if err != nil { t.Fatal(err) } content, err := os.ReadFile(file) if err != nil { t.Fatal(err) } s := string(content) if !strings.Contains(s, "```bash\necho hello\n```") { t.Errorf("expected code block: %s", s) } if !strings.Contains(s, "```output\nhello\n```") { t.Errorf("expected output block: %s", s) } } ``` ```go func TestBuildRunNonZeroExit(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "demo.md") Init(file, "Test") err := BuildRun(file, "bash", "echo fail && exit 1", "") if err != nil { t.Fatal(err) } content, err := os.ReadFile(file) if err != nil { t.Fatal(err) } if !strings.Contains(string(content), "fail") { t.Error("expected output even on non-zero exit") } } ``` -------------------------------- ### Extract Showboat Commands to Recreate a Document Source: https://github.com/simonw/showboat/blob/main/help.txt Parse a demo document and print the sequence of showboat CLI commands required to recreate it from scratch. Output blocks are omitted as they are regenerated by 'exec'. ```bash showboat extract demo.md ``` -------------------------------- ### Test showboat Verify Command Writes Output Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Tests the `Verify` function's capability to write updated outputs to a specified file. It simulates tampering, calls `Verify` with an output file path, and checks if the new file contains the expected fresh output while the original remains untouched. ```go package cmd import ( "os" "path/filepath" "strings" "testing" ) func TestVerifyWritesOutput(t *testing.T) { dir := t.TempDir() file := filepath.Join(dir, "demo.md") Init(file, "Test") BuildRun(file, "bash", "echo hello", "") // Tamper content, _ := os.ReadFile(file) tampered := strings.Replace(string(content), "hello\n", "goodbye\n", 1) os.WriteFile(file, []byte(tampered), 0644) outputFile := filepath.Join(dir, "updated.md") _, err := Verify(file, outputFile) if err != nil { t.Fatal(err) } updated, err := os.ReadFile(outputFile) if err != nil { t.Fatal(err) } if !strings.Contains(string(updated), "hello") { t.Error("expected updated output to contain fresh 'hello'") } // Original should be untouched original, _ := os.ReadFile(file) if !strings.Contains(string(original), "goodbye") { t.Error("original should still contain tampered 'goodbye'") } } ``` -------------------------------- ### Run Parser Tests Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Command to execute the parser tests. ```bash go test ./markdown/ -v -run TestParse ``` -------------------------------- ### Run Command Execution Source: https://github.com/simonw/showboat/blob/main/docs/plans/2026-02-06-showboat-implementation.md Executes code using a specified interpreter and returns combined stdout and stderr. Non-zero exit codes are treated as successful execution if the process completes. ```go package exec import ( "bytes" "fmt" "os/exec" ) // Run executes code using the given language interpreter and returns // the combined stdout+stderr output. Non-zero exit codes are not // treated as errors — the output is still captured and returned. // If workdir is empty, the current directory is used. func Run(lang, code, workdir string) (string, error) { var cmd *exec.Cmd switch lang { case "bash", "sh": cmd = exec.Command(lang, "-c", code) case "python", "python3": cmd = exec.Command(lang, "-c", code) default: // Generic: try lang -c code cmd = exec.Command(lang, "-c", code) } if workdir != "" { cmd.Dir = workdir } var buf bytes.Buffer cmd.Stdout = &buf cmd.Stderr = &buf err := cmd.Run() // We only return an error if the command couldn't be started at all. // Non-zero exit codes are fine — the output is what matters. if err != nil { if _, ok := err.(*exec.ExitError); ok { // Non-zero exit — that's fine, return output return buf.String(), nil } return "", fmt.Errorf("executing %s: %w", lang, err) } return buf.String(), nil } ```