Skill: 0xtimi/agent-squad (version 0.9.4)

--- name: agent-squad version: 0.9.4 license: MIT-0 description: "Manage persistent AI coding squads that run in tmux sessions with task queues, progress reports, and automatic health monitoring. Use when the user wants to: (1) start/launch/create/restart a squad or team of AI agents, (2) assign/give tasks to a squad, (3) check squad status or ask what a squad is doing, (4) ping/nudge a squad to report progress, (5) stop a squad, (6) list all active squads, (7) configure squad settings like default project directory, (8) delete/archive a squad. Supports Claude Code, Codex, Gemini CLI, OpenCode, Kimi, Trae, Aider, and Goose as AI engines." metadata: { "openclaw": { "requires": { "anyBins": ["tmux"], "bins": ["python3"] } } } --- # Agent Squad GitHub: https://github.com/0xTimi/agent-squad Run persistent AI coding squads in tmux. Squads pick up tasks, write code, and report progress — 24/7 in the background. ## Slash Command Usage Users can invoke `/agent-squad` directly with optional arguments: | Command | Action | |---|---| | `/agent-squad` | Show squad dashboard (or Getting Started if none exist) | | `/agent-squad list` | List all squads | | `/agent-squad start my-squad claude` | Start a squad | | `/agent-squad status my-squad` | Check squad status | | `/agent-squad stop my-squad` | Stop a squad | | `/agent-squad assign my-squad "add login page"` | Assign a task | | `/agent-squad ping my-squad` | Nudge squad to report | | `/agent-squad delete my-squad` | Archive a squad | | `/agent-squad peek my-squad` | Peek at squad's live tmux screen | | `/agent-squad restart my-squad` | Restart a stopped squad | No arguments or `list` → run `bash {baseDir}/scripts/squad-list.sh`: - **If squads exist**: show a clean status dashboard - **If no squads**: show the Getting Started intro below ## Getting Started When users ask "what is this", "how do I use this", or invoke `/agent-squad` with no squads, give a friendly intro with usage examples. Match the user's language. > Agent Squad runs AI coding agents in the background 24/7. Just tell me what you need: > > - "Start a squad called my-squad using claude for ~/projects/my-app" > - "Give my-squad a task: implement user login" > - "How's my-squad doing?" > - "Stop my-squad" > - "What squads do I have?" > > Engines: Claude Code, Codex, Gemini CLI, OpenCode, Kimi, Trae, Aider, Goose > > Want to start one now? If the user asks which engine to use or doesn't specify one: **default to `claude`** — it is the most thoroughly tested engine and works best with Agent Squad. Mention other engines only if the user specifically asks. See `{baseDir}/references/engines.md` for details. ## What Users Can Do Users interact through natural language. Here's what they might say and how to respond: ### Start a squad User: "start a squad called my-squad with claude" / "launch a codex squad for ~/projects/api" Ask if missing: squad name. Default engine to `claude` if not specified. Project dir and context are optional. First-time users: briefly mention squads run in full-auto mode — the AI has full access to the project directory. Response: "Squad 'my-squad' is up and running with Claude Code! You can assign tasks anytime." ### Assign a task User: "give my-squad a task: build the login page" / "let my-squad work on JWT auth" If only one squad exists, use it automatically. If the request is vague, ask for specifics. Response: "Task assigned! my-squad will start working on 'Login Page' shortly." ### Check status User: "how's my-squad doing?" / "what's the status?" / "is my-squad done yet?" Include the live tmux screen output (from squad-status.sh) in the response — this lets the user see what the agent is actually doing right now. Response: "my-squad is running on Claude Code, working on 'Login Page' — about 60% done. 2 tasks completed, 1 in progress. Live screen: ``` Working on form validation... Created src/components/LoginForm.tsx Running tests... ```" ### Peek at screen User: "peek at my-squad" / "what's on my-squad's screen?" / "show me what my-squad is doing" Show the raw tmux screen content. This is a quick way to see the agent's live terminal without checking reports. Response: Show the screen output directly, formatted in a code block. ### Ping for update User: "ping my-squad" / "nudge it to report" Response: "I've nudged my-squad to update its progress report. Check back in a minute." ### Stop a squad User: "stop my-squad" / "pause the squad" Always confirm before stopping. Response: "my-squad stopped. All work is saved — you can restart anytime." ### Restart a squad User: "restart my-squad" / "bring my-squad back up" Response: "my-squad is back up and running! It will pick up where it left off." ### List squads User: "what squads do I have?" / "list my squads" / "show all squads" Present a clean readable summary of all squads with name, engine, status, and task counts. ### Delete a squad User: "delete my-squad" / "archive the old squad" / "clean up my-squad" Always ask for confirmation first. Reassure: data is archived, project code is never touched. ### Configure User: "set default project dir to ~/code" / "show squad settings" ### What commands are available? User: "agent-squad都有哪些命令" / "what can you do?" / "help" **IMPORTANT**: Never show internal script names (squad-start.sh, etc.) to users. Instead, show the `/agent-squad` slash commands and natural language examples: > Here's what you can do with Agent Squad: > > | Command | Or just say... | > |---|---| > | `/agent-squad start my-squad claude` | "Start a squad called my-squad" | > | `/agent-squad assign my-squad "task"` | "Give my-squad a task: ..." | > | `/agent-squad status my-squad` | "How's my-squad doing?" | > | `/agent-squad peek my-squad` | "Peek at my-squad's screen" | > | `/agent-squad ping my-squad` | "Ping my-squad" | > | `/agent-squad stop my-squad` | "Stop my-squad" | > | `/agent-squad restart my-squad` | "Restart my-squad" | > | `/agent-squad list` | "What squads do I have?" | > | `/agent-squad delete my-squad` | "Delete my-squad" | --- ## Script Reference All scripts at `{baseDir}/scripts/`. Execute based on user intent above and present results conversationally. ### squad-start.sh ```bash bash {baseDir}/scripts/squad-start.sh "<name>" "<engine>" "<context>" [--project <dir>] [--restart] [--agent-teams] [--no-watchdog] ``` - name: lowercase alphanumeric + hyphens - engine: claude, codex, gemini, opencode, kimi, trae, aider, goose - context: optional project background - `--project <dir>`: custom code output directory - `--restart`: required if squad name already exists (also used for restart intent) - `--agent-teams`: claude only, multi-agent mode - `--no-watchdog`: skip auto-restart cron ### squad-assign.sh ```bash bash {baseDir}/scripts/squad-assign.sh "<name>" "<title>" "<objective>" "<priority>" ``` Priority: critical / high / normal (default) / low ### squad-status.sh ```bash bash {baseDir}/scripts/squad-status.sh "<name>" ``` Also read latest report in `~/.openclaw/workspace/agent-squad/squads/<name>/reports/` — check `## Current` section for real-time progress. ### squad-peek.sh ```bash bash {baseDir}/scripts/squad-peek.sh "<name>" [lines] ``` Default: 20 lines. Shows the live tmux screen content of a running squad. ### squad-ping.sh ```bash bash {baseDir}/scripts/squad-ping.sh "<name>" ``` ### squad-stop.sh ```bash bash {baseDir}/scripts/squad-stop.sh "<name>" ``` ### squad-list.sh ```bash bash {baseDir}/scripts/squad-list.sh ``` ### squad-delete.sh ```bash bash {baseDir}/scripts/squad-delete.sh "<name>" # show summary bash {baseDir}/scripts/squad-delete.sh "<name>" --confirm # confirm archive ``` ### squad-config.sh ```bash bash {baseDir}/scripts/squad-config.sh show bash {baseDir}/scripts/squad-config.sh set projects_dir "<path>" ``` ## Guidelines - **Never show internal script names** (squad-start.sh, etc.) to users — always use `/agent-squad` slash commands or natural language examples - If only one squad exists, use it automatically — don't ask "which squad?" - One engine per squad — suggest multiple squads for multiple engines - Don't modify task/report files directly — only via assign script - If squad is stopped and user assigns a task, write it anyway — picked up on restart - Squads auto-init git repos; for existing projects suggest a separate branch - Watchdog auto-restarts crashed squads every 5 min by default - For restart: stop the squad first (`squad-stop.sh`), then start with `--restart` flag ## Engine Reference Details: `{baseDir}/references/engines.md`

path: README.md
content: # agent-squad

**Your AI coding squad works 24/7 — non-stop coding in the background, ready for your review anytime.**

