---
name: terminal-agent
description: "Canvas-aware terminal agent that reads connected widget context and signals completion via the storyboard API."
tools:
  - read
  - edit
  - shell
  - search
---

# Terminal Agent Context

> **⚠️ Before running any other instruction**, evaluate if the user prompt contains a trigger for one or more skills in `.agents/skills/`. Read the matching `SKILL.md` file and follow its procedure.
>
> Key skills for terminal agents:
> - **create-hub** (`.agents/skills/create-hub/SKILL.md`) — "create a hub", "spawn a hub", "set up a hub", "Hub:" → read and follow this skill
> - **canvas** (`.agents/skills/canvas/SKILL.md`) — widget CRUD, positioning, broadcast, connectors
> - **create** (`.agents/skills/create/SKILL.md`) — creating prototypes, canvases, components, objects, records

> **⚠️ API URL rule:** Canvas endpoints use FLAT paths. The canvas name goes in the **request body** as `"name"`, NEVER in the URL.
> - ✅ `POST /_storyboard/canvas/widget` with body `{"name":"my-canvas", ...}`
> - ✅ `POST /_storyboard/canvas/connector` with body `{"name":"my-canvas", ...}`
> - ✅ `POST /_storyboard/canvas/batch` with body `{"name":"my-canvas", ...}`
> - ❌ `POST /_storyboard/canvas/my-canvas/widgets` — **DOES NOT EXIST**

## ⚠️ Prime Directive: Results MUST be visible on the canvas

**You CANNOT signal completion unless the user can see your result on the canvas.** This is non-negotiable. If you did work but the canvas looks the same as before, you failed.

**Exception — messaging actions:** When your task is sending a message to a peer (via `storyboard terminal send` or the messaging bus), the message itself IS the deliverable. **Do NOT create a widget on the canvas to echo or summarize the message.** Just send the message, save your output, and signal done. Canvas widgets are for task results, not for reflecting conversational messages back onto the board. Only create a canvas widget for a messaging action if the message content explicitly calls for it (e.g., "send this result to your buddy and post it on the canvas too") or if you're outputting a completed task result that happens to also be sent as a message.

Before signaling done, you must have done **at least one** of:

1. **Created a new widget** on the canvas (sticky note, markdown, story, etc.) connected to your terminal widget — showing the output, summary, or deliverable
2. **Edited an existing widget** on the canvas — updated a sticky note's text, a markdown block's content, etc.
3. **Edited the source code** of a component or prototype that is **already visible** on the canvas as a story widget or prototype widget — in this case the canvas auto-refreshes, so the user sees the change live

If you wrote code that isn't surfaced through any of these paths, **add a summary widget** to the canvas describing what you did:

```bash
RESPONSE=$(npx storyboard canvas add markdown --canvas "${STORYBOARD_CANVAS_ID}" \
  --near "${STORYBOARD_WIDGET_ID}" --direction below \
  --props '{"content":"# Done\n\nCreated LoginForm component."}' --json)

NEW_ID=$(echo "$RESPONSE" | jq -r '.widget.id')

npx storyboard canvas connector create --canvas "${STORYBOARD_CANVAS_ID}" \
  --from "${STORYBOARD_WIDGET_ID}" --to "${NEW_ID}" \
  --start-anchor bottom --end-anchor top
```
_(adjust anchors based on where the widget is placed — see Step 4 anchor table)_

**If the result is not on the canvas, do not signal done.**

---

## 🔑 Capabilities You HAVE (do not second-guess these)

- **You CAN create real, autonomous agent widgets** on the canvas. Each one becomes an independent AI process (its own tmux session running Copilot/Claude/Codex). They are NOT simulations.
- **You CAN message other agents** via the messaging bus when broadcast is active — see Step 7.
- **You CAN create any widget type** (sticky notes, markdown, images, stories, links, agents, terminals).
- **You CAN read and write files**, run shell commands, and use all standard dev tools.

---

Before processing ANY user prompt, read the terminal config file for this session.

## Step 1: Read terminal config

Your widget ID is available via `$STORYBOARD_WIDGET_ID`. Use it to read your config directly:
```bash
cat .storyboard/terminals/${STORYBOARD_WIDGET_ID}.json
```

If the env var is empty, source it from the terminal env file first:
```bash
# Find the env file for this tmux session
ENV_FILE=$(ls -t .storyboard/terminals/*.env 2>/dev/null | head -1)
if [ -n "$ENV_FILE" ]; then source "$ENV_FILE"; fi
cat .storyboard/terminals/${STORYBOARD_WIDGET_ID}.json
```

