Give your coding agent x-ray vision into your workflows, code paths, and cross-process timing — the runtime behavior it otherwise has to guess at. Fire small labeled events from anywhere in your stack and let the agent drain a clean, ordered timeline of what actually happened.
xray is a small HTTP server you run on your machine. From anywhere in your stack (back-end, front-end, a worker, a shell script, in any language) you fire those events with a one-line helper, then "develop the x-ray": drain the buffered events and read what actually happened. Because every process points at the same relay, you watch a single user action travel from a browser click, through your API, into a background job, on one timeline.
It pairs especially well with coding agents (Claude Code): xray init teaches the
agent the workflow, then you say "x-ray this" and it instruments, reproduces, and
drains on its own (see Use it with your coding agent).
npx @julio_ody/xray # start the relay on 127.0.0.1:7200
curl -s 127.0.0.1:7200/health # confirm it is up
xray drain # read new events (or `xray tail` for a live stream)
See packages/relay/README.md for the full user guide and WIRE_FORMAT.md for the event v2 contract.
A customer checks out successfully, but the order never gets fulfilled. The bug
spans the browser, the API, and a background worker — print debugging shows each
piece in isolation but not the order across them. So instrument all three with a
shared trace:
// browser — loaded via <script src="http://127.0.0.1:7200/xray.js"></script>
window.__XRAY_TRACE__ = crypto.randomUUID()
window.xray('checkout.submitted', { orderId })// api (Node) — npm i -D @julio_ody/xray
import { xray, setTrace } from '@julio_ody/xray/client'
setTrace(req.headers['x-trace']) // the same id, forwarded from the browser
xray('payment.authorized', { orderId, amount })# worker (Ruby) — vendored via `xray init --lang ruby`
Xray.emit('fulfillment.started', order_id: order_id)
Xray.emit('fulfillment.failed', error: e.message) # XRAY_TRACE set on the worker processReproduce the checkout, then drain the timeline:
$ xray drain
[0] web checkout.submitted trace=9f2a {"orderId":"ord_8821"}
[1] api payment.authorized trace=9f2a +41ms {"orderId":"ord_8821","amount":4200}
[2] worker fulfillment.started trace=9f2a +12ms {"orderId":"ord_8821"}
[3] worker fulfillment.failed trace=9f2a +3ms {"error":"SKU out of stock"}One timeline, three processes, in real order: checkout and payment went through fine — the worker picked up the order and failed because the item was out of stock. Fix it, reproduce again, drain again. When you're done, remove the helper calls (they no-op in production anyway).
Reads are non-destructive, so a teammate running xray tail in another terminal
sees the same events without stealing them from your drain.
xray init wires xray into a repo and, importantly, teaches your coding agent how
to use it. It writes a short xray block into your CLAUDE.md / AGENTS.md — the
terminology, the event conventions, and the instrument → reproduce → drain loop —
then vendors a helper for your stack. With --hook it also installs a Claude Code
UserPromptSubmit hook so new events inject into the agent's context automatically.
xray init # detects your stack; --lang <l> to override, --hook for ambient modeAgent instruction files are read at startup, so restart your agent (or /clear)
after init to load the new block. After that, say "x-ray this" in a session and
the agent follows the loop on its own: instrument the suspect paths, ask you to
reproduce, drain the timeline, iterate, and remove the calls when done. Helpers are
dev-only and no-op unless enabled (XRAY_ENABLED=1).
A pnpm + Turbo workspace with one published package, packages/relay
(npm @julio_ody/xray): the relay + xray CLI, bundled to a single
dependency-free file, plus the typed JS/TS client exposed at the @julio_ody/xray/client
subpath (import { xray } from '@julio_ody/xray/client').
It also carries packages/relay/templates — the
vendorable, dependency-free helper sources for browser, Ruby, Python, Go, Rust, PHP,
and shell that xray init writes into a project and the relay serves at /xray.<ext>.
pnpm install
pnpm build # bundle the CLI/relay and the client (tsup)
pnpm test # vitest
pnpm typecheck
pnpm lint
pnpm format
pnpm xray # run the relay from source (tsx)
pnpm release:patch # bump 0.1.0 -> 0.1.1, commit, tag vX.Y.Z, push, publish
pnpm release:minor # 0.x bump
pnpm release:major # x.0 bump
pnpm release # re-publish the CURRENT version (e.g. after a failed publish)
Requires Node >= 24 and pnpm 10. Conventions (ESLint flat config, Prettier, TSConfig base) live at the repo root.
The published version lives in packages/relay/package.json. pnpm release:<type>
runs npm version <type> there (bumps the version, commits release: vX.Y.Z, and
creates the vX.Y.Z git tag), then git push --follow-tags, then pnpm -r publish.
A prepublishOnly build guarantees a fresh bundle. npm version and pnpm both
require a clean working tree, and npm 2FA (your security key) prompts at publish
time. If publish fails after the tag is pushed, re-run pnpm release to publish the
already-bumped version.
The event v2 wire format — the real cross-language contract — is documented in WIRE_FORMAT.md.