Repository navigation
feat(local): Stunt Double Studio as a standalone local app - #47
Conversation
A model on the person's own connection (an Anthropic API key or an OpenAI-compatible endpoint) drives the same LocalSession as the MCP server, through the same browser tools, evaluate_check and finish_run, so the files and verdicts match. Step and token limits, usage and cost, Stop. run.json's runner gains optional model and usage. no-model.test.ts now proves the MCP server's import graph never reaches the model client, the keys or a process, and that only the runner imports model.ts. Signed-off-by: Michael Parker <michael@stuntdouble.io>
OAuth 2.1 with PKCE and dynamic registration against the hosted MCP, back to a loopback address, with the token in ~/.stuntdouble/credentials.json. A cloud run pushes the project, actor and checklist through the hosted tools, writes the ids back, polls every 30 seconds and pulls the run back. A plan field gates when present; otherwise the server's refusal is relayed with the billing link. Signed-off-by: Michael Parker <michael@stuntdouble.io>
stuntdouble studio (the default in a terminal) opens the viewer grown into an app: onboarding, actor and checklist editors validated by the run's schemas, Run locally or in the cloud, the live run with its usage and Stop, and settings for the connection and the Stunt Double sign-in. The viewer's security model holds, with this server's Origin and a JSON body required on every write. Adds run, login and logout to the CLI. Signed-off-by: Michael Parker <michael@stuntdouble.io>
Signed-off-by: Michael Parker <michael@stuntdouble.io>
…ck test CodeQL flagged /<script>/ as a filter that misses <SCRIPT>. Signed-off-by: Michael Parker <michael@stuntdouble.io>
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Not checked: tests were not run; no formal proof of the changed code.
Graphify review — findings
Adds Stunt Double Studio, a local app opened with npx stuntdouble studio for creating and editing actors and checklists, running one with an AI actor on a user-configured Anthropic or OpenAI-compatible connection, and reviewing the verdicts. A Studio run reuses the MCP run end to end, so it writes the same files, stops at 80 calls or 3M tokens, honours Stop, and adds optional model and usage to run.json's runner; mcp, view and doctor are unchanged and make no model calls. Adds login, logout and run <checklist> --cloud, which sign in via OAuth with PKCE, push the project, poll the hosted run and pull the result back, so the session and credentials handling in cloud.ts and the cli.ts command dispatch carry the main risk.
Worth a look
- Check IDs mapped to keys by position, not by identity —
packages/local/src/cloud.ts· Escalate · medium- NOT verified (no proof, no reproducing execution) — needs human review
- In-flight token refresh can re-create credentials after logout —
packages/local/src/cloud.ts:99· Escalate · medium- NOT verified (no proof, no reproducing execution) — needs human review
- Starting a sign-in wipes the existing session before the new one succeeds —
packages/local/src/cloud.ts:140· Escalate · medium- NOT verified (no proof, no reproducing execution) — needs human review
- --help opens Studio in an interactive terminal instead of printing usage —
packages/local/src/cli.ts:180· Escalate · medium- NOT verified (no proof, no reproducing execution) — needs human review
- Local run failure leaves the session open, so the CLI can hang —
packages/local/src/cli.ts:150· Escalate · medium- NOT verified (no proof, no reproducing execution) — needs human review
Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 387 functions depend on the 268 functions this change touches.
Health — this change adds coupling hotspots:
- new:
startCloudRun()— 2 callers, 11 callees - worse:
createServer()— 2 callers, 8 callees - worse:
resolveScoreBands()— 5 callers, 3 callees - worse:
isLive()— 4 callers, 3 callees - new:
loop()— 1 callers, 10 callees - new:
run()— 1 callers, 8 callees - new:
renderActorEditor()— 1 callers, 8 callees - new:
renderChecklistEditor()— 1 callers, 8 callees - …and 2 more — each is listed as a finding
Verification — 387 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 377 function(s) in the blast radius were not formally verified this run
Risky patterns (advisory)
- medium
no-unbounded-fetchinpackages/local/src/studio.js:555: unbounded read (no limit/bound): for (const row of rows) row.item = row.read();
Docs that may be stale (advisory)
.claude/skills/adding-a-stuntkit-package/SKILL.md§ (preamble) (lines 1-5): references changed symbolsjson.claude/skills/adding-a-stuntkit-package/SKILL.md§ Steps (lines 18-35): references changed symbolsjson,run.claude/skills/contributing-to-stuntkit/SKILL.md§ Changesets (lines 36-47): references changed symbolsjsonAGENTS.md§ Public repository (lines 3-10): references changed symbolscodeCONTRIBUTING.md§ The dev loop (lines 17-38): references changed symbolsjsonCONTRIBUTING.md§ Adding a package (lines 57-70): references changed symbolsjsonCONTRIBUTING.md§ Developing against unpublished changes from the Stunt Double monorepo (lines 71-90): references changed symbolscode,json,workspacepackages/browser-toolset/README.md§ Claude, on the browser use toolset (lines 37-68): references changed symbolsclient,content,resultspackages/browser-toolset/README.md§ Any model, on the direct tools (lines 69-84): references changed symbolscall,input,run,text,toolpackages/browser-toolset/README.md§ Safety: BrowserSafetyOptions and INDEX_SESSION_SAFETY (lines 85-113): references changed symbolsfield
…and 10 more.
· 7 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).
| ); | ||
| } | ||
|
|
||
| async function run( |
There was a problem hiding this comment.
run()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| * cloud ids already in the files, start a run and pull it back as | ||
| * `runs/<stamp>-<checklist>-cloud/run.json` beside the local runs. | ||
| */ | ||
| export async function startCloudRun(options: { |
There was a problem hiding this comment.
startCloudRun()
fans out to 11 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return { runId, done }; | ||
| } | ||
|
|
||
| async function loop(input: { |
There was a problem hiding this comment.
loop()
fans out to 10 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| } | ||
|
|
||
| // ── Onboarding ──────────────────────────────────────────────────────────── | ||
| function renderWelcome() { |
There was a problem hiding this comment.
renderWelcome()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| const tidy = (object) => | ||
| Object.fromEntries(Object.entries(object).filter(([, v]) => v !== undefined && v !== '')); | ||
|
|
||
| async function renderChecklistEditor(slug) { |
There was a problem hiding this comment.
renderChecklistEditor()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ]; | ||
| } | ||
|
|
||
| async function renderActorEditor(slug) { |
There was a problem hiding this comment.
renderActorEditor()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ['LM Studio', 'http://localhost:1234/v1', ''], | ||
| ]; | ||
|
|
||
| async function renderSettings() { |
There was a problem hiding this comment.
renderSettings()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Summary
npx stuntdouble studio(ornpx stuntdoublein a terminal) starts Studio on 127.0.0.1 and opens the browser. Studio is a local copy of the core capabilities: create and edit actors and checklists, run them with an AI actor in a local Chrome, watch and stop a run, review results.~/.stuntdouble/credentials.json(0600) or the environment. The page sees only a status.stuntdouble login(OAuth 2.1, PKCE, loopback redirect), for workspaces that allow them. The plan gate reads aplanfield when present, and otherwise defers to the server's allowance check.LocalSession, brief, citation check, scoring and file formats.run.jsongains optionalrunner.modelandrunner.usage.claude.test/no-model.test.tsenforces that the MCP path's import graph never reaches the model, credentials, cloud or Studio modules, and that no model SDK is imported. A Claude subscription is never used.Verification
pnpm -r type-check,pnpm lint,pnpm format:check,pnpm buildpnpm --filter stuntdouble test: 67 pass, including the Studio end-to-end test (headless Chrome, a scripted OpenAI-compatible server) and the mocked cloud sign-in and pushqwen3.5:9b): correct verdicts on all three checksRisk and rollout
list_workspacesshould return each workspace'splan, anddocs/architecture/local-suite.mdneeds a Studio section.feat/run-feedbackboth touchsrc/run.tsandsrc/ui.html.