Skip to content
KoishiAI
ไทย
← Back to contents
Chapter 6 / 12 · April 24, 2026

Advanced CLI and Automation

Agent SDK, headless mode, scripting and automating your flow

This guide is All Rights Reserved — free to read, copying/republication requires permission.

Chapter 6 | CLI technique and an AI Wiki / RAG for Claude Code Written for: developers at any level who want to use Claude Code professionally from the CLI

What the CLI is, and why it is powerful

The CLI (command line interface) means driving the computer by typing text commands rather than clicking a GUI. It sounds dated, but in a modern developer’s workflow the CLI beats a GUI in several ways, especially alongside Claude Code.

Why does the CLI matter in vibe coding?

Done in a GUIFaster with the CLI
Open file → copy → paste into Claudecat file.py | claude -p “explain”
Run tests → copy the error → paste into Claudenpm test 2>&1 | claude -p “fix the error”
Look at git diff → copy → paste into Claudegit diff | claude -p “review”
Repeat the same manual steps project by projectWrite a shell script that does it automatically
Read logs from a web UItail -f app.log | claude -p “monitor”
Constantly switch windowsEverything in one terminal

Terminals worth using

SystemRecommended terminalWhy
macOSiTerm2 + zsh + Oh My ZshSplit panes, plugin ecosystem, syntax highlighting
macOSWarp TerminalAI-native terminal, smart command history
LinuxKitty or AlacrittyFast, GPU-accelerated, configured with text
WindowsWindows Terminal + WSL2Full Linux commands
Any systemtmuxSession management, split panes, survives a disconnect

The Claude Code CLI — every flag and option worth knowing

Claude Code has a very complete CLI interface. Knowing every flag is what lets you get the most out of it.

Basic flags

claudeOpen interactive mode — chat back and forth with Claude
claude -p “prompt”One-shot mode — send a prompt, take the answer, exit immediately
claude —versionShow the current version of Claude Code
claude —helpShow help for every flag and option
claude —model [model]Pick a model by alias: opus, sonnet, haiku, fable
claude —max-tokens [n]Cap the response length (1-8192 tokens)
claude —temperature [0-1]How creative the response is (0=literal, 1=inventive)

Flags for input

CommandWhat it does
cat file | claude -p ”…”Send content through stdin (pipe), capped at 10MB
claude -p ”…” < input.txtRead input from a file
claude —add-dir [path]Give Claude access to another folder
claude —system-prompt ”…”Set a system prompt in place of CLAUDE.md
claude —continueResume the most recent session
claude —resume [session-id]Open a specific session by ID

Flags for output

claude -p ”…” —output-format jsonOutput as JSON (good for scripting)
claude -p ”…” —output-format textOutput as plain text (default)
claude -p ”…” —output-format stream-jsonStream JSON chunk by chunk
claude -p ”…” > output.txtSave the output to a file
claude -p ”…” | tee output.txtShow it on screen and save it at the same time

Correction (updated 2026-09) An earlier edition of this guide listed the flags --file, --print-stats and --no-color. None of the three exists in the CLI reference. The correct approach is to pipe the file in, or to name the file path directly in the prompt text.

Flags for automation

FlagWhat it does
claude -p "..." --allowedTools "Bash,Read"Allow only the tools named
claude -p "..." --disallowedTools "Bash(rm *)"A deny rule that is a bare tool name removes the tool entirely; a rule with parentheses forbids only what matches
claude -p "..." --permission-mode dontAskLock CI down so anything outside an allow rule is refused
claude --add-dir ../apps ../libAdd working directories
claude --model haikuPick a model by short name sonnet opus haiku fable, or by full name
claude --bare -p "..."Skip loading hooks/skills/MCP/CLAUDE.md so every machine gives the same result
claude --verboseShow extra debug information

Real flag usage

# 1. Analyse a single file one-shot (pipe the file contents in)
cat src/auth.ts | claude -p "summarise the important functions in this file"

# 2. Pick a cheaper model for an easy job, naming the path in the prompt
claude --model haiku -p "fix the typos in README.md" --allowedTools "Read,Edit"

# 3. JSON output with an enforced shape
claude -p "extract every function name from src/utils.ts" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'

# 4. Run a batch analysis across many files
for f in src/**/*.ts; do
  echo "=== $f ==="
  cat "$f" | claude --bare -p "any security issue? answer YES/NO with the reasoning"
done

# 5. Use it in CI with a tight allowlist
claude --bare -p "run the tests and report the result" \
  --permission-mode dontAsk \
  --allowedTools "Bash(npm test)" "Read" \
  --output-format json > test-report.json

Do not make --dangerously-skip-permissions the default in CI. The official documentation states that this mode belongs only in a container, a VM or a sandbox runtime, and that on Linux and macOS it should run as a non-root user. For ordinary CI, --permission-mode dontAsk paired with an explicit allowlist is the better path. See chapter 11 for the detail.

Pipes and stdin — the heart of CLI power

The pipe (|) is what makes the CLI so powerful. It feeds one command’s output into the next command’s input, letting you build complex workflows with no intermediate files.

Pipe patterns you will use often

Pattern 1: File Analysis
# Analyse various files
cat src/api/users.ts | claude -p "find the N+1 query"
 
# Send several files together
cat src/lib/auth.ts src/middleware/auth.ts | \
  claude -p "analyse the whole authentication flow"
 
# Send only a range of lines
sed -n "45,80p" src/payments.ts | \
  claude -p "explain the logic in these lines"
 
