Toast Cookbook

Recipes for AI-powered Unix workflows. Everything here has been checked against the binaries.

Getting started

curl -fsSL linuxtoaster.com/install | sh

# what can this account reach?
toast --balance
1240 credits - you@example.com - solo
Available models:
  Dev
  Paranoid
  Security

# install a persona: --add symlinks the name to the toast binary
toast --add Dev
Dev app.py "add type hints"
Use -f on the install. Without it, curl pipes an HTTP error page into sh. And run --balance before --add: --add happily creates a symlink for a persona your account cannot reach, and you only find out at request time.

Two rules

1. Never eval model output

The model is the non-deterministic part. Everything around it exists to keep that contained, and eval throws the containment away.

# NO. one stray semicolon and you have handed over the shell.
# in practice it usually just breaks, because models wrap code in fences.
CMD=$(toast "write the convert command")
eval "$CMD"

# YES. write it out, read it, then run it.
toast "write a convert command for in.jpg -> out.jpg, command only, no prose" > run.sh
cat run.sh          # <-- you are the review step
sh run.sh

If you want the model to actually run things, use the allowlist instead. .tools holds one command per line, matching on the first word, and execution goes through jam. No file means nothing runs.

printf 'df\njournalctl\nsystemctl\n' > .tools
toast "is this box running out of disk, and what is eating it?"
The difference matters. eval grants everything and reviews nothing. .tools grants three commands and reviews every call. Five rounds maximum per invocation, so a tool loop cannot run away.
But .tools is a guardrail, not a sandbox. The allowlist matches the first word only, so it decides which command runs and nothing about what that command does with its arguments โ€” find . -delete is still find. Keep the list to narrow, read-only commands, and put firejail or a container under anything that writes.

2. Batch, don't stream

In batch mode toast reads stdin to EOF. Anything that never sends EOF will hang forever:

# NO. tail -f never closes, so toast blocks and never prints a word.
# and if it did work, that is one paid request per log line.
tail -f app.log | toast "diagnose"

# YES. take a window, ask once.
grep ERROR app.log | tail -n 200 | toast "diagnose and suggest fixes"

# YES. or on a timer, on the local model, for nothing.
toasted
* * * * *  grep ERROR /var/log/app.log | tail -n 200 | toast "anything new?"

Context files

Four files, all plain text, all found by walking up from the current directory. Version them, grep them, delete them.

FileWhat it is
.personaYour system prompt for plain toast
.crumbsContext prepended to every prompt in this tree
.toolsCommands toast may run, one per line
.chatChat transcript, written in the current directory
cat > .crumbs
Python 3.11, FastAPI, PostgreSQL.
Follow PEP 8. Prefer type hints.
Sessions moved to JWT so the mobile client works offline.
^D

echo "You are terse. If the answer is a command, print the command and nothing else." > .persona
A .persona that says "command only, no prose" is what makes the generate-then-read pattern practical. Without it you get a paragraph of explanation wrapped around the thing you wanted.

Chat & rooms

Run toast with no arguments and a terminal on stdin. Leave with /exit, /quit, or Ctrl+D. The transcript goes to .chat in the current directory.

$ toast
> walk me through this stack trace
> /exit

# several personas in a single chat session
$ toast --room Dev,Paranoid,Security
$ toast --room Dev,Paranoid --color full

API

Use the CLI for terminal work. For an application, talk to the daemon or the hosted endpoint with the same message shape the CLI uses:

curl -fsS -X POST https://linuxtoaster.com/api/chat \
  -H "Authorization: Bearer $TOAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "messages": [{"role": "user", "content": "explain recursion"}],
        "provider": "toast",
        "model": "Dev",
        "stream": false
      }'
Confirm the endpoint before you build on it. The message shape above is what toast and jam send to toastd. Whether the public HTTP route is /api/chat and whether it takes the same body is worth a mail to sales@linuxtoaster.com rather than a guess in production.

Set TOAST_API_KEY to use an explicit token instead of the one derived from the machine. That is what you want in CI.

Generate the tool, not the answer

The principle: do not push a gigabyte through a model. Ask the model for the twenty characters of awk that will do it, then run the awk a thousand times for free.

# the model sees a header row. awk sees the gigabyte.
head -n 3 huge.csv | toast "awk one-liner to sum column 4, skip the header. command only." > sum.awk

cat sum.awk        # read it
awk -F, 'NR>1 {s+=$4} END {print s}'

sh sum.awk huge.csv

Two things make this work: the model gets three lines instead of the whole file, and the generated command is a file you can read before it runs. The saving is not marginal โ€” a 2GB CSV costs one small request instead of thousands.

Image processing

toast "ImageMagick command: grayscale, resize to 800px wide, 10px white border,
        in.jpg to out.jpg. command only, no prose." > convert.sh
cat convert.sh
sh convert.sh

