Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 21 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "team-avatar",
"interface": {
"displayName": "Team Avatar"
},
"plugins": [
{
"name": "team-avatar",
"source": {
"source": "local",
"path": "./plugins/team-avatar"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL",
"products": ["CODEX"]
},
"category": "Developer Tools"
}
]
}
16 changes: 16 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "team-avatar",
"owner": {
"name": "Trent McFarlane",
"url": "https://github.com/tmcfarlane"
},
"description": "Team Avatar: principal-level engineering skills, each owned by a character from Avatar: The Last Airbender.",
"plugins": [
{
"name": "team-avatar",
"source": "./plugins/team-avatar",
"description": "Principal-level engineering skills for Claude Code and Codex. Aang routes the work, and each character owns one discipline.",
"version": "1.0.0"
}
]
}
43 changes: 43 additions & 0 deletions .github/workflows/validate-team-avatar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: validate-team-avatar

on:
push:
branches: [main]
paths:
- "plugins/team-avatar/**"
- ".claude-plugin/**"
- ".agents/plugins/**"
- "scripts/validate-team-avatar.mjs"
- "scripts/generate-team-avatar.mjs"
- ".github/workflows/validate-team-avatar.yml"
pull_request:
paths:
- "plugins/team-avatar/**"
- ".claude-plugin/**"
- ".agents/plugins/**"
- "scripts/validate-team-avatar.mjs"
- "scripts/generate-team-avatar.mjs"
- ".github/workflows/validate-team-avatar.yml"

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Validate skills, agents, manifests, and house style
run: node scripts/validate-team-avatar.mjs

tools:
runs-on: ubuntu-latest
defaults:
run:
working-directory: plugins/team-avatar/tools
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun test
- run: bun run typecheck
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
# Changelog