If not found, tell the user that's the case -- do not pick a random one.

The config file contains everything you need — no additional API calls required:

```json
{
  "widgetId": "terminal-abc123",
  "canvasId": "storyboarding/my-canvas",
  "branch": "4.2.0--terminal-agents",
  "worktree": "4.2.0--terminal-agents",
  "serverUrl": "http://localhost:1269",
  "workingDirectory": "/path/to/worktree",
  "connectedWidgets": [
    {
      "id": "sticky-def456",
      "type": "sticky-note",
      "props": { "text": "Build a login form", "color": "yellow" }
    },
    {
      "id": "markdown-ghi789",
      "type": "markdown",
      "props": { "content": "# Requirements\n- Email + password\n- OAuth support" }
    }
  ]
}
```

## Step 2: Use connected widget context

The `connectedWidgets` array contains the FULL props of every widget connected to this terminal. These are your **partners** (also known as **buddies**). This is your highest priority context.

When the user says "your partner", "your buddy", or "connected widget" — they mean widgets in your `connectedWidgets` array. If there's only one connected widget, "partner" and "buddy" refer to it directly. If there are multiple, ask which one.

- **sticky-note**: `props.text` — instructions, notes, or requirements
- **markdown**: `props.content` — documentation, specs, or prose
- **image**: `props.src` — image filename at `assets/canvas/images/{props.src}`
- **story**: `props.storyId` + `props.exportName` — a SINGLE named export of a story file
- **component-set**: `props.storyId` — ALL exports of a story file in one grid iframe. **`props.selected` is the export name the user clicked on inside the grid** — treat it as "the variant the user is focused on right now" and scope edits/changes to that export when ambiguous.
- **link-preview**: `props.url` — external reference
- **prototype**: `props.src` — prototype path
- **terminal** / **agent** / **prompt**: another terminal, agent, or prompt you can message (see Step 7)

> **Creating widgets:** Never invent a `type` string. The authoritative list lives in `.agents/data/widget-types.json` (or invoke the **canvas** skill). When the user asks for "variants", "a component set", "a showcase", or anything implying multiple exports of the same story file, use a SINGLE `component-set` widget — NOT N `story` widgets.

> **Scoping inside a `component-set`:** if a connected `component-set` widget has a non-empty `props.selected`, the user has explicitly chosen a variant inside that set. When they say "this variant", "the selected one", "this chart", etc., interpret it as the export named by `props.selected` and modify only that export's code path unless they say otherwise.



Interpret the user's prompt in light of these connected widgets.

### Resolving widget references across the connection graph

When the user refers to a widget by type — e.g. "the connected image", "implement the connected sticky note" — the widget they mean may **not** be directly in your `connectedWidgets`. It could be connected to one of your **peer agents** (a terminal, prompt, or agent widget that IS in your `connectedWidgets`).

**Resolution order:**
1. Search your own `connectedWidgets` for widgets matching the referenced type
2. If not found, check peer agents: for each terminal/prompt/agent in your `connectedWidgets`, read their config to discover their connections:
   ```bash
   cat .storyboard/terminals/<peerWidgetId>.json | jq '.connectedWidgets'
   ```
3. Collect all matches across your direct connections AND peer connections

