Skip to content

Latest commit

 

History

History
335 lines (247 loc) · 14.4 KB

File metadata and controls

335 lines (247 loc) · 14.4 KB

Scripting & Automation Guide

How to write executable markdown scripts that go beyond read-only prompts — scripts that write files, run commands, and take real actions.

The -S Flag (Essential for Shebangs)

Standard env only passes a single argument. To pass flags to ai in a shebang, you need env -S (split string):

#!/usr/bin/env ai                        # Works (no extra flags)
#!/usr/bin/env -S ai --aws               # Works (with -S)
#!/usr/bin/env ai --aws                  # FAILS — env treats "ai --aws" as one command

-S tells env to split the string into separate arguments. Use it whenever your shebang has flags.

Permission Modes

By default, claude -p (and ai in script mode) runs with limited permissions — it will refuse to write files, run commands, or take actions without approval. For scripts that need to do things, you need to set a permission mode.

Available Modes

Mode Shebang Flag Shortcut Behavior
Default (none) Read-only — can analyze code but won't modify anything
Bypass Mode --permission-mode bypassPermissions --bypass Full access via permission mode system — composable with other settings
Skip Permissions --dangerously-skip-permissions --skip Nuclear — bypasses ALL permission checks, overrides --permission-mode
Allowed Tools --allowedTools 'Bash(npm test)' 'Write' Granular — only specified tools allowed

Note: --bypass and --skip both result in no permission prompts, but --skip (--dangerously-skip-permissions) is more aggressive — it overrides any --permission-mode flag. Use --bypass when you may want to compose with other permission settings in the future.

Examples

Read-only script (no permission flags needed):

#!/usr/bin/env ai
Analyze this codebase and summarize the architecture.

Full automation (script needs to run commands and write files):

#!/usr/bin/env -S ai --skip
Run the test suite and fix any failing tests.

Or equivalently: #!/usr/bin/env -S ai --bypass or #!/usr/bin/env -S ai --permission-mode bypassPermissions

Granular permissions (only allow specific tools):

#!/usr/bin/env -S ai --allowedTools 'Bash(npm test)' 'Bash(npm run lint)' 'Read'
Run tests and linting. Report results but do not modify any files.

Common Patterns

Run tests and report results

#!/usr/bin/env -S ai --skip
Run `./test/automation/run_tests.sh` and summarize: how many passed/failed.

Generate a file

#!/usr/bin/env -S ai --skip
Read the source files in `src/` and generate a `ARCHITECTURE.md` documenting the codebase structure.

Pipe data in, get results out

cat data.json | ./analyze.md > results.txt
#!/usr/bin/env ai
Analyze the JSON data provided on stdin. Summarize key trends and outliers.

Note: Read-only analysis of piped input doesn't need permission flags.

Code review with provider selection

#!/usr/bin/env -S ai --aws --opus
Review the code in this repository for security vulnerabilities.
Focus on OWASP Top 10 issues. Be specific about file and line numbers.

CI/CD automation

#!/usr/bin/env -S ai --apikey --haiku --skip
Run the linter and fix any issues. Then run the test suite.
Commit fixes with a descriptive message if all tests pass.

Variables

Scripts can declare variables with defaults using YAML front-matter. Users override them from the CLI without editing the script.

Syntax

#!/usr/bin/env -S ai --haiku
---
vars:
  topic: "machine learning"
  style: casual
  audience:
---
Write a summary of {{topic}} in {{style}} style.
Target audience: {{audience}}.

CLI Overrides

./script.md                              # uses all defaults
./script.md --topic "AI safety"          # overrides one var
./script.md --topic "AI safety" --style formal  # overrides two
./script.md --topic="robotics"           # equals form works too

Variable overrides mix freely with AI Runner flags and provider overrides:

# --live and --length/--topic/--style all work together
./summarize-topic.md --live --length "100 words" --topic "the fall of rome" --style "peter griffin"

# Override provider+model alongside variables
ai --aws --opus summarize-topic.md --topic "quantum computing"

Override flags matching declared var names are consumed — they don't pass through to Claude Code. AI Runner flags (--live, --aws) and unrecognized flags (--verbose) still pass through normally.