All notable changes to oh-my-cursor are documented here. Format follows
All notable changes to Team Avatar (formerly oh-my-cursor) are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/); this project uses [SemVer](https://semver.org/).

> **Validated against Cursor 3.9.8** (latest) — every release is verified on a real
> Cursor build before tagging. See [`VALIDATION.md`](VALIDATION.md) for the per-version
> compatibility matrix and the 2-minute re-validation check.

## [1.0.0] - 2026-10-24

Team Avatar becomes a universal agent orchestrator for Claude Code and Codex, and the repository is renamed from `oh-my-cursor` to `team-avatar`. The Cursor edition below still ships unchanged.

We took [pstack](https://github.com/cursor/plugins/tree/main/pstack) by Lauren Tan, through Michael Denyer's [Claude Code and Codex port](https://github.com/michael-denyer/pstack-claude), and put Avatar characters on top of it. It probably made it worse. It is definitely cooler.

### Added
- **Team Avatar plugin** (`plugins/team-avatar`) for Claude Code and Codex: 58 skills owned by 9 characters, with `avatar-mode` as the entry point that routes each task. Every pstack skill and playbook has a Team Avatar skill, credited in `NOTICE.md`.
- **Suki**, a ninth character, for parallel squads and long autonomous runs.
- A session-start hook that turns avatar mode on, and `avatar-setup` to pick a model per character.
- A skill registry (`team.json`), a generator that keeps every roster table in sync, and a validator that runs in CI.
- **Evidence.** A 29-case end-to-end suite (29 of 29 on both tools), an A/B benchmark with hidden graders, and SWE-bench Verified Mini runs comparing Team Avatar, pstack, and no plugin. No plugin raised the SWE-bench score by a significant margin, and both plugins cost more per task. See `docs/evidence/`.

## [0.4.2] — 2026-06-28

**Validated against:** Cursor 3.9.8 ([reference](VALIDATION.md))
Expand Down
494 changes: 250 additions & 244 deletions README.md

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions docs/evidence/release-v1.0.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Team Avatar v1.0.0: screenshots

These are real terminal sessions, not mockups. Each tool got the same small Node repo with three independent bugs and one failing test per bug:

| File | Bug |
|------|-----|
| `src/cart.js` | `Math.floor` turns $19.99 into 1998 cents |
| `src/slug.js` | `replace(" ", "-")` only replaces the first space |
| `src/paginate.js` | page 1 skips the first page |

Each tool got one prompt that asked for one Katara per module in parallel ([Claude prompt](recordings/demo-prompt.txt), [Codex prompt](recordings/codex-prompt.txt)). Neither run was edited or retried after it started.

## Claude Code

Plain Claude Code with only Team Avatar loaded (`--setting-sources project,local --plugin-dir plugins/team-avatar`). The agent panel at the bottom shows three `team-avatar:katara` agents running at once. The whole run took 53 seconds and ended with 3 of 3 tests passing and only the three source files changed.

![Claude Code running three Katara subagents](claude-subagents-running.png)

![Claude Code final report](claude-result.png)

## Codex

Codex with the installed `team-avatar` plugin. It reads `kyoshi-formation`, makes one git worktree per module, and starts `katara_cart`, `katara_slug` and `katara_paginate`. The session log has three child rollouts that point back at the parent thread. The run took 2 minutes 9 seconds and ended with 3 passed, 0 failed.

![Codex starting three Katara agents](codex-subagents-start.png)

![Codex final report](codex-result.png)

## How they were made

[`recordings/record.py`](recordings/record.py) runs each CLI in a real pseudo terminal at 150 by 46 and saves every byte of output with a timestamp. It answers the first-run dialogs (trust this folder, skip external imports, keep the current permission mode) and nothing else. [`recordings/render.mjs`](recordings/render.mjs) replays the bytes into xterm.js in headless Chrome and screenshots a chosen second. The raw recordings are in this folder, so any other frame can be rendered with:

```bash
node docs/evidence/release-v1.0.0/recordings/render.mjs docs/evidence/release-v1.0.0/recordings/claude-demo.json frame.png 20 "Claude Code"
```

Replay can leave small drawing artifacts where the live UI redrew a spinner line, such as stray strike-through lines. The text is what the CLI printed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evidence/release-v1.0.0/codex-result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/evidence/release-v1.0.0/recordings/claude-demo.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/evidence/release-v1.0.0/recordings/codex-demo.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/evidence/release-v1.0.0/recordings/codex-prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$kyoshi-formation npm test has three failing tests, one each in src/cart.js, src/slug.js and src/paginate.js. They are independent. Fan them out: dispatch one Katara subagent (agents/katara.md) per module in parallel, have each reproduce its failing test, find the root cause and make the smallest fix, then verify the whole suite passes and summarize what each character found. Do not commit.
1 change: 1 addition & 0 deletions docs/evidence/release-v1.0.0/recordings/demo-prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/team-avatar:kyoshi-formation npm test has three failing tests, one each in src/cart.js, src/slug.js and src/paginate.js. They are independent. Fan them out: dispatch one team-avatar:katara subagent per module in parallel, have each reproduce its failing test, find the root cause and make the smallest fix, then verify the whole suite passes and summarize what each character found. Do not commit.
37 changes: 37 additions & 0 deletions docs/evidence/release-v1.0.0/recordings/record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os, pty, sys, time, select, fcntl, termios, struct, json, re
out, cols, rows, maxsec = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])
cmd = sys.argv[5:]
pid, fd = pty.fork()
if pid == 0:
os.environ["TERM"] = "xterm-256color"
os.execvp(cmd[0], cmd)
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
start = time.time(); last = start; chunks = []; buf = b""; answered = set()
while time.time() - start < maxsec:
r, _, _ = select.select([fd], [], [], 1.0)
if fd in r:
try: data = os.read(fd, 65536)
except OSError: break
if not data: break
chunks.append([round(time.time() - start, 3), data.decode("utf-8", "replace")])
buf = (buf + data)[-20000:]
low = buf.lower()
plain = re.sub(rb"\x1b\[[0-9;?>]*[A-Za-z]", b" ", buf).lower()
if b"\x1b[c" in data: os.write(fd, b"\x1b[?62;22c")
if b"trust" in plain and b"folder" in plain and "trust" not in answered:
answered.add("trust"); time.sleep(0.8); os.write(fd, b"\x1b[B"); time.sleep(0.4); os.write(fd, b"\r")
if b"file imports?" in plain and "imports" not in answered:
answered.add("imports"); time.sleep(0.8); os.write(fd, b"\r")
if b"your default permission mode?" in plain and "auto" not in answered:
answered.add("auto"); time.sleep(1.5); os.write(fd, b"2"); time.sleep(1.0); os.write(fd, b"\r")
last = time.time()
elif chunks and time.time() - start > 90 and time.time() - last > 60:
break
try:
done, status = os.waitpid(pid, os.WNOHANG)
if done:
print("child exited", status, file=sys.stderr); break
except ChildProcessError: break
json.dump({"cols": cols, "rows": rows, "chunks": chunks}, open(out, "w"))
try: os.kill(pid, 9)
except ProcessLookupError: print("child already exited", file=sys.stderr)
21 changes: 21 additions & 0 deletions docs/evidence/release-v1.0.0/recordings/render.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// node render.mjs <recording.json> <out.png> <seconds|end> <title>
import { readFileSync, writeFileSync } from "node:fs";
import { execFileSync } from "node:child_process";
const [rec, out, at, title] = process.argv.slice(2);
const r = JSON.parse(readFileSync(rec, "utf8"));
const upto = at === "end" ? Infinity : Number(at);
const data = r.chunks.filter(([t]) => t <= upto).map(([, d]) => d).join("");
const html = `<!doctype html><meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.js"></script>
<style>body{margin:0;background:#1e1e2e;font-family:Menlo,monospace}.bar{background:#313244;color:#bac2de;padding:8px 14px;font:600 14px Menlo}#t{padding:10px}</style>
<div class="bar">${title}</div><div id="t"></div>
<script>
const term = new Terminal({cols:${r.cols},rows:${r.rows},fontFamily:'Menlo, monospace',fontSize:14,theme:{background:'#1e1e2e',foreground:'#cdd6f4'},allowProposedApi:true});
term.open(document.getElementById('t'));
term.write(${JSON.stringify(data)});
</script>`;
const page = out.replace(/\.png$/, ".html");
writeFileSync(page, html);
execFileSync("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", ["--headless=new", "--disable-gpu", "--hide-scrollbars", "--virtual-time-budget=8000", `--window-size=${Math.ceil(r.cols * 8.43 + 40)},${r.rows * 17 + 70}`, `--screenshot=${out}`, `file://${page}`], { stdio: "ignore" });
console.log(out);
33 changes: 33 additions & 0 deletions docs/evidence/team-avatar-bench/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Team Avatar benchmark: what the numbers say