An [OpenClaw](https://openclaw.ai) skill that runs persistent AI coding agents in [tmux](https://github.com/tmux/tmux) sessions. Assign tasks, they code around the clock. Check in whenever you want. If they crash, they auto-restart.

Built on tmux instead of ACP — because you can't monitor, peek at, or auto-restart what you can't see. OpenClaw's built-in ACP agents work well for short tasks, but 24/7 coding needs agents that stay alive, accept new orders anytime, and survive crashes.

## Highlights

- **24/7 non-stop** — Your squad keeps coding while you sleep
- **Peek & command** — See the agent's live screen without interrupting it, or send instructions anytime
- **8 AI engines** — Claude Code, Codex, Gemini, OpenCode, Kimi, Trae, Aider, Goose
- **Crash-proof** — Watchdog auto-restarts agents if they go down
- **Task queue** — Drop tasks in, the squad works through them
- **Token tracking** — Monitor token usage per task across all engines
- **Natural language** — Manage squads through conversation via OpenClaw

## Install

```bash
# Inside OpenClaw
/skill install agent-squad

# Or via CLI
npx clawhub@latest install agent-squad
```

**Requirements:** tmux, python3, and at least one AI engine CLI.

## Quick Start

```
You:      "Start a squad called backend using claude for ~/projects/my-api"
OpenClaw:  Squad "backend" started (claude). Watchdog active.

You:      "Give backend a task: add JWT authentication"
OpenClaw:  Task assigned and squad notified.

You:      "What is backend doing?"
OpenClaw:  backend is running. Working on JWT auth — implementing token
           refresh. ~60% complete.

You:      "Stop backend"
OpenClaw:  Stopped. All data preserved. Restart anytime.
```

## Supported Engines

| Engine | CLI | Auto-mode |
|--------|-----|-----------|
| Claude Code | `claude` | `--dangerously-skip-permissions` |
| Codex | `codex` | `--full-auto` |
| Gemini CLI | `gemini` | built-in |
| OpenCode | `opencode` | built-in |
| Kimi | `kimi` | built-in |
| Trae | `trae-agent` | built-in |
| Aider | `aider` | `--yes` |
| Goose | `goose` | built-in |

## Security

Squads run in **full-auto mode** — the AI can read, write, and execute anything in the project directory without asking. Only run squads on projects you trust. Keep credentials and `.env` files out of squad project directories.

## Documentation

See [docs/guide.md](docs/guide.md) for full details on operations, directory structure, configuration, and architecture.

## License

MIT


path: assets/CONTEXT-template.md
content: # Project Context

## Background
{{CONTEXT}}

## Notes
- Add project-specific notes, architecture decisions, or constraints here.
- The coordinator reads this file on startup for project context.


path: assets/PROTOCOL-template.md
content: # {{SQUAD_NAME}} — Coordination Protocol

You are **{{SQUAD_NAME}}**, a persistent coordinator running on {{ENGINE}}.
Your job is to pick up tasks, execute them (spawning sub-agents when needed), and keep reports up to date.

## Directory Layout

```
tasks/pending/          <- New tasks appear here
tasks/in-progress/      <- Move tasks here when you start working
tasks/done/             <- Move tasks here when finished
tasks/cancelled/        <- Cancelled tasks (moved by user)
reports/task-<name>.md  <- One report per task, updated continuously
logs/                   <- Logs and summaries
PROTOCOL.md             <- This file (your instructions)
CONTEXT.md              <- Optional project background (read if present)
squad.json              <- Your squad metadata (do not modify)
```

## Task Lifecycle

```
New task arrives    -> tasks/pending/task-YYYYMMDD-<name>.md
You accept it       -> move to tasks/in-progress/, create reports/task-<name>.md
Working on it       -> update report continuously
Finished            -> move to tasks/done/, set Status: done, fill Result
```

## Task File Format

```markdown
# Task: <title>

## Created
YYYY-MM-DDTHH:MM+00:00

## Context
<background information>

## Objective
<what to do>

## Target
<project path or scope>

## Acceptance Criteria
- [ ] criterion 1
- [ ] criterion 2

## Priority
critical | high | normal | low
```

## Report File Format

Path: `reports/task-YYYYMMDD-<name>.md`

```markdown
# Report: <title>

## Status
in-progress | done | blocked

## Current
Working on: <what you are doing right now>
Next step: <what you plan to do next>
Progress: <percentage or description>
Last updated: YYYY-MM-DDTHH:MM+00:00

## Timeline
| Event     | Time                   | By             |
|-----------|------------------------|----------------|
| assigned  | 2026-01-01T10:00+00:00 | user           |
| started   | 2026-01-01T10:05+00:00 | {{SQUAD_NAME}} |

## Activity Log
Append one line for each meaningful progress event:
- YYYY-MM-DDTHH:MM  [{{SQUAD_NAME}}]  Completed X, committed abc1234

## Commits
| Time | Hash | Message |
|------|------|---------|

## Token Usage
Start: <tokens at task start>
Current: <tokens now>
Consumed: <difference>

## Result
(filled on completion: summary, PRs, metrics, total tokens consumed)
```

## Reporting Rules (Mandatory)

1. **Accept task** -> Create report immediately with Status: in-progress
2. **Every commit** -> Update Activity Log + Commits table
3. **Every 15 minutes** -> Add Activity Log entry even if no commit (e.g., "debugging X", "reading docs for Y")
4. **Blocked or direction change** -> Update report immediately, add ## Blocked section
5. **Completed** -> Fill Result, set Status: done, move task to done/

The ## Current section must always reflect your real-time state. Anyone checking your report should instantly understand what you are doing.

## Token Usage Tracking

If your engine supports querying token usage (e.g., `/usage` for Claude Code, `/status` for Codex, `/stats` for Gemini/Goose, `/tokens` for Aider, `/usage` for Kimi), you should:
1. **When starting a task** — Check your current token usage and record it in the report under ## Token Usage as the "Start" value.
2. **Each time you update your report** — Check usage again and update "Current" and "Consumed" in the report.
3. **When completing a task** — Record the total tokens consumed in ## Result.

## Git Rules (Mandatory)

If the project directory is a git repo (check with `git rev-parse --is-inside-work-tree`):

1. **Commit after every meaningful change** — each completed function, fix, or logical step gets its own commit.
2. **Write clear commit messages** — describe what changed and why.
3. **Never force-push or rewrite history** — only append commits.
4. **Commit before switching tasks** — always leave the working tree clean.

This ensures the user can review, revert, or cherry-pick any change the squad makes.

## File Rules

- One task per file. Never delete tasks — move to `done/`.
- Filenames include date: `task-YYYYMMDD-<kebab-name>.md`
- All timestamps: ISO 8601 UTC (e.g., `2026-03-09T14:00:00+00:00`)
- Atomic writes: write to `.tmp` first, then `mv` to `.md`. Ignore `.tmp` files when reading.

## On Startup

1. Read `CONTEXT.md` if it exists (project background)
2. Read `logs/coordinator-summary.md` if it exists (previous session handoff)
3. Check `tasks/pending/` for new tasks
4. Check `tasks/in-progress/` for ongoing tasks to resume
5. Start working

## Before Shutdown

Write a summary to `logs/coordinator-summary.md` covering:
- What was accomplished
- What is still in progress
- Any blockers or issues
- Recommended next steps


path: clawhub.json
content: {
  "name": "agent-squad",
  "version": "0.9.4",
  "tagline": "Your AI coding squad works 24/7 — non-stop, crash-proof, ready for review anytime",
  "description": "Run persistent AI coding agents in tmux sessions. Assign tasks, they code around the clock. If they crash, a watchdog auto-restarts them. Check progress anytime through natural language. Supports 8 AI engines: Claude Code, Codex, Gemini CLI, OpenCode, Kimi, Trae, Aider, and Goose.",
  "category": "development",
  "tags": ["ai-agents", "tmux", "coding", "automation", "background-tasks", "multi-agent"],
  "homepage": "https://github.com/0xTimi/agent-squad",
  "support_url": "https://github.com/0xTimi/agent-squad/issues"
}


path: docs/guide.md
content: # agent-squad — Full Guide

## How It Works

```
You (via OpenClaw)          agent-squad              AI Agent (in tmux)
       │                         │                         │
       ├─ "start a squad" ──────►├─ create tmux session ──►├─ AI starts coding
       │                         ├─ register watchdog       │
       │                         │                         │
       ├─ "assign task" ────────►├─ write task file ───────►├─ picks up task
       │                         │                         ├─ codes & commits
       │                         │                         ├─ updates report
       │                         │                         │
       ├─ "check status" ──────►├─ read reports ───────────┤
       │                         │                         │
       │                    watchdog (every 5 min)          │
       │                         ├─ agent alive? ──────────►├─ yes → do nothing
       │                         ├─ agent dead? ───────────►├─ auto-restart
```

Each squad runs an AI engine (Claude Code, Codex, etc.) inside a tmux session. A coordinator prompt tells the AI to follow `PROTOCOL.md` — pick up tasks, write code, update reports. A watchdog cron checks every 5 minutes and restarts the engine if it crashes.

## Operations

### Start a Squad

```
"Start a squad called backend using claude for ~/projects/my-api"
```

Options:
- **`--project <dir>`** — Where the squad writes code. Defaults to `~/.openclaw/workspace/agent-squad/projects/<name>/`.
- **`--restart`** — Reuse an existing squad's data (tasks, reports). Required if the squad name was used before.
- **`--agent-teams`** — Enable Claude Code Agent Teams mode (claude only). The coordinator can spawn sub-agents for parallel work.

Engine-specific checks run automatically:
- `codex` warns if the project is not a git repo
- `gemini` uses Google OAuth login (run `gemini` once to auth before starting a squad)

### Assign a Task

```
"Give backend a task: add JWT authentication"
```

Provide a clear objective. Optionally set priority: critical, high, normal (default), low.

Tasks are written as markdown files to `tasks/pending/`. The squad picks them up and moves them through `in-progress/` → `done/`.

### Check Status

```
"What is backend doing?"
```

Shows engine, running/stopped state, task counts, and the current activity from the squad's report.

### Ping for Update

```
"Ask backend to report"
```

Sends a message to the squad asking it to update its progress report immediately.

### Stop a Squad

```
"Stop backend"
```

Kills the tmux session and removes the watchdog cron. All data (tasks, reports, logs) is preserved. Restart with `--restart`.

### Delete (Archive) a Squad

```
"Delete backend"
```

Archives the squad's coordination data to `.archive/`. The project code directory is **never touched**. Requires confirmation.

### Configure Settings

```
"Change default project directory to ~/code"
```

Sets the default directory for new squad projects. Stored in `config.json`. Existing squads are not affected.

### List All Squads

```
"What squads do I have?"
```

Shows all squads with engine, running/stopped status, and task counts.

## Directory Structure

Code and coordination data are kept separate — deleting a squad never touches your code:

```
~/.openclaw/workspace/agent-squad/
├── squads/<name>/              ← Coordination data
│   ├── tasks/
│   │   ├── pending/            ← New tasks land here
│   │   ├── in-progress/        ← Currently being worked on
│   │   ├── done/               ← Completed tasks
│   │   └── cancelled/          ← Cancelled tasks
│   ├── reports/                ← Progress reports per task
│   ├── logs/                   ← Watchdog and coordinator logs
│   ├── PROTOCOL.md             ← Coordinator instructions
│   ├── CONTEXT.md              ← Project background (optional)
│   └── squad.json              ← Squad metadata
├── projects/<name>/            ← Code output (default, configurable)
├── config.json                 ← Global settings
└── .archive/                   ← Archived (deleted) squads
```

## Watchdog

The watchdog runs every 5 minutes via OpenClaw cron and performs a 3-state check:

1. **tmux session alive + engine running** → healthy, do nothing
2. **tmux session alive + engine dead** → restart engine inside existing session
3. **tmux session gone** → full restart (new session + engine)

On restart, the coordinator is told to read `PROTOCOL.md` and `logs/coordinator-summary.md` to resume where it left off.

Log rotation: watchdog.log is rotated at ~5MB, keeping 1 backup.

## Task Lifecycle

```
New task arrives    → tasks/pending/task-YYYYMMDD-<name>.md
Squad accepts it    → moves to tasks/in-progress/, creates report
Working on it       → updates report continuously (every 15 min minimum)
Finished            → moves to tasks/done/, sets Status: done
```

Each task gets a report file in `reports/` with:
- Current status and progress
- Activity log with timestamps
- Commits table
- Final result summary

## Configuration

Global config is stored at `~/.openclaw/workspace/agent-squad/config.json`:

```json
{
  "projects_dir": "/Users/you/code"
}
```

Settings:
- **`projects_dir`** — Default parent directory for new squad projects. Each squad creates a subdirectory named after itself. Default: `~/.openclaw/workspace/agent-squad/projects/`.

## Engine Reference

All engines run in full-auto mode (no permission prompts) since squads operate unattended.

| Engine | Binary | Full command | Notes |
|--------|--------|-------------|-------|
| Claude Code | `claude` | `claude --dangerously-skip-permissions` | Supports `--agent-teams` for multi-agent |
| Codex | `codex` | `codex --full-auto` | Requires git repo |
| Gemini CLI | `gemini` | `gemini` | Google OAuth login (run `gemini` once to auth) |
| OpenCode | `opencode` | `opencode` | |
| Kimi | `kimi` | `kimi` | |
| Trae | `trae-agent` | `trae-agent` | ByteDance |
| Aider | `aider` | `aider --yes` | |
| Goose | `goose` | `goose` | Block (formerly Square) |

## Security Considerations

- **Full-auto mode** means the AI operates without any permission prompts. It can freely read, write, delete files, and execute commands within the project directory.
- **Keep sensitive files out**: credentials, API keys, `.env` files, and private keys should not be in project directories that squads work on.
- **`--agent-teams`** (Claude only) allows the coordinator to spawn additional AI sub-agents, increasing the scope of autonomous operations.
- Each squad runs in its own isolated tmux session.
- Coordination directory logs are gitignored by default.


path: references/engines.md
content: # Supported AI Engines

## Engine Configuration Table

| Engine ID    | Binary    | Auto-Mode Command                                | Notes                          |
|-------------|-----------|--------------------------------------------------|--------------------------------|
| claude      | claude    | claude --dangerously-skip-permissions            | Richest feature set, agent teams |
| codex       | codex     | codex --full-auto                                | Rust-based, low latency. Needs git repo |
| gemini      | gemini    | gemini                                           | 1M context, Google OAuth login (run `gemini` once to auth) |
| opencode    | opencode  | opencode                                         | 75+ model providers, open-source |
| kimi        | kimi      | kimi                                             | Moonshot AI, ACP compatible    |
| trae        | trae-agent| trae-agent                                       | ByteDance, multi-LLM support   |
| aider       | aider     | aider --yes                                      | Git-first, multi-file coordination |
| goose       | goose     | goose                                            | Block's open-source agent      |

## Adding Custom Engines

Users can add engines by editing `squad.json` in the squad directory. Set:

```json
{
  "engine": "custom",
  "engine_command": "my-agent --auto-mode"
}
```

The watchdog and start scripts will use `engine_command` if present, falling back to the table above for known engine IDs.

## Engine Requirements

- **All engines**: Must support unattended operation (no interactive permission prompts)
- **codex**: Requires a git repository in the working directory. The start script will warn if the target is not a git repo.
- **claude**: `--dangerously-skip-permissions` bypasses all safety prompts. Ensure the working directory contains no sensitive files.
- **gemini**: Uses Google OAuth login (same as Claude Code / Codex). Run `gemini` once in a terminal to complete the browser login flow before starting a squad. No API key needed.
- **opencode**: Configuration via `~/.opencode/config.json`.

## Security Note

All engines run in full-auto mode because the coordinator operates unattended in a tmux session. There is no one to approve permission prompts. Users should:

1. Only point squads at project directories they trust
2. Not store secrets (API keys, credentials) in the coordination directory
3. Use git branches or worktrees for isolation
4. Review the coordinator's commits before merging to main


path: scripts/squad-assign.sh
content: #!/bin/bash
# squad-assign.sh — Assign a task to a squad
# Usage: squad-assign.sh <squad-name> <task-title> <objective> [priority]

set -euo pipefail

SQUAD_NAME="${1:?Usage: squad-assign.sh <squad-name> <task-title> <objective> [priority]}"
TASK_TITLE="${2:?Usage: squad-assign.sh <squad-name> <task-title> <objective> [priority]}"
OBJECTIVE="${3:?Usage: squad-assign.sh <squad-name> <task-title> <objective> [priority]}"
PRIORITY="${4:-normal}"

# --- Validate squad name ---
if [[ ! "$SQUAD_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  echo "ERROR: Invalid squad name '$SQUAD_NAME'. Use lowercase alphanumeric with hyphens."
  exit 1
fi

SQUAD_DIR="${HOME}/.openclaw/workspace/agent-squad/squads/${SQUAD_NAME}"
TMUX_SESSION="squad-${SQUAD_NAME}"

# --- Check squad exists ---
if [ ! -d "$SQUAD_DIR" ]; then
  echo "ERROR: Squad '$SQUAD_NAME' not found. Start it first, e.g.: \"Start a squad called $SQUAD_NAME using claude\""
  exit 1
fi

# --- Generate filename ---
DATE=$(date +%Y%m%d)
KEBAB_TITLE=$(echo "$TASK_TITLE" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g; s/-\{2,\}/-/g; s/^-//; s/-$//')
TASK_FILE="task-${DATE}-${KEBAB_TITLE}.md"
TASK_PATH="${SQUAD_DIR}/tasks/pending/${TASK_FILE}"

# --- Find unique filename ---
COUNTER=0
while [ -f "$TASK_PATH" ]; do
  COUNTER=$((COUNTER + 1))
  TASK_FILE="task-${DATE}-${KEBAB_TITLE}-${COUNTER}.md"
  TASK_PATH="${SQUAD_DIR}/tasks/pending/${TASK_FILE}"
done

# --- Write task file (atomic) ---
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%S+00:00)
TMP_PATH="${TASK_PATH}.${$}.tmp"

python3 -c "
import sys
title, timestamp, objective, priority, out = sys.argv[1:6]
content = f'''# Task: {title}

## Created
{timestamp}

## Context
Assigned by user via agent-squad.

## Objective
{objective}

## Acceptance Criteria
- [ ] Objective completed as described
- [ ] All changes committed and pushed
- [ ] Report updated with results

## Priority
{priority}
'''
with open(out, 'w') as f:
    f.write(content)
" "$TASK_TITLE" "$TIMESTAMP" "$OBJECTIVE" "$PRIORITY" "$TMP_PATH"

if ! mv "$TMP_PATH" "$TASK_PATH"; then
  echo "ERROR: Failed to write task file."
  rm -f "$TMP_PATH"
  exit 1
fi

# --- Notify squad via tmux if running ---
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
  {
    tmux send-keys -t "$TMUX_SESSION" Escape 2>/dev/null || true
    sleep 1
    tmux send-keys -t "$TMUX_SESSION" "New task assigned: check ${SQUAD_DIR}/tasks/pending/${TASK_FILE} and start working on it." Enter
  } 2>/dev/null && echo "Task assigned and squad notified." || echo "Task assigned. Could not notify squad via tmux (it may still be initializing)."
else
  echo "Task assigned. Squad is not currently running — it will pick up the task when restarted."
fi

echo ""
echo "  Squad:    ${SQUAD_NAME}"
echo "  Task:     ${TASK_TITLE}"
echo "  File:     ${TASK_PATH}"
echo "  Priority: ${PRIORITY}"


path: scripts/squad-config.sh
content: #!/bin/bash
# squad-config.sh — View or update agent-squad settings
# Usage: squad-config.sh show
#        squad-config.sh set <key> <value>

set -euo pipefail

BASE_DIR="${HOME}/.openclaw/workspace/agent-squad"
CONFIG_FILE="${BASE_DIR}/config.json"

# --- Check python3 ---
if ! command -v python3 &>/dev/null; then
  echo "ERROR: python3 is required but not found in PATH."
  exit 1
fi

ACTION="${1:?Usage: squad-config.sh show | set <key> <value>}"

case "$ACTION" in
  show)
    if [ -f "$CONFIG_FILE" ]; then
      echo "Config: $CONFIG_FILE"
      echo ""
      python3 -c "import json,sys; [print(f'  {k}: {v}') for k,v in json.load(open(sys.argv[1])).items()]" "$CONFIG_FILE"
    else
      echo "No config file found. Using defaults."
      echo ""
      echo "  projects_dir: ${BASE_DIR}/projects"
    fi
    ;;

  set)
    KEY="${2:?Usage: squad-config.sh set <key> <value>}"
    VALUE="${3:?Usage: squad-config.sh set <key> <value>}"

    # Validate key
    case "$KEY" in
      projects_dir)
        # Resolve to absolute path (mkdir -p is a no-op if it already exists)
        mkdir -p "$VALUE"
        VALUE=$(cd "$VALUE" && pwd)
        ;;
      *)
        echo "ERROR: Unknown config key '$KEY'. Supported keys: projects_dir"
        exit 1
        ;;
    esac

    # Write config (safe via sys.argv)
    mkdir -p "$BASE_DIR"
    python3 -c "