# happy with it? now it is a deterministic tool. run it on ten thousand files.
find photos -name '*.jpg' -print0 | xargs -0 -n1 -P4 ./convert.sh
This is the whole pattern in one line: use the model once to write the thing, then use the thing. The second run costs nothing and behaves the same way every time.

Loops that finish

toast exits 1 when its answer contains DONE, and 0 otherwise. jam's while repeats until its command exits nonzero. Put those together and the loop stops when the work is finished rather than after a fixed count.

# jam. no do, no done.
๐Ÿž while toast server.c "improve until production ready. print DONE when finished."

# same thing, but never more than 20 passes
๐Ÿž 20 while toast server.c "keep improving"

# run something exactly N times
๐Ÿž 5 times toast "another headline for the launch post"
Cap it the first time. Use 20 while until you trust a prompt to terminate. An uncapped loop against a hosted model is an uncapped bill.

And do not use the exit code to mean "found a problem" โ€” it means "said DONE". For a pass/fail gate, ask for a marker and grep for it:

git diff --cached | toast "review. print BLOCK on the last line if anything is serious." \
  | tee /dev/stderr | grep -q BLOCK && exit 1
exit 0

Web

# fetch, reduce with jq, then ask
curl -fsS https://api.github.com/repos/torvalds/linux/commits | \
  jq -r '.[].commit.message' | \
  toast "summarise these kernel changes"

# render to text first โ€” do not send HTML to a model, you pay for the markup
curl -fsS https://news.ycombinator.com | \
  lynx -dump -stdin | \
  toast "what are the top three themes on the front page?"

jq -r rather than jq, so you send the message text rather than a quoted JSON string of it. Both work; one costs less.

Big data

# sample, ask about the sample, apply to everything
shuf -n 50 events.jsonl | toast "what shape is this data, and what looks wrong?"

# let the model write the filter, then run it over the lot
head -n 5 events.jsonl | toast "jq filter for events where status >= 500. filter only." > f.jq
cat f.jq
jq -f f.jq events.jsonl | wc -l

Writing a book

The naive version writes twelve chapters that have never heard of each other. This one carries state forward, which is the difference between a draft and twelve unrelated short stories.

#!/bin/bash
set -eu
TITLE="The Last Algorithm"
mkdir -p book && cd book
ito init

cat > .crumbs <<EOF
Sci-fi thriller about AI. Dark, fast-paced, close third person.
Never explain the technology. The reader is smarter than the narrator.
EOF

echo "$TITLE" | toast "create a 12 chapter outline" > outline.md
ito log "outline for $TITLE"

for i in $(seq -w 1 12); do
  # the brief sees the outline AND everything written so far
  cat outline.md so-far.md 2>/dev/null | \
    toast "write chapter $i, about 2000 words. continue from what exists." > ch$i.md

  # keep a running synopsis so the context does not grow without bound
  cat ch$i.md | toast "one paragraph: what happened, who knows what now" >> so-far.md

  ito log "chapter $i drafted"
done

# seq -w zero-pads, so ch01..ch12 sort correctly. `ch*.md` would put ch10 before ch2.
cat ch*.md > draft.md
wc -w draft.md
seq -w is not decoration. With ch1.md โ€ฆ ch12.md, the glob sorts lexically and cat ch*.md gives you chapters 1, 10, 11, 12, 2, 3โ€ฆ Zero-padding fixes it. This is the most common way a generated book comes out shuffled.

Because every step logged to ito, you can ask what happened and go back:

ito history | toast "where did the plot change direction?"
ito undo                 # one step back
ito restore 4a91c07      # any moment

Content pipeline

#!/bin/bash
set -eu
NOTES="meeting_notes.txt"

toast "$NOTES" "write a technical blog post"            > blog.md
toast blog.md "convert to a 6-post thread. hook first."  > thread.txt
toast blog.md "summarise for a newsletter, casual"       > email.txt

# derive everything from blog.md, not from each other โ€” errors do not compound
wc -w blog.md thread.txt email.txt

Note the file argument instead of cat โ€ฆ |: hand toast a filename and it reads it, and the model gets the name too, which is context you would otherwise throw away.

Websites

echo "SaaS for dog walkers" | \
  toast "one self-contained HTML file: hero, features, pricing. no build step." > index.html

toast resume.txt "turn this into a portfolio page, one HTML file, dark" > portfolio.html

# then iterate against the real thing rather than re-generating from scratch
๐Ÿž 10 while toast index.html "tighten the copy. print DONE when there is nothing left to cut."

Agents

An agent is a loop that observes, decides and acts. The decision is non-deterministic, so validate it before acting on it โ€” that is the entire job.

#!/bin/bash
# organize.sh โ€” sort downloads into four buckets
set -eu
TARGET="${1:-./downloads}"

echo "Reply with exactly one word: docs, img, code, or other. Nothing else." > .persona