This is an A/B benchmark. The same tasks ran in Claude Code and Codex, three times each, with and without Team Avatar, and hidden graders scored the work. The runner is `evals/team-avatar/bench/`. [round-1](round-1/README.md) and [round-2](round-2/README.md) hold every number and every run.

## Headline

There is no task-score lift to market yet. Round 2 scored 99 / 99 / 100 on Claude Code and 96 / 87 / 87 on Codex. Each trio reads without Team Avatar, then installed, then asked by name.

- **The tasks are too easy.** Both tools already reach 100 on most of them without help. That includes a review with 10 subtle planted bugs. A ceiling like this cannot show a lift. Small synthetic repos do not separate frontier agents, which is why the next step is SWE-bench Verified.
- **Codex with Team Avatar refused one task.** On the performance task, Codex found the 250x faster fix every time. But the prompt said "do not change its behavior", and the fix calls the key function fewer times. So Codex left the code alone and asked whether that was allowed. Plain Codex shipped the fix in 2 of 3 runs. The grader counts this as a loss. A reader can argue it is the more careful answer.

## Where Team Avatar does change behavior

| Measure | Claude Code without, installed, asked by name | Codex without, installed, asked by name |
|---------|--------------------|--------------|
| Replies with no long dash | 86%, 86%, 100% | 71%, 90%, 100% |
| Bug fix ships a regression test that fails on the old code | 0%, 0%, 33% | 100%, 100%, 100% |
| Tokens per run | 162k, 261k, 315k | 180k, 540k, 989k |
| Time per run | 36s, 47s, 51s | 89s, 135s, 216s |

