{"id":"68f89d6a-676e-4e6f-860e-1ed642e58e06","slug":"cc-xray","kind":"skill","name":"cc-xray","summary":"Watch a Claude Code turn as it happens: model, effort, tokens and timing per action, with forked skills nested.","owner":"dontfeedthecode","repoUrl":"https://github.com/dontfeedthecode/cc-xray","license":"MIT","licenseTier":"permissive","trust":"community","stars":2,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-09-23T05:37:40.000Z","addedAt":"2026-10-21T23:55:04.742Z","logoUrl":"https://avatars.githubusercontent.com/u/1808467?u=d4c99b27135893e2887e23987ff3acaf662dd9ae&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["developer-tools"],"tags":[],"runtime":"go","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T19:18:32.221Z","sample":false,"url":"https://conduits.dev/tools/cc-xray","language":"Go","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# X-ray for Claude Code\n\nWatch a Claude Code turn as it happens, in a terminal tab beside it.\n\nClaude Code shows you a scrolling conversation. `ccxray` shows you the\n*shape* of the turn: which model and effort level are actually in force, what\neach action cost in tokens and time, and — the part that is otherwise\ninvisible — the moment a skill changes the model or effort out from under you.\n\n```\n▎ Run the test suite and fix whatever fails.\n\n  MODEL             ACTION                                           OUT      Δt\n  ────────────────────────────────────────────────────────────────────────────\n  opus-5 (high)     Run the full test suite                          103    2.0s\n× opus-5 (high)     Re-run the failing package with -race             12    1.0s\n  opus-5 (high)     Skill  audit-deps                                 84    1.4s\n  ╰─▶ ╭─ forked → sonnet-5 (medium)  ·  audit-deps  ·  agent a8aa6be7\n      │✎ Check every module against the advisory database            410   20.3s\n      │  Read  go.sum                                                747    8.5s\n      ╰─ returned  9 req  ·  3,437 out  ·  49k ctx  ·  1m 11s\n✎ opus-5 (high)     answer  Two modules are behind, the test fail… 1,512       —\n  ────────────────────────────────────────────────────────────────────────────\n  turn complete   15 req  ·  63k ctx                               6,080  1m 48s\n```\n\nMost actions are shell commands, so `Bash` (and `PowerShell` on Windows) is\nthe assumed default and goes unnamed — the description is the useful part and\ngets the space. Anything else announces itself: `Read`, `Grep`, `Skill`, an\nMCP tool. `✎` marks a row the model thought before, `×` marks a call that\nfailed.\n\nIt is **read-only**. It tails the transcript Claude Code already writes and\nnever modifies anything. It runs on macOS, Linux and Windows.\n\n## Install\n\n**macOS and Linux**\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/dontfeedthecode/cc-xray/main/install.sh | sh\n```\n\n**Windows** (in PowerShell)\n\n```powershell\nirm https://raw.githubusercontent.com/dontfeedthecode/cc-xray/main/install.ps1 | iex\n```\n\nEither one downloads the prebuilt binary for your machine (`amd64` or\n`arm64`), checks it against the release's checksums, and puts it in\n`~/.local/bin` (`~\\.local\\bin` on Windows). There is nothing to build and\nno `sudo` or admin prompt. Then run `ccxray`, in a new terminal on Windows.\n\n- **`ccxray` not found?** On macOS and Linux, the installer doesn't edit your\n  shell profile. If `~/.local/bin` isn't on your `PATH`, it prints the line\n  to add; for zsh, the macOS default, that is\n  `echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> ~/.zshrc && source ~/.zshrc`.\n  On Windows, the installer adds the folder to your user `PATH` itself, and\n  only terminals opened afterwards see the change.\n- **From inside Claude Code,** put `!` in front to run the command in place.\n  On Windows `!` runs Git Bash, so wrap the PowerShell line:\n  `! powershell -c \"irm https://raw.githubusercontent.com/dontfeedthecode/cc-xray/main/install.ps1 | iex\"`\n- **Options:** `CCXRAY_INSTALL_DIR` installs somewhere else, and\n  `CCXRAY_VERSION=vX.Y.Z` pins a release. In PowerShell, set them first, as\n  in `$env:CCXRAY_VERSION = 'vX.Y.Z'`.\n- **With Go:** `go install github.com/dontfeedthecode/cc-xray/cmd/ccxray@latest`\n  puts it in `$(go env GOPATH)/bin`. Binaries for every platform are also on\n  the [Releases](https://github.com/dontfeedthecode/cc-xray/releases) page.\n- **Uninstall** by deleting the binary.\n\n## Use\n\nOpen a second terminal tab **in the directory Claude Code is working in**, and run:\n\n```sh\nccxray\n```\n\nIt starts empty and attaches to the first session Claude Code writes after\nlaunch — start ccxray, then send your prompt — and keeps up when you `/clear`.\nEarlier runs are never replayed; use `--session <id>` for one of those.\nIt watches the directory it starts in and every parent up to `$HOME`, never\nsubdirectories: start it where Claude Code runs, or deeper, not above it.\nEditors like Zed and VS Code open terminals at the workspace root, so if\nClaude Code runs in a subfolder of your workspace, `cd` there first, or pass\n`--project <dir>`.\n\nTo start it from anywhere, pass `--all`: it then attaches to the first\nsession written after launch in any project. The directory rule exists so\nthat a second Claude Code window in another project can't take the panel\nover; with `--all` it can, once the session being followed has been quiet\nfor 20 seconds.\n\n| | |\n|---|---|\n| `↑` `↓` `k` `j` | scroll a line |\n| `pgup` `pgdn` | scroll a page |\n| `g` / `G` | jump to the top / follow the live edge |\n| `u` | show or hide the usage breakdown by model |\n| `c` | clear the panel and wait for the next session to write |\n| `q` | quit |\n\nThe panel follows new rows like `tail -f`; scrolling up pauses that so\nincoming rows don't yank the view away, and `G` resumes.\n\n```\nccxray --project <dir>     # a directory other than the current one\nccxray --all               # any project, wherever ccxray was started\nccxray --session <id>      # pin to one existing session instead of waiting\nccxray --ascii             # plain glyphs for terminals that widen box-drawing\n```\n\n## What it shows\n\n**Model and effort on every row.** Usually unchanging — which is the point.\nWhen something does change it, a band names both values.\n\n**Forked skills, nested** under the call that launched them, on the model and\neffort the fork actually ran on.\n\n**When forks overlapped.** Two or more forks in a turn get a lane each below\nthe table on one time axis, with the peak that ran at once. Nothing in the\ntranscript says forks ran in parallel; overlapping spans are the only\nevidence, so that is what the lanes measure.\n\n**A skill that asked for an effort or model it didn't get** is flagged under\nits call. Claude Code drops these silently. A `model:` is skipped in auto\nmode when auto mode doesn't support it, or when an allowlist excludes it. And,\nundocumented, a skill the model calls itself applies its `effort:` only some\nof the time; one you invoke as `/name` has applied it every time. ccxray reads the skill's `SKILL.md` as it is\nnow, so an edit made after the run can make the flag name the wrong value.\n\n**Δt per action**, so a 20-second step is obvious. **Failures** `×`,\n**thinking** `✎`. The model's words before a call sit faintly beneath it.\n\n**Cost, for the turn and the session.** The footer prices the turn (forks\nincluded) and the session so far; `u` breaks both down by model into input,\noutput, cache reads and cache writes, as `/usage` does. Claude Code only\nwrites its own total when you leave a session, so a live session's figure is\npriced by ccxray from the transcript and marked `~`: it runs a few percent\nunder `/usage`, which also counts calls Claude Code never writes to the\ntranscript, such as title generation. Once Claude Code's total is there it\nis used as is. Prices are API rates; on a subscription they show what the\ntokens would cost, not what you are billed.\n\nA turn ends when you type the next prompt and nothing else — a background\nagent reporting back mid-turn does not start a new one. The exception is a\nskill forked into the background: while it runs, anything you type joins\nthe turn that launched it, drawn inline as `▎ your prompt`, so the fork\nkeeps streaming in place and its return (`▸ returned`) and the model's\nanswer to it land where you can see them.\n\n## Requirements\n\n- [Claude Code](https://claude.com/claude-code) (validated against `2.1.278`)\n- macOS, Linux or Windows, `amd64` or `arm64`\n\nOn Windows, use Windows Terminal (the default on Windows 11). If an older\nconsole window draws boxes where the glyphs should be, run with `--ascii`.\n\nThe transcript format is internal to Claude Code and changes between\nversions, so `ccxray` parses defensively: unknown fields are ignored, a bad\nline is skipped rather than fatal, and a version mismatch shows a notice.\n\n## How it works\n\nClaude Code writes every session to\n`~/.claude/projects/<slugged-cwd>/<session-id>.jsonl` (under\n`%USERPROFILE%` on Windows, where `C:\\work\\app` slugs to `C--work-app`),\nappending as the turn runs. `ccxray` tails that file, tracking a byte offset\nso it only parses what is new, and rebuilds the turn from it.\n\nForked skills live in a `subagents/` directory beside the session file, named\nby an `agentId` the parent transcript carries, so the correlation is exact.\nA fork is announced two different ways — `toolUseResult.status == \"forked\"`\nwhen a `Skill` call launched it, and a `<forked-skill-launch>` payload on a\n`system`/`local_command` record when a slash command did.\n\nCounting tokens correctly turns out to be the fiddly part:\n\n1. **Dedupe by `requestId`.** One assistant message is written as several\n   records, and `output_tokens` is repeated in full on every one. Summing per\n   record roughly doubles the total.\n2. **`cache_read_input_tokens` is a peak, not a sum.** Every request re-reads\n   the whole cache.\n3. **A segment's duration is last record − first record** within it, never the\n   gap to the next segment, which would swallow time you spent idle.\n4. **`system` / `turn_duration`** is authoritative for the turn total.\n\nEach produced plausible, wrong numbers before it was fixed. The test suite\nasserts the exact figures of a known turn to keep them fixed.\n\nDeciding what counts as a prompt is the other fiddly part. Claude Code writes\nplenty into `user` records that nobody typed: skill bodies, command echoes,\nattachment notes, background-agent notifications, and the summary handed\nacross a `/compact`. A prompt with an image attached is a block array rather\nthan a bare string, so both shapes are read. A slash command is a real prompt\nwhen work follows it and noise when it does not, so it opens a turn only once\nthe model acts on it.\n\nA `/compact` shows up as the first row of the next turn, with the context it\ndropped — the reason the footer's context figure just collapsed.\n\n## Trying it out\n\nA real turn is whatever you happen to be doing, so the repo ships a skill\nthat drives a deliberately varied one. Start Claude Code in this directory\nand run `/ccxray-demo`. It works through plain shell calls, several named\ntools, a deliberate failure, and two skills that move the model and effort\nout from under the turn.\n\nIt needs [Go](https://go.dev/dl/) (on Windows, `winget install GoLang.Go`),\nand it sets up the rest itself. It first checks that ccxray is running. If\nit isn't, the skill installs it from this checkout with `go install` when no\n`ccxray` is found, prints the command to start it in a second terminal, and\nwaits for you to say it's running. If Go was installed after Claude Code\nstarted, Claude Code's shell can't see it yet; the skill says so, and\nrestarting Claude Code from a new terminal fixes it.\n\nTo run the checkout without installing it, use `go run ./cmd/ccxray` from\nthis directory.\n\nThose skills live in `.claude/skills/` here rather than `~/.claude/skills`, so\nthey arrive with a clone. Claude Code loads them from the working directory at\nstartup — start it **from this directory**, and restart after a pull.\n\nTwo frontmatter keys matter, and they do not behave the same way:\n\n| key | effect |\n| --- | --- |\n| `model:` | inert on its own; applies only with `context: fork` |\n| `effort:` | applies without forking, but only if the skill is resolved before the turn's first request |\n\nThat second condition is why a slash command always works and a `Skill` call\nonly does when the model reaches for it immediately: one preceding request is\nenough to lose the override silently.\n\n`ccxray-demo-probe` sets both and forks; `ccxray-demo-effort` sets only\n`effort:` and does not fork. Watch the MODEL column across both.\n\n## Development\n\n```sh\ngo test ./...\ngo run ./cmd/ccxray\n```\n\n`internal/turn/testdata/session.jsonl` is **synthetic**: the token arithmetic\nand record structure come from a real session, but every prompt, reply, path\nand identifier is fabricated. `internal/ui/testdata/fork-agent.jsonl` is a\nreal forked-skill transcript with its paths, tool output and system context\nreplaced — a fork's records differ in shape from a main session's.\n\nReleases are cut by pushing a `v*` tag; [GoReleaser](https://goreleaser.com)\nbuilds the binaries and checksums that `install.sh` downloads.\n\n## Licence\n\nMIT — see [LICENSE](LICENSE).\n"}