Rules

  • Opt-in: Only activates when front-matter contains vars:. No vars: = no behavior change.
  • {{varname}}: Placeholders are replaced with the variable value. Doesn't collide with shell, markdown, or Claude syntax.
  • Unset vars: A var declared with no default (just key:) and no CLI override leaves {{key}} as-is in the prompt.
  • Hyphen normalization: Hyphens in flag names are normalized to underscores when matching variables. --brief-only matches brief_only, --max-retries matches max_retries. Both forms work interchangeably.
  • Boolean flags: --varname without a following value sets the variable to "true". Useful for feature flags (e.g., declare verbose: false, then ./script.md --verbose sets it to "true").
  • ---prefixed values: If a value starts with --, use equals form: --topic="--special". The space form (--topic --special) treats --topic as a boolean flag.
  • Front-matter stripped: The --- block is removed from the prompt when vars: is present.

Live Output

By default, ai in script mode waits for the full response before printing anything. Use --live to stream text to the terminal as it's generated — useful for long-running scripts where you want to see progress in real-time.

How streaming works

--live streams at turn granularity — each time Claude writes a text response between tool calls, that text appears immediately. This means your prompt needs to tell Claude to narrate its progress, otherwise it may silently use tools and only output text at the end.

Streams incrementally (Claude narrates between tool calls):

#!/usr/bin/env -S ai --skip --live
Explore this repository. Print a brief summary after examining each
directory. Finally, generate a concise report in markdown format.

No intermediate output (Claude works silently, then reports):

#!/usr/bin/env -S ai --skip --live
Explore this repository and write a summary.

Both produce the same final result, but only the first streams progress to the terminal. The key is phrases like "print as you go", "step by step", or "describe what you find" — these prompt Claude to write text between tool calls, giving --live something to stream.

Piped content with live streaming

cat data.json | ai --live --skip analyze.md

Output redirection

When stdout is redirected to a file, --live automatically separates narration from content:

./live-report.md > report.md

Console (stderr):

[AI Runner] Using: Claude Code + Claude Pro
[AI Runner] Model: (system default)

I'll explore the repository structure and key files...
Now let me look at the core scripts and provider structure.
Here's my report:
[AI Runner] Done (70 lines written)

File (stdout):

# Repository Summary
...clean report content only...