for f in "$TARGET"/*; do
  [ -f "$f" ] || continue

  C=$(basename "$f" | toast "categorise this filename" | tr -d '[:space:]')

  # the model is not trusted. only these four words are allowed through.
  case "$C" in
    docs|img|code|other) ;;
    *) echo "skip $f (model said: $C)" >&2; continue ;;
  esac

  mkdir -p "$TARGET/$C"
  mv -n "$f" "$TARGET/$C/"
done
What the case is for. Without it, a model that replies "This looks like a document!" gets you a directory named Thislookslikeadocument!, and a model that replies with nothing gets you mv "$f" "$TARGET//". Quote the glob, check the file exists, whitelist the answer, and use mv -n so nothing is overwritten.

Integrations

Slack

#!/bin/sh
# send-slack.sh โ€” reads stdin, posts a friendly version
toast "rewrite as a short friendly Slack message" \
  | jq -Rs '{text: .}' \
  | curl -fsS -X POST -H 'Content-Type: application/json' -d @- "$SLACK_WEBHOOK_URL"
jq -Rs, not string interpolation. Model output contains quotes, newlines and apostrophes. Building the JSON with -d "{\"text\": \"$MSG\"}" breaks on the first one, and a payload with a " in it can change the shape of the request.

SQL

# describe the schema, get a query, read it, then run it read-only
psql -d mydb -c '\d users' -c '\d orders' | \
  toast "postgres query: top 10 users by spend last month. SQL only." > q.sql

cat q.sql
psql -d mydb -U readonly -f q.sql
Connect as a read-only role. Generated SQL run on a writable connection is one hallucinated DELETE away from a bad afternoon. Give the model the schema, not the credentials.

cron

# monday 9am: your standup, from what you actually did
0 9 * * 1  cd /srv/app && ito history | toast "write my standup, first person, modest" | mail -s standup you@example.com
Under cron, stdin is /dev/null. That is a pipe rather than a terminal, so toast takes the batch path and reads it โ€” and gets nothing. Always pipe something in or pass a file. Use absolute paths; cron's PATH is minimal.

Many machines

One box holds one model. A room holds as many as you own. squawkd joins machines over LAN multicast with no broker and no configuration; squawk bot puts a model in the room as an ordinary participant.

# once per machine
squawkd &

# on the Mini and on the Studio โ€” USER sets the nick it posts under
USER=Paranoid squawk bot Paranoid
USER=Security squawk bot Security

# join from the laptop
squawk
dirk> auth.py rotates tokens inside a 30 second window. anything wrong?
Paranoid> two requests in that window both mint a token. the second wins.
Security> long enough to replay a stolen token once, too.

# from a script, on any machine
echo "deploy finished on arm64" | squawk

# across the internet, through your existing ssh access
squawk linuxtoaster.com

jam has a lighter version of the same bus for key/value work between machines:

๐Ÿž send build "green on arm64"
๐Ÿž listen build
# contract review, locally, so the document never leaves the building
toasted
pdftotext contract.pdf - | \
  toast "flag risky clauses, indemnification and termination. quote the clause."

# three readers, one document
toast --add Paranoid; toast --add Dev
for p in Paranoid Dev; do pdftotext contract.pdf - | $p "review this"; done | toast "integrate"

cat notes.txt | toast "draft a cease and desist, professional, no threats we cannot make"
For privileged material, run a local provider and start the daemon as toastd -l. The log stays on your machine and never comes to us, which is what gives you a record you can show someone.

Creative & ad

echo "Energy drink for programmers" | toast "10 product names, edgy"
echo "Sign up now" | toast "10 stronger CTAs, no exclamation marks"

# twenty variations, then let a different persona pick
๐Ÿž 20 times toast "one headline for the launch post" >> heads.txt
cat heads.txt | Paranoid "which of these overpromise? rank the rest."

Education

echo "Photosynthesis" | toast "45 minute lesson plan, 5th grade, with a hands-on section"
toast chapter.txt "5 multiple choice questions with an answer key"

# mark a stack of submissions, one file at a time, and keep the record
for f in submissions/*.txt; do
  toast "$f" "grade against rubric.md. score, then two lines of feedback." > "graded/$(basename "$f")"
done
ito log "graded week 4"

Interviewer

An interview needs turns, so this one belongs in chat mode. A single-shot toast "act as an interviewer" just prints an opening question and exits.

mkdir interview && cd interview
cat > .persona <<EOF
You are a senior backend engineer interviewing me. Ask one question at a time.
Wait for my answer. Follow up on anything vague. Do not give me the answer.
EOF

toast
> ready
> /exit

# the transcript is a file, so grade it afterwards
cat .chat | toast "evaluate my answers against the STAR method, score each 1-5"

Fun

# sales announcer (macOS). polls, so it terminates โ€” `tail -f | toast` never would.
while sleep 60; do
  tail -n 20 sales.log | toast "one enthusiastic sentence about these sales" | say
done

# roast your own directory
ls -al | toast "roast my directory"

# a running commentary on your own commits, from the room
git log --oneline -20 | squawk