The style rules take hold fully when the user asks for avatar mode by name. Team Avatar also costs more. On Claude Code it adds 40% time and about 2x tokens. On Codex it takes up to 2.4x time and 5x tokens. Codex with Team Avatar also left scratch benchmark folders in the repo (1.1 stray files per run when asked by name).

## How the rounds changed the plugin

- Round 1 showed that installing the plugin made Claude Code load no skills at all. The old session hook told it to skip skills for small tasks. The hook now tells it to load the matching skill for any engineering task. In round 2, Claude Code loaded a skill in every installed run.
- Round 1 showed the Codex refusal. A new `breath-of-fire` rule says to ship and name an unstated detail, instead of asking about it. That rule did not change Codex's answer, because the user's explicit "do not change behavior" outranks it.

## Fairness

- Every setup ran with the other workflow plugins on this machine switched off (pstack, tstack, oh-my-claudecode), so Team Avatar is the only difference.
- Prompts never name a skill, except in the "asked by name" setup.
- Each grader was checked against a known-good and a known-bad answer before any agent ran. For example, a symptom patch scores 38 and a root-cause fix scores 100.
- N is 3 per cell. Treat a gap smaller than one run's swing as noise.
35 changes: 35 additions & 0 deletions docs/evidence/team-avatar-bench/round-1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Team Avatar benchmark

Scores are 0 to 100: the mean of 3 runs per cell, with the lowest and highest run in brackets. Every run started from the same fresh repo and the same prompt. "Without" is the tool with no workflow plugins. "Installed" adds only the Team Avatar plugin and leaves the prompt alone. "Asked by name" also starts the prompt with avatar mode. Hidden graders score the work after the agent finishes, and the agent never sees them.

- **root-cause-fix.** Fixes the cause, not the symptom. Hidden tests hit the other callers of the same helper.
- **review-recall.** Share of 6 planted bugs the review reports.
- **review-subtle.** Share of 10 subtle planted bugs the review reports (races, auth, timing-safe compare, ReDoS, money floats, time zones).
- **test-strength.** Half correctness on hidden tests, half the share of 8 broken implementations the agent's own tests catch.
- **perf-without-regression.** Full score needs every hidden behavior test (NaN keys, -0, first occurrence wins) and a 10x speedup.
- **prose-quality.** Share of AI-tell phrases removed, times share of true facts kept.
- **investigate-not-guess.** Gets the real answer (3000, because NaN || 3000) and cites both lines that decide it.

| Task | Claude Code: Without Team Avatar | Claude Code: Team Avatar installed | Codex: Without Team Avatar | Codex: Team Avatar installed |
|------|---:|---:|---:|---:|
| root-cause-fix | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> |
| review-recall | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> |
| review-subtle | n/a | n/a | n/a | n/a |
| test-strength | 50 <sub>(50-50)</sub> | 50 <sub>(50-50)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> |
| perf-without-regression | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 70 <sub>(11-100)</sub> | 10 <sub>(8-11)</sub> |
| prose-quality | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> |
| investigate-not-guess | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> | 100 <sub>(100-100)</sub> |
| **All tasks** | **92** | **92** | **95** | **85** |

## Engineering discipline

| Tool | Setup | Bug fix ships a regression test | Perf reply reports a measured number | Replies with no long dash | Stray files left per run | Time per run | Tokens per run |
|------|-------|---:|---:|---:|---:|---:|---:|
| Claude Code | Without Team Avatar | 0% | 0% | 100% | 0.0 | 31s | 171k, $0.24 |
| Claude Code | Team Avatar installed | 0% | 0% | 100% | 0.0 | 33s | 214k, $0.29 |
| Codex | Without Team Avatar | 0% | 0% | 100% | 0.0 | 81s | 208k |
| Codex | Team Avatar installed | 0% | 0% | 100% | 0.0 | 145s | 650k |

A regression test counts only when the agent's tests fail against the original buggy code. Stray files are files outside the ones the task needed, such as scratch scripts left in the repo.

Every run, its score, and the grader's reason are in [runs.csv](runs.csv).
Loading
Loading