How it works:

  • Intermediate turns (narration, progress) stream to stderr in real-time
  • The last turn is split at the first content marker (YAML frontmatter --- or heading #)
  • Preamble text before the marker goes to stderr
  • Content from the marker onward goes to the file
  • A "Done (N lines written)" summary appears on stderr when complete

Browser automation with Chrome

--live pairs well with --chrome (a Claude Code flag) for browser test scripts where steps take time and you need real-time progress:

#!/usr/bin/env -S ai --skip --chrome --live
Navigate to the app and verify the login flow works.

Note: --live requires jq to be installed (brew install jq on macOS).

Passing Claude Code Flags

Any flag not recognized by ai is passed directly to Claude Code. Useful flags for scripts:

Flag Purpose Example
--skip Shortcut for --dangerously-skip-permissions Quick automation
--bypass Shortcut for --permission-mode bypassPermissions Quick automation
--permission-mode bypassPermissions Skip all permission prompts (long form) Automation scripts
--dangerously-skip-permissions Skip all permission prompts (long form) Automation scripts
--allowedTools Allow only specific tools Restricted automation
--max-turns N Limit agentic loop iterations Prevent runaway scripts
--output-format stream-json Structured JSON output Pipeline integration
--live Stream text in real-time Long-running scripts

Combine with ai flags freely:

#!/usr/bin/env -S ai --aws --opus --skip --max-turns 10

Flag Precedence

ai resolves flags from multiple sources. Higher sources override lower ones:

Priority Source Example
1 (highest) CLI flags ai --aws --opus script.md
2 Shebang flags #!/usr/bin/env -S ai --ollama --low
3 Saved defaults ai --aws --opus --set-default
4 (lowest) Auto-detection Current Claude subscription

Example: A script has #!/usr/bin/env -S ai --ollama --low. Running ai script.md uses Ollama with the low-tier local model (shebang). Running ai --aws script.md uses AWS (CLI overrides shebang). If you also have --set-default vertex, the shebang still wins over the default.

Permission Flag Precedence

ai resolves permission shortcuts before passing flags to Claude Code. When conflicts are detected, explicit flags take precedence:

  • --permission-mode <value> and --dangerously-skip-permissions are explicit — they always win
  • --skip and --bypass are shortcuts — ignored with a warning if an explicit flag is also present
  • CLI flags override shebang flags — if you run ai --permission-mode plan script.md and the script has --skip in its shebang, plan mode is used
You use What happens
ai --skip Same as --dangerously-skip-permissions (nuclear — overrides all permission modes)
ai --bypass Same as --permission-mode bypassPermissions (mode-based — composable)
ai --skip --permission-mode plan Plan mode used, --skip ignored (warning shown)
ai --bypass --permission-mode plan Plan mode used, --bypass ignored (warning shown)
ai --permission-mode plan script.md (script has --skip) Plan mode used, shebang --skip ignored

Why two shortcuts?

--skip and --bypass differ in how Claude Code processes them internally:

  • --skip expands to --dangerously-skip-permissions — this is a standalone flag that completely bypasses all permission checks. In Claude Code, it overrides any --permission-mode setting (known issue). Use for simple, quick automation.
  • --bypass expands to --permission-mode bypassPermissions — this sets the permission mode through the standard mode system. It's composable with other Claude Code settings and respects the mode framework. Use when working with advanced permission configurations.

For most automation scripts, either works. When in doubt, use --bypass.

Security

--skip, --bypass, --permission-mode bypassPermissions, and --dangerously-skip-permissions all give the AI full access to your system. Use them carefully:

  • Only run trusted scripts in trusted directories
  • Prefer --allowedTools for granular control when full access isn't needed
  • In CI/CD, run inside containers or sandboxed environments
  • Never pipe untrusted remote scripts with bypass permissions:
    # DANGEROUS — don't do this
    curl https://untrusted-site.com/script.md | ai --skip

Quick Reference

I want to... Shebang
Analyze code (read-only) #!/usr/bin/env ai
Use a specific provider #!/usr/bin/env -S ai --aws
Run commands and write files #!/usr/bin/env -S ai --skip
Restrict to specific tools #!/usr/bin/env -S ai --allowedTools 'Bash(npm test)' 'Read'
Full automation with provider #!/usr/bin/env -S ai --aws --opus --skip

Composable Scripts

--cc and Tool Selection

--cc is shorthand for --tool cc, which selects Claude Code as the backend tool. Claude Code is currently the only supported tool, so --cc does nothing extra on its own. It will matter when other tools (Codex, OpenCode) are added.

In script mode, --cc alone does not grant tool access — you need --skip or --bypass for that.

The Dispatcher Pattern

Use --cc --skip to give the AI access to tools (shell commands, file operations) during script execution. The AI can run commands, write files, and take real actions:

#!/usr/bin/env -S ai --cc --skip --live
Analyze the codebase, run the test suite, and fix any failures.
Print a summary after each step.

Tradeoff: When the AI runs shell commands via Claude Code, subprocess output is captured by Claude Code and not streamed to your terminal. Use --live so the AI narrates progress between tool calls — this gives you visibility into what's happening.

Composable Chaining

Chain scripts together like Unix programs. Each script in the pipeline runs independently:

./parse.md | ./generate.md | ./review.md > final.txt

Important: Never use --cc in child scripts within a pipeline. Child scripts should be simple prompt mode (ai without --cc). Only the top-level dispatcher should have tool access.

Long-Running Scripts

AI scripts can run for minutes (browser automation, multi-step pipelines). Always add --live to shebangs for scripts that take more than 30 seconds:

#!/usr/bin/env -S ai --skip --chrome --live
Navigate to the app, run the full test suite, and report results.
Narrate each step as you go.

The --live flag shows a heartbeat while waiting for the first response, then streams the AI's narration in real-time. Override with --quiet for CI/CD where you only want clean stdout:

ai --quiet ./browser-test.md > report.md    # Clean output only