import json, sys, os
config_file = sys.argv[1]
key = sys.argv[2]
value = sys.argv[3]
config = {}
if os.path.exists(config_file):
    with open(config_file) as f:
        config = json.load(f)
config[key] = value
with open(config_file, 'w') as f:
    json.dump(config, f, indent=2)
    f.write('\n')
" "$CONFIG_FILE" "$KEY" "$VALUE"

    echo "Set $KEY = $VALUE"
    echo "New squads will use this setting. Existing squads are not affected."
    ;;

  *)
    echo "Usage: \"Show squad settings\" or \"Set default project dir to ~/code\""
    exit 1
    ;;
esac


path: scripts/squad-delete.sh
content: #!/bin/bash
# squad-delete.sh — Archive a stopped squad (non-destructive)
# Usage: squad-delete.sh <squad-name> [--confirm]
# Moves squad data to .archive/, preserving everything.
# Project code in projects/ is NEVER touched.

set -euo pipefail

BASE_DIR="${HOME}/.openclaw/workspace/agent-squad"
SQUADS_DIR="${BASE_DIR}/squads"
ARCHIVE_DIR="${BASE_DIR}/.archive"

SQUAD_NAME="${1:?Usage: squad-delete.sh <squad-name> [--confirm]}"
CONFIRM="${2:-}"