# Send through head/tail
tail -100 src/server.ts | \
  claude -p "is there anything wrong in the last 100 lines"
Pattern 2: Error Analysis
# Analyse errors from tests
npm test 2>&1 | claude -p "fix the failing tests"
 
# Catch only the errors
npm run build 2>&1 | grep -E "ERROR|error" | \
  claude -p "explain these errors and how to fix them"
 
# TypeScript errors
npx tsc --noEmit 2>&1 | \
  claude -p "fix all the TypeScript errors, ordered by priority"
 
# ESLint
npx eslint src/ --format json 2>&1 | \
  claude -p "summarise the linting issues and the fixes, most important first"
 
# A runtime error from the server log
tail -50 logs/error.log | \
  claude -p "analyse the pattern in these errors and find the root cause"
Pattern 3: Git Integration
# Write a commit message
git diff --staged | \
  claude -p "write a commit message following Conventional Commits"
 
# Review before a PR
git diff main...HEAD | \
  claude -p "review these code changes like a senior developer"
 
# PR description
git log main..HEAD --oneline | \
  claude -p "write a PR description from these commits"
 
# Work out which commit changed what
git log --oneline -20 | \
  claude -p "summarise how these 20 commits changed the system"
 
# Analyse a merge conflict
git diff --diff-filter=U | \
  claude -p "resolve all of these merge conflicts sensibly"
Pattern 4: System & Process
# Analyse the processes eating the most CPU
ps aux | sort -k3 -rn | head -20 | \
  claude -p "which processes are worrying? explain and advise"
 
# Analyse disk usage
du -sh * | sort -rh | head -20 | \
  claude -p "what is taking the most space, and what should be done"
 
# Look at network connections
netstat -an | grep ESTABLISHED | \
  claude -p "are any of these connections suspicious"
 
# Analyse docker
docker ps --format json | \
  claude -p "which containers are using an abnormal amount of resource"
 
# Package vulnerabilities
npm audit --json | \
  claude -p "summarise the vulnerabilities by severity and how to fix them"
Pattern 5: Data Processing
# Analyse a CSV
cat sales-data.csv | \
  claude -p "summarise the top 10 products and the revenue trends"
 
# Pull data from a JSON API and analyse it
curl -s https://api.myapp.com/stats | \
  claude -p "explain these metrics and identify anomalies"
 
# Convert format
cat data.json | \
  claude -p "convert this JSON to CSV format with headers"
 
# Analyse log patterns
grep "ERROR" logs/app.log | \
  sort | uniq -c | sort -rn | head -20 | \
  claude -p "which errors happen most often, which should be fixed first"

Chaining commands — several pipes together

Chaining pipes lets you build a complex workflow on a single line:

# A complex chain: find dead code and propose deletions
find src/ -name "*.ts" -exec grep -l "export" {} \; | \
  xargs grep -L "import.*from" | \
  claude -p "these files export but nobody imports them; they may be dead code"
 
# Find the longest-standing TODO comments
git log --all --format="%ai %H" | \
  head -100 | \
  while read date hash; do
    git grep -l "TODO" $hash 2>/dev/null | head -5
  done | sort | uniq | \
  claude -p "which TODOs have probably been sitting the longest"
 
# Analyse the performance of API endpoints
cat logs/access.log | \
  awk "{print $7, $10}" | \
  sort -k2 -rn | head -20 | \
  claude -p "which endpoints have the highest response times, analyse the pattern"

Shell scripting with Claude Code

Once you know pipes, the next step is writing shell scripts that use Claude as one part of an automation workflow.

A basic script: daily code review

A script that runs every morning and reviews the code committed yesterday:

#!/bin/bash
# daily-review.sh
# run: chmod +x daily-review.sh && ./daily-review.sh
 
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d 2>/dev/null || date -v-1d +%Y-%m-%d)
REPORT_FILE="reports/review-$(date +%Y-%m-%d).md"
 
mkdir -p reports
 
echo "# Daily Code Review Report: $(date)" > "$REPORT_FILE"
echo "" >> "$REPORT_FILE"
 
# Look at yesterday's commits
COMMITS=$(git log --oneline --after="$YESTERDAY 00:00" --before="$YESTERDAY 23:59")
 
if [ -z "$COMMITS" ]; then
  echo "No commits yesterday" >> "$REPORT_FILE"
  exit 0
fi
 
echo "## Commits" >> "$REPORT_FILE"
echo "$COMMITS" >> "$REPORT_FILE"
echo "" >> "$REPORT_FILE"
 
# Analyse the code changes
echo "## Code Analysis" >> "$REPORT_FILE"
git diff HEAD~$(echo "$COMMITS" | wc -l)..HEAD | \
  claude -p "review all of yesterday's code changes
            covering: quality, security, performance
            give a summary and action items" \
  >> "$REPORT_FILE"
 
# Send the report to Slack
REPORT_CONTENT=$(cat "$REPORT_FILE")
curl -X POST "$SLACK_WEBHOOK" \
  -H "Content-Type: application/json" \
  -d "{"text": "$REPORT_CONTENT"}"
 
echo "Report saved: $REPORT_FILE"

Script: automatic commit messages

A hook that writes the commit message from the staged changes:

#!/bin/bash
# .git/hooks/prepare-commit-msg
# install: chmod +x .git/hooks/prepare-commit-msg
 
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
 
# Only run on a fresh commit (not merge, squash, etc.)
if [ "$COMMIT_SOURCE" != "" ]; then
  exit 0
fi
 
