Skip to content

Commit 5f214b7

Browse files
gewenyu99claude
andcommitted
docs(e2e-harness): add the agent exploration runbook
EXPLORING-AS-AN-AGENT.md — a runbook for an agent that wants to run/drive/explore the wizard headlessly: ask the user for a key file path + set env, then either a full `wizard-ci --e2e` run or a hand-driven read_state→perform_action loop, with renderFrame to snapshot the TUI for itself to view. Gives wizard-ci-tools its documented use (agentic exploration). Recipe smoke-tested (intro → health-check, renders the real screen). ARCHITECTURE.md points at it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6e88d7d commit 5f214b7

2 files changed

Lines changed: 135 additions & 0 deletions

File tree

e2e-harness/ARCHITECTURE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ it runs the WHOLE interactive flow headlessly via `wizard-ci-tools` and asserts
66
on structured state — not the classic `--ci` mode (LoggingUI, stdout-grep,
77
agent-only).
88

9+
> If you're an agent that just wants to **run and explore the wizard** (drive
10+
> it, view the screens, snapshot it), start with the runbook:
11+
> [`EXPLORING-AS-AN-AGENT.md`](EXPLORING-AS-AN-AGENT.md). This doc is the _how
12+
> it works_ underneath.
13+
914
## The pieces
1015

1116
This whole harness lives in `e2e-harness/` at the repo root — deliberately OUT
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Driving & exploring the wizard as an agent
2+
3+
A runbook for a future AI agent (you) that wants to **run the real wizard
4+
headlessly, drive its state, and snapshot the TUI to view it** — to explore or
5+
test the app with no terminal. It uses the control plane in this folder
6+
(`WizardCiDriver` + the `wizard-ci-tools` MCP server). For _how_ it works under
7+
the hood, read [`ARCHITECTURE.md`](ARCHITECTURE.md); this is the _how to do it_.
8+
9+
## 0. Ask for the key, then set up
10+
11+
**First, ask the user for the path to their PostHog key file** — e.g. "What's
12+
the absolute path to your phx key file?" — plus the project id and region if you
13+
don't have them. Then, in the shell you'll run from:
14+
15+
```bash
16+
export POSTHOG_PERSONAL_API_KEY="$(cat <key-file-path>)" # the phx key
17+
export POSTHOG_WIZARD_PROJECT_ID=<project-id> # the team the key is scoped to
18+
export POSTHOG_REGION=us # or eu
19+
export WIZARD_PATH=<this wizard repo> # where e2e-harness/ lives
20+
```
21+
22+
Rules: **never print or commit the key.** Always run against a **`/tmp` copy**
23+
of an app, never a real fixture. If you're inside a Claude Code session, the
24+
harness strips the host `CLAUDE_*`/`ANTHROPIC_*` env for the child so the
25+
spawned agent auths with the phx key (the `apiKeySource: none` → 401 trap).
26+
27+
## 1. Full run, then view it (the easy path)
28+
29+
From [wizard-workbench](https://github.com/PostHog/wizard-workbench):
30+
31+
```bash
32+
pnpm wizard-ci <app> --e2e # real agent, headless; writes a recording
33+
pnpm wizard-ci-snapshots <app> # renders each key moment → .ans + report.html
34+
```
35+
36+
To watch it back:
37+
`pnpm wizard-ci --replay /tmp/wizard-e2e-<app>.recording.json --step`, or just
38+
read the `.ans` frames / `report.html`. This is the whole flow, real agent, no
39+
decisions for you to make.
40+
41+
## 2. Drive it yourself (the control plane)
42+
43+
To step the flow and **decide each screen**, use the three primitives —
44+
`read_state`, `list_actions`, `perform_action`. They're exposed as the
45+
`wizard-ci-tools` MCP server (`createWizardCiToolsServer`) for a connected
46+
driver model; the same primitives are `WizardCiDriver` methods you can call
47+
directly from a tsx script. The loop is always:
48+
49+
```
50+
read_state → look at currentScreen + the legal actions → perform_action(one of them) → read_state → …
51+
```
52+
53+
Recipe — write it to a scratch file **inside this repo** so the `@lib`/`@ui`/
54+
`@e2e-harness` aliases resolve (a `/tmp` file won't see the tsconfig). Name it
55+
`scripts/explore.no-jest.ts` (the `.no-jest` suffix keeps Jest from picking it
56+
up), run `npx tsx scripts/explore.no-jest.ts` from `$WIZARD_PATH`, then delete
57+
it. It drives the **UI** screens offline (no agent/auth) and renders each one so
58+
you can see it:
59+
60+
```ts
61+
import { WizardStore } from '@ui/tui/store';
62+
import { InkUI } from '@ui/tui/ink-ui';
63+
import { setUI } from '@ui/index';
64+
import { buildSession } from '@lib/wizard-session';
65+
import { Program } from '@lib/programs/program-registry';
66+
import { WizardCiDriver } from '@e2e-harness/wizard-ci-driver';
67+
import { WizardRecorder } from '@e2e-harness/recorder';
68+
import { renderFrame } from '@e2e-harness/replay';
69+
70+
async function main() {
71+
const store = new WizardStore(Program.PostHogIntegration);
72+
setUI(new InkUI(store));
73+
store.session = buildSession({ installDir: '/tmp/app-copy', ci: true });
74+
await store.runReadyHooks(); // real framework detection
75+
76+
const rec = new WizardRecorder(store, { program: 'posthog-integration' });
77+
rec.start();
78+
const driver = new WizardCiDriver(store);
79+
80+
// LOOK: where am I, and what can I commit?
81+
console.log(
82+
driver.readState().currentScreen,
83+
driver.listActions().map((a) => a.id),
84+
);
85+
86+
// ACT: name an action from list_actions (not a keystroke)
87+
driver.performAction('confirm_setup');
88+
console.log(
89+
driver.readState().currentScreen,
90+
driver.listActions().map((a) => a.id),
91+
);
92+
// …repeat: read_state → decide → perform_action…
93+
94+
// VIEW: render every captured frame as the real TUI (ANSI) so you can see it
95+
rec.stop();
96+
for (const f of rec.getRecording().frames) {
97+
console.log(
98+
`\n=== ${f.screen} ===\n` + renderFrame(f, Program.PostHogIntegration),
99+
);
100+
}
101+
}
102+
main();
103+
```
104+
105+
`auth` and `run` are _external_ steps (the runner sets credentials, the agent
106+
sets run state) — for those, drive the full `--e2e` path in §1, which runs the
107+
real agent and records it for you.
108+
109+
## 3. Snapshot for yourself to view
110+
111+
Two ways to "see" a screen as an agent:
112+
113+
- **`renderFrame(frame, program)`** → the real Ink screen as an ANSI string you
114+
can print and read (used above). Strip ANSI if you want plain text.
115+
- **The recording JSON** — each frame already carries `screen`, `tasks`,
116+
`statusMessages`, and the (secret-redacted) `session`, so you can assert on
117+
what happened without rendering.
118+
119+
The access token is redacted in both `read_state` and recordings, so anything
120+
you capture is safe to share.
121+
122+
## Key facts
123+
124+
- **State → screen.** You never navigate; you flip a session flag (via an
125+
action's store setter) and the router re-derives the active screen. Name
126+
actions, not keys.
127+
- **Secrets stay out.** `read_state` reduces credentials to `hasCredentials` +
128+
`projectId`; the token is never serialized.
129+
- **None of this ships.** The harness lives in `e2e-harness/`, out of `src/`,
130+
and is absent from the production bundle.

0 commit comments

Comments
 (0)