Skip to content

jnyross/multishooter

v0.1.0

Present focused evidence and collect typed review decisions from coding agents.

MultiShooter

MultiShooter is a narrow, active-only review surface for Codex. A skill or tool publishes the evidence needed for the current decision; the user reads and answers in a calm vertical browser view; a typed receipt returns to the owning Codex flow. The browser stores no decision history.

The first slice supports prose, one or more raster images, choice/approval asks, one active review process, and a manual-link fallback. It deliberately excludes archives, dashboards, generic document editing, sharing, and transcript scraping.

Quickstart

Prerequisites: Bun 1.3 or newer and this trusted project checkout.

bun install
bun run doctor
bun run example:hello -- --manual-link

Open the one printed loopback URL, choose an answer, and submit it. The CLI prints the typed receipt, holds the acknowledgement briefly, closes the server, and removes the active browser state.

Without --manual-link, the example opens the operating-system browser. That does not claim native Codex-browser support. The project-scoped MCP server returns the URL so Codex can open it in its own browser pane.

Install as a Codex plugin

Prerequisite: Bun 1.3 or newer, and Codex CLI v0.142.0 or newer for the root-local marketplace path (source.path: "./").

Add this repository as a marketplace:

codex plugin marketplace add jnyross/MultiShooter

Open /plugins in Codex, select the MultiShooter marketplace, and install the MultiShooter plugin. Start a new Codex session after installation so its bundled MCP tools are discovered.

The portable package follows Agent Plugins 1.0: plugin.json identifies the plugin and mcp.json declares its local MCP server. Codex uses the repository marketplace only for distribution. The installed plugin runs the bundled dist/mcp.js with dist/renderer/*; it does not depend on checkout-local paths or a generated Codex config.

For development from a trusted source checkout, generate the checkout-local Codex MCP registration:

bun install
bun run codex:sync-config

.codex/config.toml is a development fallback with absolute paths for the current checkout. Start a new Codex session after changing it. The Mac mini dogfood lab notes live in docs/dogfood/mac-mini-lab.md. The tools are:

  • multishooter_doctor
  • multishooter_present
  • multishooter_present_gstack_plan_design
  • multishooter_answer
  • multishooter_wait
  • multishooter_close

The MCP server supports one active surface. present returns a capability URL, present_gstack_plan_design maps one gstack decision brief into the validated v0 contract without asking the skill to hand-author packet JSON, answer lets Codex answer one explicit ask through the same stale/idempotency validation as the browser, wait performs bounded 25-second reads, and close revokes the current state. MCP receipt delivery is classified trusted_unverified because Codex does not expose transcript-delivery proof.

Author API

import {
  CallbackHostAdapter,
  choice,
  option,
  presentReview,
  prose,
} from "./src";

const session = await presentReview(
  {
    title: "Choose the interaction model",
    evidence: [prose("Keep Codex primary and the review active-only.")],
    ask: choice("interaction model", [
      option("vertical", "Vertical review workspace", {
        recommended: true,
      }),
      option("canvas", "Open canvas"),
    ]),
  },
  {
    hostAdapter: new CallbackHostAdapter((receipt) => {
      console.log(JSON.stringify(receipt));
    }),
  },
);

console.log(session.url);
const answer = await session.waitForAnswer();
await session.acknowledgeAndClose(answer);

A recommendation is never a selection. Submission remains disabled until the user deliberately chooses an answer.

CallbackHostAdapter callbacks must synchronously complete their receipt side effect; use the optional abort signal to stop before writing. Async callbacks are rejected so a timed-out delivery cannot be reported as verified after the callback continues in the background.

For image evidence, use a logical assetId in the packet and pass a generated artifact root plus its root-relative path:

const session = await presentReview(packet, {
  assets: {
    root: "/absolute/generated-artifacts",
    paths: { "hero-image": "hero.png" },
  },
});

The browser receives only a fresh opaque asset ID; filesystem paths never enter the page.

Development

bun run typecheck
bun run test
bun run test:e2e
bun run build

The Playwright test exercises 600, 720, and 800px layouts, keyboard-operable controls, deliberate selection, response delivery, and axe accessibility.

Documentation

Plannotator-derived lifecycle code and its MIT notice are recorded in THIRD_PARTY_NOTICES.md.