# See what is staged
DIFF=$(git diff --staged)
if [ -z "$DIFF" ]; then
  exit 0
fi
 
# Let Claude write the message
echo "🤖 Generating the commit message..."
 
GENERATED_MSG=$(echo "$DIFF" | claude -p \
  "write a commit message in Conventional Commits format
   format: type(scope): description
   types: feat|fix|refactor|docs|test|chore|perf
   use English, no more than 72 characters on the first line
   if there is more detail, leave a blank line then add bullet points
   answer with the commit message only, no extra explanation" \
  --model haiku)
 
# Put the generated message into the commit file
echo "$GENERATED_MSG" > "$COMMIT_MSG_FILE"
 
echo "✅ Commit message ready (editable in your editor)"

Script: batch file processing

Analyse or transform many files at once:

#!/bin/bash
# batch-analyze.sh — analyse every Python file in the project
 
OUTPUT_DIR="analysis"
mkdir -p "$OUTPUT_DIR"
 
TOTAL=0
ISSUES=0
 
# Analyse every .py file
find src/ -name "*.py" | while read file; do
  BASENAME=$(basename "$file" .py)
  OUTPUT="$OUTPUT_DIR/$BASENAME-analysis.md"
 
  echo "Analysing: $file"
 
  RESULT=$(cat "$file" | claude -p \
    "analyse this file:
     1. any obvious bug?
     2. any security issue?
     3. can performance be improved?
     answer as JSON: {bugs: [], security: [], performance: []}",
    --output-format json)
 
  echo "## $file" > "$OUTPUT"
  echo "$RESULT" >> "$OUTPUT"
 
  # Count the files with problems
  HAS_ISSUES=$(echo "$RESULT" | python3 -c
    "import json,sys; d=json.load(sys.stdin)
    print(1 if any(d.values()) else 0)")
 
  TOTAL=$((TOTAL + 1))
  if [ "$HAS_ISSUES" = "1" ]; then
    ISSUES=$((ISSUES + 1))
  fi
done
 
echo "Summary: $ISSUES/$TOTAL files have problems"
echo "See the detail in $OUTPUT_DIR/"

Script: automatic environment setup

A script for setting up a new developer environment:

#!/bin/bash
# setup-dev.sh — set up the environment for a new developer
 
echo "🚀 Setup Development Environment"
 
# Check dependencies
check_dependency() {
  if ! command -v $1 &> /dev/null; then
    echo "❌ $1 is not installed"
    return 1
  fi
  echo "✅ $1 ready"
}
 
check_dependency node || exit 1
check_dependency npm || exit 1
check_dependency git || exit 1
 
# Read the README and ask Claude whether there are any special steps
if [ -f README.md ]; then
  SETUP_STEPS=$(cat README.md | claude -p \
    "extract every setup step from this README
     as a numbered list of commands to run",
    --model haiku)
  echo "📋 Setup steps from the README:"
  echo "$SETUP_STEPS"
fi
 
# Install packages
npm install
 
# Check .env is complete
if [ -f .env.example ] && [ ! -f .env ]; then
  MISSING_VARS=$(diff <(grep -oP "^[A-Z_]+" .env.example) \
    <(grep -oP "^[A-Z_]+" .env 2>/dev/null) | \
    grep "^<" | sed "s/< //")
 
  if [ -n "$MISSING_VARS" ]; then
    echo "$MISSING_VARS" | claude -p \
      "these environment variables are missing from .env
       explain what each one is and where to get it"
  fi
fi
 
echo "✅ Setup complete!"

tmux — managing the terminal like a professional

tmux (terminal multiplexer) lets you open several terminals at once in one window, and, importantly, the session survives closing the terminal, so Claude Code sessions stay alive.

Installing tmux

# macOS
brew install tmux
 
# Ubuntu/Debian
sudo apt install tmux
 
# Check
tmux --version

The tmux commands you will use most with Claude Code

tmux new -s claudeCreate a new session named “claude”
tmux attach -t claudeReturn to an existing session
tmux lsList every session
Ctrl+b dDetach from the session (it keeps running)
Ctrl+b %Split the pane vertically (side by side)
Ctrl+b “Split the pane horizontally (top/bottom)
Ctrl+b [arrow]Move between panes
Ctrl+b zZoom the current pane (full screen)
Ctrl+b [Scroll mode (move with the arrow keys)
Ctrl+b cCreate a new window
Ctrl+b [0-9]Switch windows

The layout that works best with Claude Code:

# Build the layout automatically with a script
#!/bin/bash
# claude-layout.sh
 
SESSION="dev"
PROJECT=$(basename $(pwd))
 
# Create the session if it does not exist
tmux new-session -d -s $SESSION -n "main"
 
# Window 1: Claude Code (left 60%) + Terminal (right 40%)
tmux split-window -h -p 40
tmux select-pane -t 0
tmux send-keys "claude" Enter
tmux select-pane -t 1
 
# Window 2: Editor
tmux new-window -n "editor"
tmux send-keys "code ." Enter
 
# Window 3: Logs (split top/bottom)
tmux new-window -n "logs"
tmux split-window -v -p 30
tmux select-pane -t 0
tmux send-keys "npm run dev" Enter
tmux select-pane -t 1
tmux send-keys "tail -f logs/error.log" Enter
 
# Window 4: Git
tmux new-window -n "git"
 
# Back to the first window
tmux select-window -t 0
 
# Attach
tmux attach -t $SESSION

Sending commands to Claude in tmux automatically

An advanced trick: send text to another tmux pane automatically, so a script can hand Claude a prompt without you typing it:

# Send a prompt to the Claude pane
tmux send-keys -t dev:main.0 "explain this error: $(cat error.log | tail -5)" Enter
 
# Script: ask Claude automatically whenever a test fails
#!/bin/bash
# auto-debug.sh
 
npm test 2>&1 | tee /tmp/test-output.txt
 
if [ ${PIPESTATUS[0]} -ne 0 ]; then
  ERROR=$(tail -20 /tmp/test-output.txt)
  # Send the error to the Claude pane in tmux
  tmux send-keys -t dev:claude "$(echo $ERROR | claude -p "fix these test failures")" Enter
fi

The AI Wiki — a complete guide

An AI Wiki is a documentation system designed so that Claude reads it and understands the context immediately. This section builds a genuinely usable wiki from scratch.

How an AI Wiki differs from ordinary documentation

Ordinary documentation
Written for human readers
Uses narrative writing
Explains by telling a story
No particular structure
Claude may get confused about the context
AI Wiki
Written for Claude and for people
Uses a structured format
Clear, actionable information
Has headings, tables, examples
Claude understands the context immediately

Building an AI Wiki from scratch — step by step

1Analyse the project and create an index
Have Claude analyse the project and say which docs are needed
> Analyse this entire project:
  @package.json @prisma/schema.prisma @src/
 
  Create a suitable AI Wiki structure:
  - say which doc files are needed
  - explain what each doc file should contain
  - create docs/README.md as the index
 
  Index format:
  | File | Contents | Read before |
  |------|---------|-----------|
  | architecture.md | ... | everything |
2Create the architecture overview
Explain the big picture of the system
# docs/architecture/overview.md
# System Architecture Overview
 
## System Purpose
An e-commerce platform selling digital products
Users: 10,000 DAU, peak 50,000 concurrent
 
## Technology Stack
| Layer | Technology | Version | Why |
|-------|-----------|---------|-----|
| Frontend | Next.js App Router | 14.x | SSR + RSC |
| Backend | Next.js Server Actions | 14.x | Type-safe |
| Database | PostgreSQL | 16 | ACID, jsonb support |
| ORM | Prisma | 5.x | Type-safe, migration |
| Cache | Redis | 7 | Session, rate limit |
| Auth | NextAuth.js | 5 beta | OAuth + credentials |
| Deploy | Vercel + AWS | - | Frontend + DB |
 
## Data Flow
Browser → CDN → Vercel Edge → Next.js → Prisma → PostgreSQL

                     Redis Cache
 
## Key Design Decisions
- Server Actions instead of a REST API (40% less boilerplate)
- PostgreSQL jsonb for product metadata (flexible schema)
- Redis sessions rather than JWT (revokable, stateful)
 
## Performance Targets
- API P95: < 200ms
- Page LCP: < 2.5s on 4G
- Database query: < 50ms
3Write the business rules
The business rules Claude must know before implementing
# docs/domain/business-rules.md
# Business Rules Reference
 
## Pricing Engine
 
### Priority Order (very important: apply in this order)
1. Original price
2. Product sale price (if any)
3. Coupon discount (off the price from step 2)
4. Member discount (off the price from step 3)
5. Bundle discount (off the price from step 4)
6. Floor price (the lowest price allowed = cost + 10%)
 
### Coupon Rules
| Rule | Detail |
|------|--------|
| 1 coupon per order | Several coupons cannot be combined |
| Must match the user tier | A Silver coupon works only for Silver and above |
| Minimum spend | Some coupons set a minimum order amount |
| Single use | Per user, per coupon code |
 
### Member Tiers
| Tier | Lifetime spend | Discount | Extra benefit |
|------|------------|--------|-----------------|
| Bronze | < 5,000 | 0% | - |
| Silver | 5,000-19,999 | 3% | Free shipping |
| Gold | 20,000-49,999 | 5% | Priority support |
| Platinum | 50,000+ | 8% | Early access |
 
## Order State Machine

pending → confirmed → processing → shipped → delivered ↓ ↓ cancelled cancelled

 
### State Transition Rules
- pending → confirmed: needs payment confirmation
- confirmed → cancelled: automatic refund within 3 business days
- shipped → delivered: needs confirmation from tracking or a manual step
- forbidden: delivered → anything (final state)
 
## Inventory Rules
- Stock < 10 → send an admin alert
- Stock = 0 → hide the add to cart button
- Reserved stock → locked for 15 minutes on add to cart
4Write the API reference
Document every endpoint
# docs/api/endpoints.md
# API Endpoints Reference
 
## Authentication
Every endpoint not marked Public requires a session cookie
or Authorization: Bearer {token}
 
## Products
 
### GET /api/products
**Purpose**: fetch the product list with pagination and filters
**Auth**: Public
**Query params**:
| param | type | required | description |
|-------|------|----------|-------------|
| page | number | no | default: 1 |
| limit | number | no | default: 20, max: 100 |
| category | string | no | category slug |
| q | string | no | search term |
| sort | string | no | price_asc, price_desc, newest |
 
**Response 200**:
```json
{
  "products": [{ "id": "...", "name": "...", "price": 0 }],
  "pagination": { "total": 0, "page": 1, "totalPages": 0 }
}

Error cases:

statuscodewhen
400INVALID_PARAMSlimit > 100
404CATEGORY_NOT_FOUNDthe category does not exist

POST /api/orders

Purpose: create a new order Auth: Required Body:

{
  "items": [{ "productId": "...", "quantity": 1 }],
  "couponCode": "SAVE10",
  "shippingAddressId": "..."
}

Important: the system locks inventory the moment the request arrives If payment does not succeed within 15 minutes, the lock is released automatically


| 5 | Write a runbook <br> A guide to the problems that come up most |
|---|---|

docs/runbooks/common-issues.md

Common Issues Runbook

Redis Connection Failed

Symptoms: login fails, sessions disappear Steps:

  1. Check the Redis status: redis-cli ping
  2. On a NOAUTH error: check REDIS_PASSWORD in .env
  3. On Connection refused: check the Redis service
    • AWS: look at the ElastiCache status in the console
    • Local: docker start redis
  4. Flush the sessions if you must: redis-cli FLUSHDB (everyone has to log in again)

Stripe webhook not firing

Symptoms: orders stuck at “pending” even after payment Steps:

  1. Open the Stripe Dashboard → Webhooks → View attempts
  2. Check the endpoint URL is correct
  3. Check STRIPE_WEBHOOK_SECRET matches
  4. Read the server logs: grep "stripe" logs/app.log
  5. Retry the webhook from the Stripe Dashboard

Database Connection Pool Exhausted

Symptoms: “too many connections” error Steps:

  1. Look at the current connections: SELECT count(*) FROM pg_stat_activity;
  2. Look for long-running queries: SELECT pid, now()-query_start, query FROM pg_stat_activity WHERE state="active" ORDER BY 2 DESC LIMIT 10;
  3. Kill the stuck query: SELECT pg_terminate_backend(pid);
  4. Long-term: raise the connection pool limit in DATABASE_URL

### Using the AI Wiki with the Claude Code CLI

Once the wiki is ready, here is how you actually use it with the Claude Code CLI:

1. Send a wiki file directly

cat docs/domain/business-rules.md |
claude -p “implement coupon validation following these business rules”

2. Combine the wiki with source code

cat docs/api/endpoints.md src/app/api/orders/route.ts |
claude -p “check whether the implementation matches the API spec”

3. Search the wiki, then send the result to Claude

use grep to find the relevant part

grep -A 20 ”## Coupon Rules” docs/domain/business-rules.md |
claude -p “implement validateCoupon following these rules”

4. Send several wiki files

cat docs/architecture/overview.md
docs/domain/business-rules.md
docs/api/endpoints.md |
claude -p “design a Notification system consistent with the architecture”

5. A script that searches the wiki automatically

#!/bin/bash

wiki-search.sh [keyword] [question]

KEYWORD=$1 QUESTION=$2

Find the relevant sections in the wiki

RELEVANT=$(grep -r -l “$KEYWORD” docs/ |
xargs grep -A 30 “$KEYWORD” 2>/dev/null)

if [ -z “$RELEVANT” ]; then echo “Nothing found about: $KEYWORD” exit 1 fi

Send it to Claude

echo “Context from the wiki:” > /tmp/wiki-context.txt echo “$RELEVANT” >> /tmp/wiki-context.txt echo “Question: $QUESTION” >> /tmp/wiki-context.txt

cat /tmp/wiki-context.txt | claude -p “$QUESTION”

Usage:

./wiki-search.sh “coupon” “implement coupon validation”


### Auto-updating the wiki from code

A script that generates API docs from the source automatically, keeping the wiki from going stale:

#!/bin/bash

update-wiki.sh — update the wiki after implementing a feature

run: ./update-wiki.sh “notification system”

FEATURE=$1

echo “Updating the wiki for feature: $FEATURE”

1. Generate API docs from the new route files

ROUTES=$(find src/app/api -name “route.ts” -newer docs/api/endpoints.md)

if [ -n “$ROUTES” ]; then echo “New route files found, updating the API docs…” cat $ROUTES | claude -p
“add documentation for these new routes use the same format as @docs/api/endpoints.md answer with markdown for the new endpoints only”
>> docs/api/endpoints.md fi

2. Update the architecture overview

git diff —staged —name-only | grep -E “prisma|schema” | while read f; do cat “$f” | claude -p
“what changed in the database schema? update the relevant part of docs/database/schema.md”
| tee -a docs/database/schema.md > /dev/null done

3. Record the decision

DATE=$(date +%Y-%m-%d) CHANGES=$(git diff —staged —stat)

cat >> docs/architecture/decisions.md << EOF

ADR-$(date +%Y%m%d): $FEATURE

Date: $DATE Status: Accepted

Changes

$CHANGES EOF

echo ”✅ Wiki updated” git add docs/ git commit -m “docs: update wiki for $FEATURE”


### RAG with the CLI — intelligent search for developers

This section builds a RAG system integrated into the CLI workflow, so Claude can search a knowledge base and use what it finds directly from the command line.

### The RAG architecture for CLI use

A RAG system for the CLI has to respond fast, be easy to use, and integrate with shell commands:

System structure

knowledge-base/ ← all the material docs/ ← the AI Wiki src/ ← source code external/ ← third-party docs | ↓ index (built once, updated by a Git hook) vector-db/ ← the stored embeddings index.faiss ← FAISS vector index metadata.json ← source file information | ↓ query rag-server/ ← FastAPI server main.py ← API endpoint | ↓ HTTP CLI tools ← the shell scripts the user runs ask.sh ← ask the knowledge base search.sh ← semantic search update.sh ← update the index


### Setting up a minimal RAG server

Build a RAG server that responds fast enough for CLI use:

requirements.txt

fastapi==0.109.0 uvicorn==0.27.0 llama-index==0.10.0 llama-index-llms-anthropic==0.1.0 faiss-cpu==1.7.4 sentence-transformers==2.3.1

install

pip install -r requirements.txt

rag_server.py

from fastapi import FastAPI, HTTPException from pydantic import BaseModel from llama_index.core import VectorStoreIndex, StorageContext from llama_index.core import load_index_from_storage from llama_index.llms.anthropic import Anthropic from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.core import Settings import os, json

app = FastAPI(title=“Project RAG API”)

Configure the LLM and the embeddings

Settings.llm = Anthropic( model=“claude-haiku-4-5”, # Haiku for speed api_key=os.environ[“ANTHROPIC_API_KEY”] ) Settings.embed_model = HuggingFaceEmbedding( model_name=“BAAI/bge-small-en-v1.5” # a small model, faster )

Load the index

try: storage_ctx = StorageContext.from_defaults(persist_dir=”./vector-db”) index = load_index_from_storage(storage_ctx) print(”✅ Index loaded”) except: print(“⚠️ No index yet, run build_index.py first”) index = None

class Query(BaseModel): question: str top_k: int = 5 include_sources: bool = True

class SearchQuery(BaseModel): query: str top_k: int = 10

@app.post(“/ask”) async def ask(q: Query): """Ask a question; Claude looks in the knowledge base before answering""" if not index: raise HTTPException(500, “Index not initialized”)

engine = index.as_query_engine(similarity_top_k=q.top_k)
response = engine.query(q.question)

result = {"answer": str(response)}
if q.include_sources:
    result["sources"] = [
        {
            "file": node.metadata.get("file_path", "unknown"),
            "score": round(node.score, 3),
            "snippet": node.text[:200]
        }
        for node in response.source_nodes
    ]
return result

@app.post(“/search”) async def search(q: SearchQuery): """Find the relevant chunks without generating an answer""" if not index: raise HTTPException(500, “Index not initialized”)

retriever = index.as_retriever(similarity_top_k=q.top_k)
nodes = retriever.retrieve(q.query)

return {
    "results": [
        {
            "file": n.metadata.get("file_path"),
            "score": round(n.score, 3),
            "text": n.text
        }
        for n in nodes
    ]
}

@app.get(“/health”) async def health(): return {“status”: “ok”, “index_ready”: index is not None}

run: uvicorn rag_server:app —port 8765 —reload

build_index.py — build the index from the knowledge base

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader from llama_index.core.node_parser import SentenceSplitter from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.core import Settings import os

Settings.embed_model = HuggingFaceEmbedding( model_name=“BAAI/bge-small-en-v1.5” )

Load the documents

print(”📚 Loading documents…”) loader = SimpleDirectoryReader( input_files=[], # or input_dir=”.”, recursive=True, required_exts=[“.md”, “.txt”, “.ts”, “.py”, “.js”], exclude=[“node_modules”, “.git”, “dist”, “.next”, “vector-db”] ) documents = loader.load_data() print(f”Loaded {len(documents)} files”)

Split the text into chunks

splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)

Build the index

print(”🔨 Building index…”) index = VectorStoreIndex.from_documents( documents, transformations=[splitter], show_progress=True )

Save

index.storage_context.persist(persist_dir=”./vector-db”) print(f”✅ Index saved to ./vector-db”)


### CLI tools for using RAG

Write shell scripts that are easy to use in every scenario:

### Script 1: ask — put a question to the knowledge base

#!/bin/bash

ask.sh — ask the knowledge base

usage: ./ask.sh “how to implement payment retry”

RAG_URL=“http://localhost:8765” QUESTION=”$*”

if [ -z “$QUESTION” ]; then echo “Usage: ask.sh [question]” exit 1 fi

Check the server is running

if ! curl -s “$RAG_URL/health” | grep -q “ok”; then echo ”❌ The RAG server is not running. Run: uvicorn rag_server:app —port 8765” exit 1 fi

Send the question

RESPONSE=$(curl -s -X POST “$RAG_URL/ask”
-H “Content-Type: application/json”
-d ”{“question”: “$QUESTION”, “top_k”: 5}“)

Show the result

echo ”=== Answer ===” echo “$RESPONSE” | python3 -c “import json,sys; d=json.load(sys.stdin); print(d[‘answer’])”

echo "" echo ”=== Sources ===” echo “$RESPONSE” | python3 -c ” import json,sys d=json.load(sys.stdin) for s in d.get(“sources”, []): print(f” [{s[“score”]:.2f}] {s[“file”]}”) ”


### Script 2: search — find the relevant chunks

#!/bin/bash

search.sh — semantic search (no generated answer)

usage: ./search.sh “authentication middleware”

RAG_URL=“http://localhost:8765” QUERY=”$*” TOP_K=${TOP_K:-10}

RESPONSE=$(curl -s -X POST “$RAG_URL/search”
-H “Content-Type: application/json”
-d ”{“query”: “$QUERY”, “top_k”: $TOP_K}”)

echo ”🔍 Search results: $QUERY” echo ""

echo “$RESPONSE” | python3 -c ” import json, sys d = json.load(sys.stdin) for i, r in enumerate(d[“results”], 1): print(f”{i}. [{r[“score”]:.3f}] {r[“file”]}”) print(f” {r[“text”][:150]}…”) print() ”


### Script 3: rag-claude — search, then hand the result to Claude Code

#!/bin/bash

rag-claude.sh — search for material, then hand it to Claude to implement

usage: ./rag-claude.sh “payment retry” “implement payment retry logic”

SEARCH_TERM=“$1” CLAUDE_PROMPT=“$2” RAG_URL=“http://localhost:8765

if [ -z “$SEARCH_TERM” ] || [ -z “$CLAUDE_PROMPT” ]; then echo “Usage: rag-claude.sh [search_term] [claude_prompt]” echo “Example: rag-claude.sh coupon implement coupon validation” exit 1 fi

Search for the relevant material

echo ”🔍 Searching for material about: $SEARCH_TERM” CONTEXT=$(curl -s -X POST “$RAG_URL/search”
-H “Content-Type: application/json”
-d ”{“query”: “$SEARCH_TERM”, “top_k”: 8}” |
python3 -c ” import json, sys d = json.load(sys.stdin) for r in d[“results”]: print(f”--- {r[“file”]} (score: {r[“score”]:.2f}) ---”) print(r[“text”]) print() “)

Send it to Claude along with the context

echo ”🤖 Sending to Claude with the context…” FULL_PROMPT=“Context from the knowledge base:\n$CONTEXT\n\nTask: $CLAUDE_PROMPT”

echo -e “$FULL_PROMPT” | claude -p “$CLAUDE_PROMPT”


### Integration: RAG + Claude Code + tmux

Here is the workflow that puts it all together, as realistically as possible:

Set up a tmux layout for the RAG workflow

#!/bin/bash

rag-workspace.sh

SESSION=“rag-dev”

Window 1: Claude Code (left) + RAG terminal (right)

tmux new-session -d -s $SESSION -n “main” tmux split-window -h -p 35

Left: Claude Code

tmux select-pane -t 0 tmux send-keys “claude” Enter

Right: RAG tools

tmux select-pane -t 1 tmux send-keys “echo Ready for RAG queries” Enter

Window 2: RAG Server

tmux new-window -n “rag-server” tmux send-keys “uvicorn rag_server:app —port 8765 —reload” Enter

Window 3: Build/Watch

tmux new-window -n “watch” tmux send-keys “npm run dev” Enter

tmux select-window -t 0 tmux attach -t $SESSION

─── The real working workflow ───

Right terminal: search before implementing

./ask.sh “how to handle order cancellation under the business rules”

What comes back: context from docs/domain/business-rules.md

The explanation: refund policy, state machine rules, etc.

Take that context into Claude Code (left terminal)

./rag-claude.sh “order cancellation”
“implement the cancelOrder function in src/server/actions/order.ts”

Claude gets the full context and implements to the business rules


### A real example: a whole day of RAG + CLI workflow

Here is a genuine working day using RAG + CLI at every step:

|   | 9:00 — start the session |
|---|---|

Open the workspace

./rag-workspace.sh

Read yesterday’s handoff

./ask.sh “summarise the outstanding work from yesterday’s handoff”

See what today holds

cat .claude/handoffs/latest.md | claude -p “summarise what has to be done today”


|   | 10:00 — implement the feature |
|---|---|

Find the material before implementing

./search.sh “notification system architecture”

Ask a specific question

./ask.sh “what is the notification delivery order, is there a priority”

Implement with the context from RAG

./rag-claude.sh “notification”
“create the createNotification function in src/services/notification.ts”

Run the tests straight away

npm test — —testPathPattern=notification 2>&1 |
claude -p “fix the failing tests”


|   | 14:00 — code review |
|---|---|

Review the code written this morning

git diff main…HEAD |
claude -p “review this code like a senior developer, focus on security + logic”

Check it is consistent with the codebase

./search.sh “similar pattern to notification”

Update the wiki

./update-wiki.sh “notification system”


|   | 17:00 — wrap up |
|---|---|

Create the handoff

cat > .claude/handoffs/$(date +%Y-%m-%d).md << EOF $(git diff main…HEAD —stat) EOF

cat .claude/handoffs/$(date +%Y-%m-%d).md |
claude -p “write a complete handoff document for tomorrow”

Update the index if there are new files

python3 build_index.py

Commit

git diff —staged | claude -p “write a commit message” |
git commit -F -


### Advanced CLI patterns

### Pattern 1: Parallel processing

Run several Claude instances at once for batch work:

#!/bin/bash

parallel-analyze.sh — analyse files in parallel

MAX_JOBS=4 # how many Claude instances at once RESULTS_DIR=“analysis” mkdir -p “$RESULTS_DIR”

analyze_file() { local file=$1 local output=“$RESULTS_DIR/$(basename $file).analysis”

cat “$file” | claude -p
“analyse the security issues in 3 bullet points”
—model haiku > “$output”

echo ”✓ $file” }

export -f analyze_file

Run in parallel with GNU parallel

find src/ -name “*.ts” |
parallel -j $MAX_JOBS analyze_file

Combine the results

echo ”=== Security Analysis Summary ===” cat “$RESULTS_DIR”/*.analysis |
claude -p “summarise every security issue, ordered by severity”


### Pattern 2: Watch mode — monitoring in real time

Run Claude every time a file changes. Good for TDD:

#!/bin/bash

watch-and-fix.sh — TDD helper

install: npm install -g nodemon

echo ”👀 Watch mode: fixing test failures automatically”

nodemon
—watch src/
—ext ts,tsx
—exec ’ OUTPUT=$(npm test 2>&1) if echo “$OUTPUT” | grep -q “FAIL”; then echo ”❌ Tests failed, asking Claude…” echo “$OUTPUT” | tail -50 | claude -p
“fix these test failures, name the exact changes needed” else echo ”✅ All tests pass!” fi ‘


### Pattern 3: Context-aware completion

Write a shell function that picks up the project context automatically:

Add to ~/.zshrc or ~/.bashrc

Function: ask Claude with the project context added automatically

function ca() { local QUESTION=”$*” local CONTEXT=""

Add context automatically when inside a git repo

if git rev-parse —git-dir > /dev/null 2>&1; then local PROJECT=$(basename $(git rev-parse —show-toplevel)) local BRANCH=$(git branch —show-current) local RECENT_CHANGES=$(git diff —stat HEAD~3..HEAD 2>/dev/null | head -5)

CONTEXT="Project: $PROJECT | Branch: $BRANCH"
if [ -f CLAUDE.md ]; then
  CONTEXT="$CONTEXT\n$(head -20 CLAUDE.md)"
fi

fi

if [ -n “$CONTEXT” ]; then echo -e “$CONTEXT\n\nQuestion: $QUESTION” | claude -p “$QUESTION” else claude -p “$QUESTION” fi }

Function: analyse the file open in the editor

function af() { local PROMPT=”$*“

read the most recently opened file in VS Code

local RECENT_FILE=$(code —list-extensions 2>/dev/null | head -1) cat “$1” | claude -p ”${PROMPT:-explain this file}” }

Usage

ca “how to implement payment retry”

af src/utils.ts “find the bug in this function”


### Pattern 4: A git automation suite

A set of git commands that use Claude automatically:

Add to ~/.gitconfig

[alias]

Smart commit: Claude writes the message

smart-commit = “!git diff —staged | claude -p ‘write a git commit message’ | git commit -F -“

PR review: analyse before the PR

pr-review = “!git diff main…HEAD | claude -p ‘review like a senior dev‘“

PR description: build the PR body

pr-desc = “!git log main..HEAD —oneline | claude -p ‘write a PR description‘“

Bug blame: work out who or which commit caused the bug

smart-blame = “!git log —oneline -20 | claude -p ‘which commit probably caused the bug: $1‘“

Usage

git smart-commit git pr-review git pr-desc > /tmp/pr-body.txt


### Pattern 5: An API testing suite

Test the API and have Claude analyse the results automatically:

#!/bin/bash

api-test.sh — test API endpoints, then have Claude analyse them

BASE_URL=”${1:-http://localhost:3000}” TOKEN=”${API_TOKEN:-}”

run_test() { local name=$1 local method=$2 local endpoint=$3 local body=$4 local expected_status=$5

echo -n “Testing $name… ”

if [ -n “$body” ]; then RESPONSE=$(curl -s -w “\n%{http_code}” -X $method
-H “Authorization: Bearer $TOKEN”
-H “Content-Type: application/json”
-d “$body” “$BASE_URL$endpoint”) else RESPONSE=$(curl -s -w “\n%{http_code}” -X $method
-H “Authorization: Bearer $TOKEN”
“$BASE_URL$endpoint”) fi

STATUS=$(echo “$RESPONSE” | tail -1) BODY=$(echo “$RESPONSE” | head -n -1)

if [ “$STATUS” = “$expected_status” ]; then echo ”✅ $STATUS” else echo ”❌ Expected $expected_status, got $STATUS” echo “$BODY” | claude -p “explain this response and say what is likely wrong” fi }

Run the test cases

run_test “Get products” GET “/api/products” "" “200” run_test “Search products” GET “/api/products?q=phone” "" “200” run_test “Create order (no auth)” POST “/api/orders” ”{}” “401” run_test “Apply invalid coupon” POST “/api/coupons/apply” ’{“code”:“INVALID”}’ “404”

Analyse the response times

echo "" echo “Response time analysis:” for endpoint in /api/products /api/categories /api/users/me; do TIME=$(curl -s -o /dev/null -w ”%{time_total}”
-H “Authorization: Bearer $TOKEN”
“$BASE_URL$endpoint”) echo ” $endpoint: ${TIME}s” done | claude -p “analyse these response times, which endpoint is abnormally slow”


## Glossary for this chapter

| CLI (Command Line Interface) | Driving the computer by typing text commands |
|---|---|
| Terminal | The program that opens a CLI, such as iTerm2, Windows Terminal, Warp |
| Shell | The program that takes commands from the terminal, such as bash, zsh, fish |
| Pipe (\|) | Feeds one command's output into another command's input |
| stdin | Standard Input — a program's input channel |
| stdout | Standard Output — the normal output channel |
| stderr | Standard Error — the error output channel (2>&1 merges it with stdout) |
| One-shot mode | Running claude -p "...", taking the answer and exiting; no session opened |
| Flag | A special parameter passed to a command, such as --model, --add-dir |
| tmux | Terminal multiplexer; several terminals in one window |
| Pane | A subdivision of the terminal in tmux (vertical or horizontal) |
| Session (tmux) | A group of windows and panes in tmux that survives a detach |
| Shebang (#!/bin/bash) | The first line of a shell script, naming the interpreter |
| chmod +x | Grants execute permission on a script file |
| Semantic Search | Searching by meaning rather than by keyword matching |
| FAISS | Facebook AI Similarity Search, a library for vector search |
| Embedding | Turning text into vector numbers that represent its semantic meaning |
| RAG Server | A web server offering RAG over an HTTP API |
| Chunking | Splitting a long document into small pieces for indexing |
| Retriever | The part that finds the relevant chunks in the vector database |
| Parallel Processing | Running several tasks at once instead of one at a time |
| Watch Mode | Monitoring for changes and running an action automatically |
| Git Alias | A short name for a long or complex git command |