# --- Validate squad name ---
if [[ ! "$SQUAD_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  echo "ERROR: Invalid squad name '$SQUAD_NAME'. Use lowercase alphanumeric with hyphens."
  exit 1
fi
SQUAD_DIR="${SQUADS_DIR}/${SQUAD_NAME}"
TMUX_SESSION="squad-${SQUAD_NAME}"

# --- Check squad exists ---
if [ ! -d "$SQUAD_DIR" ]; then
  echo "ERROR: Squad '$SQUAD_NAME' not found."
  exit 1
fi

# --- Refuse to delete running squad ---
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
  echo "ERROR: Squad '$SQUAD_NAME' is still running. Stop it first, e.g.: \"Stop $SQUAD_NAME\""
  exit 1
fi

# --- Count tasks and show summary ---
PENDING=$(find "$SQUAD_DIR/tasks/pending" -maxdepth 1 -name "task-*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
IN_PROG=$(find "$SQUAD_DIR/tasks/in-progress" -maxdepth 1 -name "task-*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
DONE=$(find "$SQUAD_DIR/tasks/done" -maxdepth 1 -name "task-*.md" -type f 2>/dev/null | wc -l | tr -d ' ')

# Read project dir
PROJECT_DIR=""
if [ -f "$SQUAD_DIR/squad.json" ] && command -v python3 &>/dev/null; then
  PROJECT_DIR=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('project_dir', ''))" "$SQUAD_DIR/squad.json" 2>/dev/null || echo "")
fi

echo "Squad '$SQUAD_NAME' summary:"
echo "  Tasks: ${PENDING} pending, ${IN_PROG} in-progress, ${DONE} done"
echo "  Squad data: $SQUAD_DIR"
if [ -n "$PROJECT_DIR" ]; then
  echo "  Project code: $PROJECT_DIR (will NOT be deleted)"
fi
echo ""

if [ "$IN_PROG" -gt 0 ]; then
  echo "WARNING: There are ${IN_PROG} tasks still in-progress!"
fi

if [ "$CONFIRM" != "--confirm" ]; then
  echo "This will archive the squad data (tasks, reports, logs) to:"
  echo "  ${ARCHIVE_DIR}/${SQUAD_NAME}/"
  echo ""
  echo "Project code at '${PROJECT_DIR}' will NOT be touched."
  echo ""
  echo "To proceed, confirm: \"Yes, delete $SQUAD_NAME\""
  exit 0
fi

# --- Archive ---
mkdir -p "$ARCHIVE_DIR"

# Handle collision in archive
ARCHIVE_TARGET="${ARCHIVE_DIR}/${SQUAD_NAME}"
if [ -d "$ARCHIVE_TARGET" ]; then
  ARCHIVE_TARGET="${ARCHIVE_DIR}/${SQUAD_NAME}-$(date +%Y%m%d-%H%M%S)"
fi

mv "$SQUAD_DIR" "$ARCHIVE_TARGET"

# Remove watchdog cron if openclaw is available
CRON_NAME="squad-watchdog-${SQUAD_NAME}"
if command -v openclaw &>/dev/null; then
  openclaw cron remove --name "$CRON_NAME" 2>/dev/null || true
fi

echo "Squad '$SQUAD_NAME' archived to:"
echo "  $ARCHIVE_TARGET"
if [ -n "$PROJECT_DIR" ]; then
  echo ""
  echo "Project code is still at: $PROJECT_DIR"
fi


path: scripts/squad-list.sh
content: #!/bin/bash
# squad-list.sh — List all squads and their status
# Usage: squad-list.sh

set -euo pipefail

SQUADS_DIR="${HOME}/.openclaw/workspace/agent-squad/squads"

# --- Check squads directory ---
if [ ! -d "$SQUADS_DIR" ]; then
  echo "No squads found. Try: \"Start a squad called my-squad using claude\" or \"/agent-squad start my-squad claude\""
  exit 0
fi

# --- Find all squads ---
SQUAD_COUNT=0
for squad_dir in "$SQUADS_DIR"/*/; do
  [ -d "$squad_dir" ] || continue
  SQUAD_NAME=$(basename "$squad_dir")
  TMUX_SESSION="squad-${SQUAD_NAME}"

  # Skip hidden dirs and .archive
  [[ "$SQUAD_NAME" == .* ]] && continue

  # Read engine (safe via sys.argv)
  ENGINE="?"
  if [ -f "$squad_dir/squad.json" ] && command -v python3 &>/dev/null; then
    ENGINE=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('engine', '?'))" "$squad_dir/squad.json" 2>/dev/null || echo "?")
  fi

  # tmux status
  if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
    STATUS="running"
  else
    STATUS="stopped"
  fi

  # Count tasks
  PENDING=$(find "$squad_dir/tasks/pending" -maxdepth 1 -name "task-*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
  IN_PROG=$(find "$squad_dir/tasks/in-progress" -maxdepth 1 -name "task-*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
  DONE=$(find "$squad_dir/tasks/done" -maxdepth 1 -name "task-*.md" -type f 2>/dev/null | wc -l | tr -d ' ')

  if [ "$SQUAD_COUNT" -eq 0 ]; then
    printf "%-20s %-12s %-10s %s\n" "SQUAD" "ENGINE" "STATUS" "TASKS (P/A/D)"
    printf "%-20s %-12s %-10s %s\n" "-----" "------" "------" "-------------"
  fi

  printf "%-20s %-12s %-10s %s/%s/%s\n" "$SQUAD_NAME" "$ENGINE" "$STATUS" "$PENDING" "$IN_PROG" "$DONE"
  SQUAD_COUNT=$((SQUAD_COUNT + 1))
done

if [ "$SQUAD_COUNT" -eq 0 ]; then
  echo "No squads found. Try: \"Start a squad called my-squad using claude\" or \"/agent-squad start my-squad claude\""
fi


path: scripts/squad-peek.sh
content: #!/bin/bash
# squad-peek.sh — Peek at a squad's live tmux screen
# Usage: squad-peek.sh <squad-name> [lines]

set -euo pipefail

SQUAD_NAME="${1:?Usage: squad-peek.sh <squad-name> [lines]}"
LINES="${2:-20}"

# --- Validate squad name ---
if [[ ! "$SQUAD_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  echo "ERROR: Invalid squad name '$SQUAD_NAME'. Use lowercase alphanumeric with hyphens."
  exit 1
fi

TMUX_SESSION="squad-${SQUAD_NAME}"

# --- Check tmux session exists ---
if ! tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
  echo "ERROR: Squad '$SQUAD_NAME' is not running (no tmux session found)."
  exit 1
fi

# --- Capture screen ---
SCREEN_OUTPUT=$(tmux capture-pane -t "$TMUX_SESSION" -p 2>/dev/null | sed '/^$/d' | tail -"$LINES") || true

if [ -z "$SCREEN_OUTPUT" ]; then
  echo "Screen is empty — the squad may still be initializing."
else
  echo "Live screen of '$SQUAD_NAME' (last $LINES lines):"
  echo ""
  echo "$SCREEN_OUTPUT"
fi


path: scripts/squad-ping.sh
content: #!/bin/bash
# squad-ping.sh — Ask a squad to update its status report
# Usage: squad-ping.sh <squad-name>

set -euo pipefail

SQUAD_NAME="${1:?Usage: squad-ping.sh <squad-name>}"

# --- Validate squad name ---
if [[ ! "$SQUAD_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  echo "ERROR: Invalid squad name '$SQUAD_NAME'. Use lowercase alphanumeric with hyphens."
  exit 1
fi

SQUAD_DIR="${HOME}/.openclaw/workspace/agent-squad/squads/${SQUAD_NAME}"
TMUX_SESSION="squad-${SQUAD_NAME}"

# --- Check tmux session exists ---
if ! tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
  echo "ERROR: Squad '$SQUAD_NAME' is not running (no tmux session '$TMUX_SESSION')."
  exit 1
fi

# --- Send ping ---
{
  tmux send-keys -t "$TMUX_SESSION" Escape 2>/dev/null || true
  sleep 1
  tmux send-keys -t "$TMUX_SESSION" "Please update your report now: write your current progress, completed items, any issues, and next steps to ${SQUAD_DIR}/reports/. Update the ## Current section so it reflects your real-time state." Enter
} 2>/dev/null || true

echo "Pinged squad '$SQUAD_NAME'. Check status in a minute to see the updated report."


path: scripts/squad-start.sh
content: #!/bin/bash
# squad-start.sh — Create and launch a new squad
# Usage: squad-start.sh <squad-name> <engine> [context-text] [--project <dir>] [--restart] [--agent-teams]

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
BASE_DIR="${HOME}/.openclaw/workspace/agent-squad"
SQUADS_DIR="${BASE_DIR}/squads"

# --- Args (python3 not needed yet) ---
SQUAD_NAME="${1:?Usage: squad-start.sh <squad-name> <engine> [context-text] [--project <dir>] [--restart] [--agent-teams] [--no-watchdog]}"
ENGINE="${2:?Usage: squad-start.sh <squad-name> <engine> [context-text] [--project <dir>] [--restart] [--agent-teams] [--no-watchdog]}"
CONTEXT=""
RESTART=false
AGENT_TEAMS=false
NO_WATCHDOG=false
PROJECT_DIR=""
args=("${@:3}")
i=0
while [ $i -lt ${#args[@]} ]; do
  case "${args[$i]}" in
    --restart)       RESTART=true ;;
    --agent-teams)   AGENT_TEAMS=true ;;
    --no-watchdog)   NO_WATCHDOG=true ;;
    --project)       i=$((i + 1)); PROJECT_DIR="${args[$i]}" ;;
    *)               CONTEXT="${args[$i]}" ;;
  esac
  i=$((i + 1))
done

# --- Validate squad name (lowercase alphanumeric + hyphens) ---
if [[ ! "$SQUAD_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  echo "ERROR: Squad name must be lowercase alphanumeric with hyphens (e.g., 'my-squad')"
  exit 1
fi

# --- Reject reserved names as squad names (causes confusion with OpenClaw) ---
RESERVED_NAMES="claude codex gemini opencode kimi trae aider goose start stop status ping delete assign list squad task report watchdog openclaw skill cron"
for reserved in $RESERVED_NAMES; do
  if [ "$SQUAD_NAME" = "$reserved" ]; then
    echo "ERROR: '$SQUAD_NAME' is a reserved name and cannot be used as a squad name."
    echo ""
    echo "  Pick a name that combines your project and role, so it's"
    echo "  easy to tell squads apart, e.g.:"
    echo "    myapp-backend, acme-billing, dario-team, sam-frontend"
    exit 1
  fi
done

# --- Validate --agent-teams flag ---
if [ "$AGENT_TEAMS" = true ] && [ "$ENGINE" != "claude" ]; then
  echo "ERROR: --agent-teams is only supported with the claude engine."
  exit 1
fi

# --- Resolve engine binary and command ---
get_engine_bin_name() {
  case "$1" in
    claude)    echo "claude" ;;
    codex)     echo "codex" ;;
    gemini)    echo "gemini" ;;
    opencode)  echo "opencode" ;;
    kimi)      echo "kimi" ;;
    trae)      echo "trae-agent" ;;
    aider)     echo "aider" ;;
    goose)     echo "goose" ;;
    *)         echo "" ;;
  esac
}

get_engine_flags() {
  case "$1" in
    claude)    echo "--dangerously-skip-permissions" ;;
    codex)     echo "--full-auto --dangerously-bypass-approvals-and-sandbox" ;;
    aider)     echo "--yes" ;;
    *)         echo "" ;;
  esac
}

ENGINE_BIN_NAME=$(get_engine_bin_name "$ENGINE")
if [ -z "$ENGINE_BIN_NAME" ]; then
  echo "ERROR: Unknown engine '$ENGINE'. Supported: claude, codex, gemini, opencode, kimi, trae, aider, goose"
  exit 1
fi

# Resolve absolute path for the engine binary
ENGINE_PATH=$(command -v "$ENGINE_BIN_NAME" 2>/dev/null || true)
ENGINE_FLAGS=$(get_engine_flags "$ENGINE")
ENGINE_CMD="${ENGINE_PATH}${ENGINE_FLAGS:+ $ENGINE_FLAGS}"
ENGINE_BIN="$ENGINE_BIN_NAME"

# --- Environment check: collect all issues, report together ---
ERRORS=()
WARNINGS=()

if ! command -v python3 &>/dev/null; then
  ERRORS+=("python3 is required but not found. Install: https://www.python.org/downloads/")
fi

if ! command -v tmux &>/dev/null; then
  ERRORS+=("tmux is required but not found. Install: brew install tmux (macOS) or apt install tmux (Linux)")
fi

if [ -z "$ENGINE_PATH" ]; then
  ERRORS+=("'$ENGINE_BIN_NAME' (engine: $ENGINE) is not found in PATH. Please install it first.")
fi

if ! command -v git &>/dev/null; then
  WARNINGS+=("git is not installed. Code changes won't be version-controlled — consider installing git for safety backups.")
fi

if ! command -v openclaw &>/dev/null; then
  WARNINGS+=("openclaw not found. Watchdog cron won't be registered — the squad will run but won't auto-recover from crashes.")
fi

# Report warnings first, then errors
if [ ${#WARNINGS[@]} -gt 0 ]; then
  echo ""
  for w in "${WARNINGS[@]}"; do
    echo "WARNING: $w"
  done
fi

if [ ${#ERRORS[@]} -gt 0 ]; then
  echo ""
  for e in "${ERRORS[@]}"; do
    echo "ERROR: $e"
  done
  echo ""
  echo "Fix the errors above before starting a squad."
  exit 1
fi

OPENCLAW_AVAILABLE=true
command -v openclaw &>/dev/null || OPENCLAW_AVAILABLE=false

# --- Default project dir (read from config or use built-in default) ---
if [ -z "$PROJECT_DIR" ]; then
  CONFIGURED_DIR=""
  if [ -f "${BASE_DIR}/config.json" ]; then
    CONFIGURED_DIR=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('projects_dir', ''))" "${BASE_DIR}/config.json" 2>/dev/null || echo "")
  fi
  if [ -n "$CONFIGURED_DIR" ]; then
    PROJECT_DIR="${CONFIGURED_DIR}/${SQUAD_NAME}"
  else
    PROJECT_DIR="${BASE_DIR}/projects/${SQUAD_NAME}"
  fi
fi

TMUX_SESSION="squad-${SQUAD_NAME}"
SQUAD_DIR="${SQUADS_DIR}/${SQUAD_NAME}"

# --- Validate / create project directory ---
if [ ! -d "$PROJECT_DIR" ]; then
  mkdir -p "$PROJECT_DIR"
fi
PROJECT_DIR=$(cd "$PROJECT_DIR" && pwd)

# --- Initialize git if available ---
if command -v git &>/dev/null; then
  if ! git -C "$PROJECT_DIR" rev-parse --is-inside-work-tree &>/dev/null; then
    git -C "$PROJECT_DIR" init -q
    git -C "$PROJECT_DIR" add -A 2>/dev/null || true
    git -C "$PROJECT_DIR" commit -q -m "initial commit (before squad)" --allow-empty 2>/dev/null || true
    GIT_INITIALIZED=true
  else
    GIT_INITIALIZED=already
  fi
else
  GIT_INITIALIZED=false
fi

# --- Engine-specific validation ---
case "$ENGINE" in
  codex)
    if ! git -C "$PROJECT_DIR" rev-parse --is-inside-work-tree &>/dev/null 2>&1; then
      echo "WARNING: codex requires a git repository. '$PROJECT_DIR' is not a git repo."
      echo "         Run 'git init' in the project directory first, or codex may fail."
    fi
    ;;
esac

# --- Check for existing squad ---
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
  echo "ERROR: Squad '$SQUAD_NAME' is already running (tmux session '$TMUX_SESSION' exists)"
  exit 1
fi

if [ -d "$SQUAD_DIR" ]; then
  if [ "$RESTART" = true ]; then
    if [ ! -f "$SQUAD_DIR/squad.json" ]; then
      echo "ERROR: Squad directory exists but squad.json is missing or corrupted."
      exit 1
    fi
    echo "Restarting squad '$SQUAD_NAME' with existing data."
  else
    echo "ERROR: Squad '$SQUAD_NAME' already exists."
    echo ""
    echo "  To restart it: \"Restart $SQUAD_NAME\""
    echo "  To create a new one: pick a different name"
    exit 1
  fi
fi

# --- Create directory structure ---
mkdir -p "$SQUAD_DIR"/{tasks/pending,tasks/in-progress,tasks/done,tasks/cancelled,reports,logs}

# --- Write squad.json (safe via python3) ---
python3 -c "
import json, sys
data = {
    'name': sys.argv[1],
    'engine': sys.argv[2],
    'engine_path': sys.argv[3],
    'engine_command': sys.argv[4],
    'agent_teams': sys.argv[5] == 'true',
    'project_dir': sys.argv[6],
    'tmux_session': sys.argv[7],
    'created_at': sys.argv[8],
    'squads_dir': sys.argv[9]
}
with open(sys.argv[10], 'w') as f:
    json.dump(data, f, indent=2)
    f.write('\n')
" "$SQUAD_NAME" "$ENGINE" "$ENGINE_PATH" "$ENGINE_CMD" "$AGENT_TEAMS" "$PROJECT_DIR" "$TMUX_SESSION" \
  "$(date -u +%Y-%m-%dT%H:%M:%S+00:00)" "$SQUADS_DIR" "$SQUAD_DIR/squad.json"

# --- Render PROTOCOL.md from template ---
if [ ! -f "$SQUAD_DIR/PROTOCOL.md" ]; then
  TEMPLATE="$SKILL_DIR/assets/PROTOCOL-template.md"
  if [ ! -f "$TEMPLATE" ]; then
    echo "ERROR: Template not found: $TEMPLATE"
    exit 1
  fi
  # Use python3 for safe template rendering (no sed escaping issues)
  python3 -c "
import sys
with open(sys.argv[1]) as f:
    content = f.read()
content = content.replace('{{SQUAD_NAME}}', sys.argv[2])
content = content.replace('{{ENGINE}}', sys.argv[3])
with open(sys.argv[4], 'w') as f:
    f.write(content)
" "$TEMPLATE" "$SQUAD_NAME" "$ENGINE" "$SQUAD_DIR/PROTOCOL.md"
fi

# --- Write CONTEXT.md if provided ---
if [ -n "$CONTEXT" ] && [ ! -f "$SQUAD_DIR/CONTEXT.md" ]; then
  CONTEXT_TEMPLATE="$SKILL_DIR/assets/CONTEXT-template.md"
  if [ -f "$CONTEXT_TEMPLATE" ]; then
    python3 -c "
import sys
with open(sys.argv[1]) as f:
    content = f.read()
content = content.replace('{{CONTEXT}}', sys.argv[2])
with open(sys.argv[3], 'w') as f:
    f.write(content)
" "$CONTEXT_TEMPLATE" "$CONTEXT" "$SQUAD_DIR/CONTEXT.md"
  fi
fi

# --- Add .gitkeep files ---
for dir in tasks/pending tasks/in-progress tasks/done tasks/cancelled reports; do
  touch "$SQUAD_DIR/$dir/.gitkeep" 2>/dev/null || true
done

# --- Add logs/.gitignore ---
if [ ! -f "$SQUAD_DIR/logs/.gitignore" ]; then
  cat > "$SQUAD_DIR/logs/.gitignore" <<'GITIGNORE'
*.log
*.jsonl
!.gitkeep
GITIGNORE
fi

# --- Start tmux session (cwd = project dir, env vars for coordination) ---
TMUX_CMD="env SQUAD_DIR='${SQUAD_DIR}'"
if [ "$AGENT_TEAMS" = true ]; then
  TMUX_CMD="$TMUX_CMD CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"
fi
TMUX_CMD="$TMUX_CMD $ENGINE_CMD"
tmux new-session -d -s "$TMUX_SESSION" -c "$PROJECT_DIR" "$TMUX_CMD"

# --- Send initial prompt ---
{
  sleep 5
  INIT_PROMPT="You are ${SQUAD_NAME}, a persistent AI development coordinator. Your coordination directory is ${SQUAD_DIR} — read ${SQUAD_DIR}/PROTOCOL.md immediately, it contains your complete instructions. Check ${SQUAD_DIR}/CONTEXT.md if it exists for project background. Your code goes in the current directory (${PROJECT_DIR}). Check ${SQUAD_DIR}/tasks/pending/ for new tasks and ${SQUAD_DIR}/tasks/in-progress/ for tasks to resume. Write reports to ${SQUAD_DIR}/reports/. Begin."
  tmux send-keys -t "$TMUX_SESSION" "$INIT_PROMPT" Enter
} &

# --- Register watchdog cron ---
if [ "$NO_WATCHDOG" = true ]; then
  OPENCLAW_AVAILABLE=false
fi
if [ "$OPENCLAW_AVAILABLE" = true ]; then
  WATCHDOG_PATH="${SKILL_DIR}/scripts/squad-watchdog.sh"
  CRON_NAME="squad-watchdog-${SQUAD_NAME}"

  # Remove existing cron if any
  openclaw cron remove --name "$CRON_NAME" 2>/dev/null || true

  openclaw cron add \
    --name "$CRON_NAME" \
    --cron "*/5 * * * *" \
    --session isolated \
    --light-context \
    --message "Run this command to check squad health: bash ${WATCHDOG_PATH} ${SQUAD_NAME}. If the script reports the squad restarted, say so. If healthy, do nothing." \
    2>/dev/null || echo "WARNING: Could not register cron watchdog. You may need to monitor the squad manually."
fi

# --- Output ---
echo ""
echo "Squad '${SQUAD_NAME}' started successfully."
echo ""
echo "  Engine:      ${ENGINE}"
if [ "$AGENT_TEAMS" = true ]; then
  echo "  Mode:        agent-teams (multi-agent coordination)"
fi
echo "  Project:     ${PROJECT_DIR}"
if [ "$GIT_INITIALIZED" = true ]; then
  echo "  Git:         initialized (safety backup enabled)"
elif [ "$GIT_INITIALIZED" = already ]; then
  echo "  Git:         already initialized"
else
  echo "  Git:         not installed (no automatic backup — consider installing git)"
fi
echo "  Squad data:  ${SQUAD_DIR}"
echo "  tmux:        tmux attach -t ${TMUX_SESSION}  (Ctrl+B D to detach)"
if [ "$NO_WATCHDOG" = true ]; then
  echo "  Watchdog:    disabled (--no-watchdog)"
elif [ "$OPENCLAW_AVAILABLE" = true ]; then
  echo "  Watchdog:    openclaw cron (every 5 min)"
else
  echo "  Watchdog:    not registered (openclaw not found)"
fi
echo ""


path: scripts/squad-status.sh
content: #!/bin/bash
# squad-status.sh — Check squad status, peek at screen, nudge if needed
# Usage: squad-status.sh <squad-name>

set -euo pipefail

SQUAD_NAME="${1:?Usage: squad-status.sh <squad-name>}"

# --- Validate squad name ---
if [[ ! "$SQUAD_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  echo "ERROR: Invalid squad name '$SQUAD_NAME'. Use lowercase alphanumeric with hyphens."
  exit 1
fi

SQUAD_DIR="${HOME}/.openclaw/workspace/agent-squad/squads/${SQUAD_NAME}"
TMUX_SESSION="squad-${SQUAD_NAME}"

# --- Check squad exists ---
if [ ! -d "$SQUAD_DIR" ]; then
  echo "ERROR: Squad '$SQUAD_NAME' not found."
  exit 1
fi

# --- Read engine and project_dir from squad.json (safe via sys.argv) ---
ENGINE="unknown"
PROJECT_DIR=""
if [ -f "$SQUAD_DIR/squad.json" ] && command -v python3 &>/dev/null; then
  ENGINE=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('engine', 'unknown'))" "$SQUAD_DIR/squad.json" 2>/dev/null || echo "unknown")
  PROJECT_DIR=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('project_dir', ''))" "$SQUAD_DIR/squad.json" 2>/dev/null || echo "")
fi

# --- tmux status ---
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
  TMUX_STATUS="running"
else
  TMUX_STATUS="stopped"
fi

# --- Count tasks ---
count_files() {
  local dir="$1"
  find "$dir" -maxdepth 1 -name "task-*.md" -type f 2>/dev/null | wc -l | tr -d ' '
}

PENDING=$(count_files "$SQUAD_DIR/tasks/pending")
IN_PROGRESS=$(count_files "$SQUAD_DIR/tasks/in-progress")
DONE=$(count_files "$SQUAD_DIR/tasks/done")

# --- Current status from reports ---
CURRENT_REPORT=""
for report in "$SQUAD_DIR"/reports/task-*.md; do
  [ -f "$report" ] || continue
  if grep -qi "in-progress" "$report" 2>/dev/null; then
    CURRENT_SECTION=$(sed -n '/^## Current/,/^## /{ /^## Current/d; /^## /d; p; }' "$report" 2>/dev/null | head -5) || true
    if [ -n "$CURRENT_SECTION" ]; then
      REPORT_NAME=$(basename "$report")
      CURRENT_REPORT="  Report: ${REPORT_NAME}
${CURRENT_SECTION}"
    fi
  fi
done

# --- Output ---
echo "Squad: ${SQUAD_NAME}"
echo "Engine: ${ENGINE}"
echo "Status: ${TMUX_STATUS}"
if [ -n "$PROJECT_DIR" ]; then
  echo "Project: ${PROJECT_DIR}"
fi
echo "Tasks: ${PENDING} pending, ${IN_PROGRESS} in-progress, ${DONE} done"

if [ -n "$CURRENT_REPORT" ]; then
  echo ""
  echo "Current activity:"
  echo "$CURRENT_REPORT"
fi

# --- Peek at live screen (if running) ---
if [ "$TMUX_STATUS" = "running" ]; then
  SCREEN_OUTPUT=$(tmux capture-pane -t "$TMUX_SESSION" -p 2>/dev/null | sed '/^$/d' | tail -5) || true
  if [ -n "$SCREEN_OUTPUT" ]; then
    echo ""
    echo "Live screen (last 5 lines):"
    echo "$SCREEN_OUTPUT" | sed 's/^/  /'
  fi
fi

# --- Check for blocked tasks ---
for report in "$SQUAD_DIR"/reports/task-*.md; do
  [ -f "$report" ] || continue
  if grep -qi "^## Blocked" "$report" 2>/dev/null; then
    echo ""
    echo "WARNING: $(basename "$report") has a ## Blocked section!"
  fi
done

# --- Nudge: remind to keep working if tasks remain ---
if [ "$TMUX_STATUS" = "running" ]; then
  REMAINING=$((PENDING + IN_PROGRESS))
  if [ "$REMAINING" -gt 0 ]; then
    {
      tmux send-keys -t "$TMUX_SESSION" Escape 2>/dev/null || true
      sleep 0.5
      tmux send-keys -t "$TMUX_SESSION" "You have ${REMAINING} task(s) remaining (${PENDING} pending, ${IN_PROGRESS} in-progress). Keep working — do not stop until all tasks are done. Update your report with current progress." Enter
    } 2>/dev/null || true
  fi
fi


path: scripts/squad-stop.sh
content: #!/bin/bash
# squad-stop.sh — Stop a squad (keep all data)
# Usage: squad-stop.sh <squad-name>

set -euo pipefail

SQUAD_NAME="${1:?Usage: squad-stop.sh <squad-name>}"

# --- Validate squad name ---
if [[ ! "$SQUAD_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  echo "ERROR: Invalid squad name '$SQUAD_NAME'. Use lowercase alphanumeric with hyphens."
  exit 1
fi

TMUX_SESSION="squad-${SQUAD_NAME}"
SQUAD_DIR="${HOME}/.openclaw/workspace/agent-squad/squads/${SQUAD_NAME}"
CRON_NAME="squad-watchdog-${SQUAD_NAME}"

# --- Kill tmux session ---
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
  tmux kill-session -t "$TMUX_SESSION"
  echo "Stopped tmux session: $TMUX_SESSION"
else
  echo "tmux session '$TMUX_SESSION' was not running."
fi

# --- Remove watchdog cron ---
if command -v openclaw &>/dev/null; then
  openclaw cron remove --name "$CRON_NAME" 2>/dev/null && \
    echo "Removed watchdog cron: $CRON_NAME" || \
    echo "No watchdog cron found for '$CRON_NAME'."
fi

# --- Log the stop event ---
if [ -d "$SQUAD_DIR/logs" ]; then
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S+00:00)] Squad stopped by user." >> "$SQUAD_DIR/logs/watchdog.log"
fi

echo ""
echo "Squad '$SQUAD_NAME' stopped. All data preserved at:"
echo "  $SQUAD_DIR"
echo ""
echo "To restart, just say: \"Restart $SQUAD_NAME\""


path: scripts/squad-watchdog.sh
content: #!/bin/bash
# squad-watchdog.sh — Health check and auto-restart for a squad
# Called by openclaw cron every 5 minutes
# Usage: squad-watchdog.sh <squad-name>

set -euo pipefail

SQUADS_DIR="${HOME}/.openclaw/workspace/agent-squad/squads"
SQUAD_NAME="${1:?Usage: squad-watchdog.sh <squad-name>}"

# --- Validate squad name ---
if [[ ! "$SQUAD_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
  echo "ERROR: Invalid squad name '$SQUAD_NAME'."
  exit 1
fi

SQUAD_DIR="${SQUADS_DIR}/${SQUAD_NAME}"
TMUX_SESSION="squad-${SQUAD_NAME}"
LOG_FILE="${SQUAD_DIR}/logs/watchdog.log"

# --- Log rotation (max ~5MB, keep 1 backup) ---
if [ -f "$LOG_FILE" ]; then
  LOG_SIZE=$(wc -c < "$LOG_FILE" 2>/dev/null | tr -d ' ')
  if [ "${LOG_SIZE:-0}" -gt 5242880 ]; then
    mv "$LOG_FILE" "${LOG_FILE}.1"
  fi
fi

# --- Check squad directory exists ---
if [ ! -d "$SQUAD_DIR" ]; then
  echo "ERROR: Squad directory not found: $SQUAD_DIR"
  exit 1
fi

# --- Check python3 ---
if ! command -v python3 &>/dev/null; then
  echo "ERROR: python3 is required but not found."
  exit 1
fi

# --- Read squad config (safe via sys.argv) ---
SQUAD_JSON="$SQUAD_DIR/squad.json"
if [ ! -f "$SQUAD_JSON" ]; then
  echo "ERROR: squad.json not found in $SQUAD_DIR"
  exit 1
fi

# Read engine_command, engine_path, project_dir, agent_teams from squad.json
eval "$(python3 -c "
import json, sys, shlex
d = json.load(open(sys.argv[1]))
print(f'ENGINE={shlex.quote(d.get(\"engine\", \"\"))}')
print(f'ENGINE_PATH={shlex.quote(d.get(\"engine_path\", \"\"))}')
print(f'ENGINE_CMD={shlex.quote(d.get(\"engine_command\", \"\"))}')
print(f'PROJECT_DIR={shlex.quote(d.get(\"project_dir\", \"\"))}')
print(f'AGENT_TEAMS={\"true\" if d.get(\"agent_teams\") else \"false\"}')
" "$SQUAD_JSON" 2>/dev/null)"

if [ -z "$ENGINE_CMD" ]; then
  echo "ERROR: Could not read engine_command from squad.json"
  exit 1
fi

# Verify the engine binary still exists at the stored path
if [ -n "$ENGINE_PATH" ] && [ ! -x "$ENGINE_PATH" ]; then
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S+00:00)] WARNING: Engine binary not found at $ENGINE_PATH" >> "$LOG_FILE"
  echo "ERROR: Engine binary not found at stored path: $ENGINE_PATH"
  exit 1
fi

if [ -z "$PROJECT_DIR" ] || [ ! -d "$PROJECT_DIR" ]; then
  PROJECT_DIR="$SQUAD_DIR"
fi

# Build env-prefixed command
TMUX_CMD="env SQUAD_DIR='${SQUAD_DIR}'"
if [ "$AGENT_TEAMS" = "true" ]; then
  TMUX_CMD="$TMUX_CMD CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"
fi
TMUX_CMD="$TMUX_CMD $ENGINE_CMD"

RESTART_PROMPT="You are ${SQUAD_NAME}, a persistent AI development coordinator. Your coordination directory is ${SQUAD_DIR} — read ${SQUAD_DIR}/PROTOCOL.md immediately for your complete instructions. Read ${SQUAD_DIR}/logs/coordinator-summary.md if it exists to resume. Check ${SQUAD_DIR}/tasks/pending/ and ${SQUAD_DIR}/tasks/in-progress/. Write code in ${PROJECT_DIR}. Continue working."

# --- Consecutive failure tracking ---
FAIL_COUNT_FILE="${SQUAD_DIR}/logs/.restart-failures"
MAX_FAILURES=3

record_failure() {
  local count=0
  [ -f "$FAIL_COUNT_FILE" ] && count=$(cat "$FAIL_COUNT_FILE" 2>/dev/null | tr -d ' \n')
  count=$(( ${count:-0} + 1 ))
  echo "$count" > "$FAIL_COUNT_FILE"
  echo "$count"
}

reset_failures() {
  rm -f "$FAIL_COUNT_FILE"
}

warn_user() {
  local count="$1"
  echo ""
  echo "============================================="
  echo "  ALERT: Squad '${SQUAD_NAME}' keeps crashing"
  echo "============================================="
  echo ""
  echo "The watchdog has restarted '${SQUAD_NAME}' ${count} times in a row"
  echo "but it keeps failing. This usually means:"
  echo ""
  echo "  1. The engine ($ENGINE) has a configuration issue"
  echo "  2. The engine requires interactive input that can't run in tmux"
  echo "  3. The project directory has a problem"
  echo ""
  echo "Suggested actions:"
  echo "  - Check the log: cat ${LOG_FILE}"
  echo "  - Try running the engine manually: cd ${PROJECT_DIR} && ${ENGINE_CMD}"
  echo "  - Stop the squad to prevent further retries: /agent-squad stop ${SQUAD_NAME}"
  echo ""
  echo "IMPORTANT: Please notify the user about this issue."
  echo "============================================="
  echo ""
}

# --- Check tmux session ---
if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
  # Session exists — check if the engine is still running inside
  PANE_PID="$(tmux list-panes -t "$TMUX_SESSION" -F '#{pane_pid}' 2>/dev/null | head -1)"
  if [ -n "$PANE_PID" ]; then
    if pgrep -P "$PANE_PID" >/dev/null 2>&1; then
      # Engine is running, all good
      reset_failures
      exit 0
    fi
  fi

  # tmux session exists but engine has exited — restart engine inside existing session
  FAIL_COUNT=$(record_failure)
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S+00:00)] Engine exited in session $TMUX_SESSION. Restarting... (failure #${FAIL_COUNT})" >> "$LOG_FILE"

  if [ "$FAIL_COUNT" -ge "$MAX_FAILURES" ]; then
    echo "[$(date -u +%Y-%m-%dT%H:%M:%S+00:00)] ALERT: ${FAIL_COUNT} consecutive failures, notifying user." >> "$LOG_FILE"
    warn_user "$FAIL_COUNT"
    exit 1
  fi

  tmux send-keys -t "$TMUX_SESSION" "$TMUX_CMD" Enter
  sleep 5
  tmux send-keys -t "$TMUX_SESSION" "$RESTART_PROMPT" Enter
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S+00:00)] Engine restarted in existing session." >> "$LOG_FILE"
  echo "RESTARTED: Engine was dead inside tmux session. Restarted (attempt #${FAIL_COUNT})."
  exit 0
fi

# --- tmux session does not exist — full restart ---
FAIL_COUNT=$(record_failure)
echo "[$(date -u +%Y-%m-%dT%H:%M:%S+00:00)] Session $TMUX_SESSION not found. Full restart... (failure #${FAIL_COUNT})" >> "$LOG_FILE"

if [ "$FAIL_COUNT" -ge "$MAX_FAILURES" ]; then
  echo "[$(date -u +%Y-%m-%dT%H:%M:%S+00:00)] ALERT: ${FAIL_COUNT} consecutive failures, notifying user." >> "$LOG_FILE"
  warn_user "$FAIL_COUNT"
  exit 1
fi

tmux new-session -d -s "$TMUX_SESSION" -c "$PROJECT_DIR" "$TMUX_CMD"

{
  sleep 5
  tmux send-keys -t "$TMUX_SESSION" "$RESTART_PROMPT" Enter
} &

echo "[$(date -u +%Y-%m-%dT%H:%M:%S+00:00)] Full restart completed." >> "$LOG_FILE"
echo "RESTARTED: tmux session was gone. Created new session and started engine (attempt #${FAIL_COUNT})."


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)