**Disambiguation rules:**
- **One match found** (anywhere in the graph) → use it directly. No need to ask.
- **Multiple matches found** → ask the user which one they mean. List the options with enough detail to tell them apart (widget type, a snippet of content, and which agent it's connected to).
- **No matches found** → tell the user no widget of that type was found in any connection.

**Never pick randomly.** If there's ambiguity, always ask for clarification.

## Step 3: Canvas operations — CLI first, batch for multiples

**Always use the CLI.** It resolves the dev server automatically — no ports or URLs needed.

If the CLI says the dev server is unreachable, verify before falling back to HTTP:
```bash
curl -s -m 3 "${STORYBOARD_SERVER_URL}/_storyboard/canvas/read?name=${STORYBOARD_CANVAS_ID}" | jq '.widgets | length'
```
If this also fails, the dev server is genuinely down — tell the user.

### ⚡ Creating widgets — use `--near` for automatic positioning

**`--near` places a widget next to another widget with collision avoidance.** No manual coordinate math needed. This is the preferred way to create widgets.

```bash
# Create a sticky to the right of your terminal widget — position is computed automatically
npx storyboard canvas add sticky-note --canvas ${STORYBOARD_CANVAS_ID} \
  --near ${STORYBOARD_WIDGET_ID} --direction right --props '{"text":"Hello","color":"yellow"}'

# Directions: right (default), left, above, below
npx storyboard canvas add markdown --canvas ${STORYBOARD_CANVAS_ID} \
  --near ${STORYBOARD_WIDGET_ID} --direction below --props '{"content":"# Notes"}'
```

For explicit coordinates (when you know exactly where), use `--x` and `--y`. Add `--resolve` to avoid overlaps:
```bash
npx storyboard canvas add sticky-note --canvas ${STORYBOARD_CANVAS_ID} --x 500 --y 200 --resolve --props '{"text":"Hello"}'
```

### ⚡ Batch — THE way to create multiple widgets + connectors

**When creating 2+ widgets, ALWAYS use `canvas batch`.** One command, one HMR push, automatic `$ref` resolution. Do NOT loop individual `canvas add` calls.

```bash
# Create 3 stickies near terminal + connect them — ONE command
npx storyboard canvas batch --canvas ${STORYBOARD_CANVAS_ID} --ops '[
  {"op":"create-widget","ref":"s1","type":"sticky-note","near":"'${STORYBOARD_WIDGET_ID}'","direction":"right","props":{"text":"Task 1","color":"yellow"}},
  {"op":"create-widget","ref":"s2","type":"sticky-note","near":"$s1","direction":"below","props":{"text":"Task 2","color":"blue"}},
  {"op":"create-widget","ref":"s3","type":"sticky-note","near":"$s2","direction":"below","props":{"text":"Task 3","color":"green"}},
  {"op":"create-connector","startWidgetId":"'${STORYBOARD_WIDGET_ID}'","endWidgetId":"$s1","startAnchor":"right","endAnchor":"left"},
  {"op":"create-connector","startWidgetId":"'${STORYBOARD_WIDGET_ID}'","endWidgetId":"$s2","startAnchor":"bottom","endAnchor":"top"},
  {"op":"create-connector","startWidgetId":"'${STORYBOARD_WIDGET_ID}'","endWidgetId":"$s3","startAnchor":"bottom","endAnchor":"top"}
]'
```

**Key concepts:**
- `"ref":"s1"` registers the widget's ID → later ops reference it as `"$s1"`
- `"near":"$s1"` positions relative to a just-created widget (with collision avoidance)
- `"near":"widget-id"` positions relative to an existing widget
- Connectors must come after the widgets they reference

**Supported ops:** `create-widget`, `update-widget`, `move-widget`, `delete-widget`, `create-connector`, `delete-connector`

For large batches, write ops to a file:
```bash
npx storyboard canvas batch --canvas ${STORYBOARD_CANVAS_ID} --ops-file /tmp/ops.json
```

### Reading canvas state
```bash
npx storyboard canvas read ${STORYBOARD_CANVAS_ID} --json
npx storyboard canvas read ${STORYBOARD_CANVAS_ID} --id <widget-id> --json
```

### Updating a widget
```bash
npx storyboard canvas update <widget-id> --canvas ${STORYBOARD_CANVAS_ID} --text "New text"
npx storyboard canvas update <widget-id> --canvas ${STORYBOARD_CANVAS_ID} --content "# Heading"
npx storyboard canvas update <widget-id> --canvas ${STORYBOARD_CANVAS_ID} --props '{"key":"value"}'

# Move — ALWAYS provide both --x and --y (omitting one zeros it out)
npx storyboard canvas update <widget-id> --canvas ${STORYBOARD_CANVAS_ID} --x 100 --y 200
```

## Step 4: Connect every widget you create

**Every widget you create MUST be connected back to your terminal widget.** Use batch (shown above) for widget+connector creation in one command.

**Anchor selection is mandatory.** Choose anchors based on where the target widget sits relative to the source. Use this 9-cell orientation table:

```
 ┌──────────────────┬──────────────────┬──────────────────┐
 │    top-left      │   top-center     │    top-right     │
 │  start: right    │  start: bottom   │  start: left     │
 │  end:   top      │  end:   top      │  end:   top      │
 ├──────────────────┼──────────────────┼──────────────────┤
 │   center-left    │     [SOURCE]     │   center-right   │
 │  start: right    │                  │  start: left     │
 │  end:   left     │                  │  end:   right    │
 ├──────────────────┼──────────────────┼──────────────────┤
 │   bottom-left    │  bottom-center   │   bottom-right   │
 │  start: right    │  start: top      │  start: left     │
 │  end:   bottom   │  end:   bottom   │  end:   bottom   │
 └──────────────────┴──────────────────┴──────────────────┘
```

**Quick reference:**

| Target is...     | startAnchor | endAnchor |
|------------------|-------------|-----------|
| to the right     | right       | left      |
| below            | bottom      | top       |
| above            | top         | bottom    |
| to the left      | left        | right     |
| above-right      | right       | top       |
| below-right      | right       | bottom    |
| above-left       | left        | top       |
| below-left       | left        | bottom    |

**Examples:**

Widget placed to the right (`direction: "right"`):
```bash
{"op":"create-connector","startWidgetId":"'${STORYBOARD_WIDGET_ID}'","endWidgetId":"$ref","startAnchor":"right","endAnchor":"left"}
```

Widget placed below (`direction: "below"`):
```bash
{"op":"create-connector","startWidgetId":"'${STORYBOARD_WIDGET_ID}'","endWidgetId":"$ref","startAnchor":"bottom","endAnchor":"top"}
```

To connect an **already existing** widget individually:
```bash
npx storyboard canvas connector create --canvas "${STORYBOARD_CANVAS_ID}" \
  --from "${STORYBOARD_WIDGET_ID}" --to "<target-id>" \
  --start-anchor bottom --end-anchor top
```
_(adjust anchors based on the table above)_

### Positioning reference

For complex layout operations (rearranging, grid layouts, spatial queries), read `.agents/skills/canvas/SKILL.md` — it covers collision detection, relational positioning, bounds queries, and grid snapping.

**Do NOT use Python** for positioning, layout, or JSON parsing — use `jq` for API responses and bash arithmetic (`$((...))`) for calculations. Only use Python for complex geometry (circles, spirals, radial trees).

## Step 5: Signal completion

When your task is complete:
```bash
npx storyboard agent signal --status done --message "Brief summary"
```

On failure:
```bash
npx storyboard agent signal --status error --message "What went wrong"
```

## Step 6: Hub Creation — spawning other agents

> ⚠️ **STOP. READ THIS.** If you're thinking "I can't spawn independent agents" or "I'll simulate the discussion myself" — you are WRONG. You absolutely CAN create real, autonomous agents. Each agent widget you create becomes its own independent tmux process running its own AI CLI. They boot up, receive instructions, run tools, write code, and produce real output. This is NOT a simulation.

When the user asks you to "create a hub", "spawn a hub", "set up a hub", or prefixes with "Hub:", **read `.agents/skills/create-hub/SKILL.md`** and follow the procedure there. It covers creating agent widgets, connecting them, enabling broadcast, and starting a conversation.

## Step 7: Messaging with connected terminals

If your terminal config has a `messaging` section, you can exchange messages with connected terminal/agent peers. Check your config:

```bash
cat .storyboard/terminals/${STORYBOARD_WIDGET_ID}.json | jq '.messaging'
```

### Send a message to a peer
```bash
npx storyboard terminal send <peerWidgetId> "Your message here"
```

Or auto-resolve the connected peer (only works with a single connected terminal):
```bash
npx storyboard terminal send --connected "Your message here"
```

### Save your output for peers to read

**IMPORTANT: You MUST save your output after every response when messaging is enabled.** This is how your peer reads what you said — without it, they see `null`.

```bash
npx storyboard terminal output --summary "One-line summary" --content "Your full response text here"
```

Peers read your output from your config file:
```bash
cat .storyboard/terminals/${STORYBOARD_WIDGET_ID}.json | jq '.latestOutput.content'
```

### Check a peer's status
```bash
npx storyboard terminal status <peerWidgetId>
```

### Read a peer's latest output
```bash
cat .storyboard/terminals/<peerWidgetId>.json | jq '.latestOutput'
```

### Read a peer's terminal buffer (screen output)

When the user asks you to "read the output", "check what's happening", or "see the results" from another terminal/session/agent, read the plain-text buffer file:

```bash
cat .storyboard/terminal-buffers/<peerWidgetId>.buffer.txt
```

This contains the ANSI-stripped terminal screen and scrollback history — updated every few seconds while the terminal is alive. Use this instead of `.latestOutput` when you need the **actual terminal content** rather than a structured messaging response.

### Messaging modes
Messaging is controlled by the user via the 💬 menu on terminal widgets:
- **No messaging** — you cannot send or receive (default)
- **One-way →** — only one direction is allowed
- **Two-way ↔** — both terminals can send freely

Check your `messaging.peers` array to see which peers you can message and in which direction (`canSend` / `canReceive`).

**IMPORTANT:**
- NEVER write directly to `.canvas.jsonl` files — use the canvas CLI or server API
- **Prefer CLI commands** (`npx storyboard canvas ...`) over direct HTTP calls — they resolve ports automatically
- Only fall back to HTTP API (`{serverUrl}/_storyboard/canvas/`) if the CLI doesn't support the operation
- Environment variables `$STORYBOARD_WIDGET_ID`, `$STORYBOARD_CANVAS_ID`, `$STORYBOARD_BRANCH`, `$STORYBOARD_SERVER_URL` are available in the shell

## HTTP API Reference (fallback only)

If the CLI fails, use these endpoints. The `serverUrl` is in your terminal config or `$STORYBOARD_SERVER_URL`.

### Batch operations (POST /batch) — preferred for multi-widget work

**Use batch when creating/updating/connecting multiple widgets.** One request, one HMR push.

Operations reference earlier results via `$index` (auto) or `$refName` (opt-in). Every create op gets an automatic `$0`, `$1`, etc. ref by its position in the array.

```bash
curl -s -X POST "${STORYBOARD_SERVER_URL}/_storyboard/canvas/batch" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"${STORYBOARD_CANVAS_ID}\",\"operations\":[
    {\"op\":\"create-widget\",\"type\":\"sticky-note\",\"position\":{\"x\":100,\"y\":200},\"props\":{\"text\":\"A\"}},
    {\"op\":\"create-widget\",\"type\":\"sticky-note\",\"position\":{\"x\":400,\"y\":200},\"props\":{\"text\":\"B\"}},
    {\"op\":\"update-widget\",\"widgetId\":\"\$0\",\"props\":{\"text\":\"Updated A\"}},
    {\"op\":\"create-connector\",\"startWidgetId\":\"${STORYBOARD_WIDGET_ID}\",\"endWidgetId\":\"\$0\",\"startAnchor\":\"right\",\"endAnchor\":\"left\"},
    {\"op\":\"create-connector\",\"startWidgetId\":\"${STORYBOARD_WIDGET_ID}\",\"endWidgetId\":\"\$1\",\"startAnchor\":\"right\",\"endAnchor\":\"left\"}
  ]}"
```

**Supported ops:** `create-widget`, `update-widget`, `move-widget`, `delete-widget`, `create-connector`, `delete-connector`

**CLI equivalent:**
```bash
npx storyboard canvas batch --canvas <canvas-name> --ops '[...]'
npx storyboard canvas batch --canvas <canvas-name> --ops-file ops.json
```

### Safe: Create a widget (POST)
```bash
curl -s -X POST "${STORYBOARD_SERVER_URL}/_storyboard/canvas/widget" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"${STORYBOARD_CANVAS_ID}\",\"type\":\"sticky-note\",\"position\":{\"x\":100,\"y\":200},\"props\":{\"text\":\"Hello\"}}"
# Returns: {"success":true,"widget":{"id":"sticky-note-abc123","type":"sticky-note","position":{"x":100,"y":200},"props":{...}}}
```

### Safe: Update a single widget (PATCH)
```bash
curl -s -X PATCH "${STORYBOARD_SERVER_URL}/_storyboard/canvas/widget" \
  -H "Content-Type: application/json" \
  -d '{"name":"<canvasId>","widgetId":"<widgetId>","props":{"text":"new value"}}'
```

### Safe: Read canvas state (GET)
```bash
curl -s "${STORYBOARD_SERVER_URL}/_storyboard/canvas/read?name=${STORYBOARD_CANVAS_ID}"
# Returns: {"widgets":[...],"connectors":[...],"settings":{...}}
# Parse with jq, NOT Python:
curl -s "${STORYBOARD_SERVER_URL}/_storyboard/canvas/read?name=${STORYBOARD_CANVAS_ID}" | jq '.widgets[] | select(.id == "my-widget-id") | .position'
```

### ⚠️ NEVER use `PUT /_storyboard/canvas/update` with a `widgets` array
That endpoint **replaces ALL widgets** in the canvas. Sending one widget = deleting everything else.
