stuntdouble 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -6
- package/dist/chunk-BFI6P5DO.js +278 -0
- package/dist/chunk-BFI6P5DO.js.map +1 -0
- package/dist/chunk-DFXASIGV.js +431 -0
- package/dist/chunk-DFXASIGV.js.map +1 -0
- package/dist/{chunk-7IC2G3Z3.js → chunk-F3R3QL7V.js} +19 -252
- package/dist/chunk-F3R3QL7V.js.map +1 -0
- package/dist/{mcp-MOXTYXZU.js → chunk-FMZTT2MC.js} +72 -1160
- package/dist/chunk-FMZTT2MC.js.map +1 -0
- package/dist/chunk-MSV5OLSY.js +117 -0
- package/dist/chunk-MSV5OLSY.js.map +1 -0
- package/dist/chunk-P3CI4FTE.js +469 -0
- package/dist/chunk-P3CI4FTE.js.map +1 -0
- package/dist/{chunk-JXTUCMO2.js → chunk-QDA5FTUH.js} +5 -3
- package/dist/{chunk-JXTUCMO2.js.map → chunk-QDA5FTUH.js.map} +1 -1
- package/dist/chunk-XHSDRB3X.js +996 -0
- package/dist/chunk-XHSDRB3X.js.map +1 -0
- package/dist/cli.js +179 -22
- package/dist/cli.js.map +1 -1
- package/dist/cloud-DLLNKYND.js +34 -0
- package/dist/credentials-QHESDY4K.js +27 -0
- package/dist/doctor-ZAYOOCPT.js +13 -0
- package/dist/doctor-ZAYOOCPT.js.map +1 -0
- package/dist/files-UBPF7PKW.js +45 -0
- package/dist/files-UBPF7PKW.js.map +1 -0
- package/dist/mcp-HFIAQ6HP.js +133 -0
- package/dist/mcp-HFIAQ6HP.js.map +1 -0
- package/dist/open-browser-CTOGH5HQ.js +17 -0
- package/dist/open-browser-CTOGH5HQ.js.map +1 -0
- package/dist/run-MPIAR3LQ.js +20 -0
- package/dist/run-MPIAR3LQ.js.map +1 -0
- package/dist/runner-E45EJ76B.js +17 -0
- package/dist/runner-E45EJ76B.js.map +1 -0
- package/dist/studio-FBC66BER.js +360 -0
- package/dist/studio-FBC66BER.js.map +1 -0
- package/dist/studio.js +1621 -0
- package/dist/ui-server-C7CFJWRD.js +12 -0
- package/dist/ui-server-C7CFJWRD.js.map +1 -0
- package/dist/ui.html +100 -56
- package/package.json +2 -2
- package/dist/chunk-7IC2G3Z3.js.map +0 -1
- package/dist/doctor-7PW2QD3M.js +0 -12
- package/dist/mcp-MOXTYXZU.js.map +0 -1
- package/dist/ui-server-ZUJ5WJ7Q.js +0 -9
- /package/dist/{doctor-7PW2QD3M.js.map → cloud-DLLNKYND.js.map} +0 -0
- /package/dist/{ui-server-ZUJ5WJ7Q.js.map → credentials-QHESDY4K.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/run.ts","../package.json","../src/brief.ts","../src/driver.ts","../src/evidence.ts"],"sourcesContent":["// One process, one run at a time: the run lifecycle behind start_run,\n// evaluate_check and finish_run, and the browser tools in between.\n//\n// Everything a run produces is a file under `.stuntdouble/runs/<id>/`, written\n// as it happens: `run.json` is rewritten atomically after every verdict, and\n// `activity.jsonl` gains a line per tool call. A run whose process dies stays\n// `running` on disk with no live marker (files.ts `isLive`), and the next\n// start_run marks it interrupted, so it can be resumed.\n\nimport {\n DirectBrowserTools,\n type DirectBrowserToolsOptions,\n} from '@stuntkit/browser-toolset/direct-tools';\nimport { isPrivateHost, redactInput } from '@stuntkit/browser-toolset/executor';\nimport { hostMatchesDomain, INDEX_SESSION_SAFETY } from '@stuntkit/browser-toolset/safety';\nimport { createHash } from 'node:crypto';\nimport { mkdir, mkdtemp, readdir, readFile, rm, stat } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\nimport pkg from '../package.json' with { type: 'json' };\nimport { buildBrief, checkLabel } from './brief.ts';\nimport { type LaunchOptions, type LocalDriver, PlaywrightDriver } from './driver.ts';\nimport { cleanLocator, focusEvidence } from './evidence.ts';\nimport {\n type Actor,\n ActorSchema,\n appendJsonl,\n type Check,\n type Checklist,\n ChecklistSchema,\n type Config,\n ConfigSchema,\n effectiveStatus,\n isLive,\n type Locator,\n readJson,\n readRuns,\n type Result,\n type Run,\n runningMarker,\n sdDir,\n tallyRun,\n writeFileAtomic,\n writeJsonAtomic,\n} from './files.ts';\nimport { startUi, type UiServer } from './ui-server.ts';\nimport { resolveSkippedChecks } from './vendor/scoring/conditions.ts';\nimport { resolveHaltedChecks } from './vendor/scoring/failure.ts';\nimport {\n describeRunScore,\n newChecklistScoring,\n scoreChecklistRun,\n} from './vendor/scoring/scoring.ts';\n\nexport const PACKAGE = `${pkg.name}@${pkg.version}`;\n\nexport type Launch = (options: LaunchOptions) => Promise<LocalDriver>;\n\nexport type Verdict = 'passed' | 'failed' | 'informational' | 'blocked';\n\nexport type ToolOutput = { text: string; image?: string; isError?: boolean };\n\nconst STOPPED =\n 'This run was stopped from the viewer. Do not use the browser again: call finish_run now with a summary of what you found.';\n/** What finish_run records for a check nobody judged; a resume judges it after all. */\nconst NOT_JUDGED = 'Not judged before the run finished.';\nconst PRIVATE_REFUSED =\n 'Local and private addresses other than the app under test are not reachable.';\nconst SETUP = 'Run /stuntdouble setup to create one.';\n/** activity.jsonl is committed, so a page read is kept to its opening lines. */\nconst LOG_TEXT_CHARS = 500;\n\nconst PRESETS = {\n desktop: { width: 1280, height: 720, touch: false },\n tablet: { width: 1024, height: 1366, touch: true },\n mobile: { width: 390, height: 844, touch: true },\n};\n\ntype Active = {\n dir: string;\n run: Run;\n checklist: Checklist;\n actor: Actor;\n driver: LocalDriver;\n trusted: DirectBrowserTools;\n /** The same tools with the pay, sign-up and secrets guards on, for other hosts. */\n guarded: DirectBrowserTools | null;\n trustedHosts: string[];\n /** A fresh profile to delete when the run ends; null for a kept one. */\n tempProfile: string | null;\n everyScreenshot: boolean;\n stopped: boolean;\n};\n\n/** The checklist URL with its origin swapped for `local_base_url`. */\nexport function rebase(url: string, localBase: string | null | undefined): string {\n if (!localBase) return url;\n const target = new URL(url);\n // Not `target.host = base.host`: that keeps the checklist's port when the base has none.\n return new URL(target.pathname + target.search + target.hash, new URL(localBase).origin).href;\n}\n\n/** `20261103-142210`, in local time, so folders sort by when they started. */\nfunction stamp(date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, '0');\n return `${date.getFullYear()}${p(date.getMonth() + 1)}${p(date.getDate())}-${p(date.getHours())}${p(date.getMinutes())}${p(date.getSeconds())}`;\n}\n\nconst now = () => new Date().toISOString();\n\n/** `5 min ago`, for a run listed in a reply. */\nfunction ago(iso: string | null): string {\n if (!iso) return 'not started';\n const minutes = Math.round((Date.now() - Date.parse(iso)) / 60_000);\n if (minutes < 1) return 'just now';\n if (minutes < 60) return `${minutes} min ago`;\n const hours = Math.round(minutes / 60);\n return hours < 48 ? `${hours} h ago` : `${Math.round(hours / 24)} days ago`;\n}\n\nexport class LocalSession {\n readonly root: string;\n private readonly launch: Launch;\n private active: Active | null = null;\n private ui: UiServer | null = null;\n /** The run this session last finished, so a second finish_run is answered, not refused. */\n private finished: { id: string; report: string } | null = null;\n\n /** Extra `runner` fields for a new run, such as the model a standalone run uses. */\n private readonly runnerFields: Partial<Run['runner']>;\n /** A viewer started by someone else (the Studio): its links are used, and it is not closed here. */\n private readonly sharedUi: boolean;\n\n constructor(options: {\n project: string;\n launch?: Launch;\n runner?: Partial<Run['runner']>;\n ui?: UiServer;\n }) {\n this.root = sdDir(options.project);\n this.launch = options.launch ?? PlaywrightDriver.launch;\n this.runnerFields = options.runner ?? {};\n this.ui = options.ui ?? null;\n this.sharedUi = Boolean(options.ui);\n }\n\n /** The viewer's address, starting it on first use. */\n async openUi(path = '/'): Promise<string> {\n this.ui ??= await startUi({ root: this.root });\n await writeJsonAtomic(join(this.root, '.live', 'server.json'), {\n port: this.ui.port,\n token: this.ui.token,\n pid: process.pid,\n });\n return this.ui.link(path);\n }\n\n /** The viewer's port while it is running in this process. */\n get viewerPort(): number | null {\n return this.ui?.port ?? null;\n }\n\n /** open_ui: the link, and a line on each of the latest runs. */\n async overview(): Promise<string> {\n const link = await this.openUi();\n const runs = (await readRuns(this.root)).slice(0, 5);\n if (runs.length === 0) return `${link}\\n\\nNo runs yet.`;\n const lines = runs.map((e) => {\n if (!('run' in e)) return `- ${e.id}: unreadable (${e.error.split('\\n')[0]})`;\n const { run } = e;\n const status = effectiveStatus(this.root, run);\n const t = tallyRun(run);\n const outcome = run.score ? `${status}, ${run.score.outcome}` : status;\n return `- ${run.checklist}: ${outcome}, ${t.passed}/${t.total} checks passed, started ${ago(run.started_at)} (${run.id})`;\n });\n return [link, `Latest runs, newest first:\\n${lines.join('\\n')}`].join('\\n\\n');\n }\n\n async startRun(input: { checklist: string; resume?: boolean }): Promise<string> {\n if (this.active) return this.resumeActive(input);\n if (!/^[a-z0-9][a-z0-9-]*$/.test(input.checklist)) {\n throw new Error(`\"${input.checklist}\" is not a checklist slug (checklists/<slug>.json).`);\n }\n const config: Config = await readJson(\n join(this.root, 'stuntdouble.json'),\n ConfigSchema,\n '.stuntdouble/stuntdouble.json'\n ).catch((err: Error) => {\n throw /file not found$/.test(err.message) ? new Error(`${err.message}. ${SETUP}`) : err;\n });\n let checklist = await readJson(\n join(this.root, 'checklists', `${input.checklist}.json`),\n ChecklistSchema,\n `.stuntdouble/checklists/${input.checklist}.json`\n ).catch(async (err: Error) => {\n if (!/file not found$/.test(err.message)) throw err;\n const slugs = (await readdir(join(this.root, 'checklists')).catch(() => []))\n .filter((f) => f.endsWith('.json'))\n .map((f) => f.slice(0, -'.json'.length));\n throw new Error(\n `${err.message}. ${slugs.length ? `The checklists here are: ${slugs.join(', ')}.` : SETUP}`\n );\n });\n const actor = await readJson(\n join(this.root, 'actors', `${checklist.actor}.json`),\n ActorSchema,\n `.stuntdouble/actors/${checklist.actor}.json`\n );\n await this.markInterrupted();\n\n let run: Run;\n let dir: string;\n if (input.resume) {\n const entry = (await readRuns(this.root)).find(\n (e) =>\n 'run' in e &&\n e.run.checklist === input.checklist &&\n e.run.runner.kind === 'local' &&\n (e.run.status === 'interrupted' || e.run.status === 'stopped')\n );\n if (!entry || !('run' in entry)) {\n throw new Error(`No interrupted or stopped run of ${input.checklist} to resume.`);\n }\n dir = join(this.root, 'runs', entry.id);\n // A resumed run is judged against the checks it started with.\n checklist = ChecklistSchema.parse(entry.run.checklist_snapshot);\n await this.clearStop(entry.id);\n run = {\n ...entry.run,\n results: entry.run.results.filter((r) => r.evidence !== NOT_JUDGED),\n status: 'running',\n pid: process.pid,\n finished_at: null,\n score: null,\n summary: null,\n };\n } else {\n dir = '';\n run = {\n format: 'stuntdouble.run/1',\n id: '',\n runner: { ...this.runnerFields, kind: 'local', package: PACKAGE, judged: false },\n status: 'running',\n pid: process.pid,\n url: rebase(checklist.url, config.local_base_url),\n started_at: now(),\n finished_at: null,\n checklist: input.checklist,\n actor: checklist.actor,\n checklist_snapshot: { ...checklist, cloud: undefined },\n results: [],\n score: null,\n summary: null,\n cloud: null,\n };\n }\n\n const trusted = [run.url, checklist.url, config.local_base_url]\n .filter((u): u is string => Boolean(u))\n .map((u) => new URL(u));\n const trustedHosts = [...new Set(trusted.map((u) => u.hostname))];\n // A private host is trusted by exact origin: trusting localhost:3000 must not\n // open every other local service (the viewer, a database console) to the actor.\n const trustedOrigins = new Set(trusted.map((u) => u.origin));\n const refuseNavigation = (href: string) => {\n const url = URL.parse(href);\n return url && isPrivateHost(url.hostname) && !trustedOrigins.has(url.origin)\n ? PRIVATE_REFUSED\n : null;\n };\n const preset = PRESETS[checklist.browser_config?.preset ?? 'desktop'];\n const toolOptions: DirectBrowserToolsOptions = {\n viewport: checklist.browser_config?.viewport ?? preset,\n allowPrivateHosts: true,\n checkNavigation: refuseNavigation,\n };\n\n // The browser first: a run folder is only made for a run that can start.\n const tempProfile = actor.persist_login\n ? null\n : await mkdtemp(join(tmpdir(), 'stuntdouble-profile-'));\n // Kept per project: two projects with a `shopper` must not share its cookies.\n const project = createHash('sha256').update(this.root).digest('hex').slice(0, 12);\n const profileDir =\n tempProfile ?? join(homedir(), '.stuntdouble', 'profiles', project, checklist.actor);\n await mkdir(profileDir, { recursive: true });\n let driver: LocalDriver;\n try {\n driver = await this.launch({\n profileDir,\n headless: config.headless,\n viewport: toolOptions.viewport,\n touch: checklist.browser_config?.touch ?? preset.touch,\n locale: checklist.browser_config?.locale,\n refuseNavigation,\n });\n } catch (err) {\n if (tempProfile) await rm(tempProfile, { recursive: true, force: true });\n throw err;\n }\n\n if (!input.resume) {\n await mkdir(join(this.root, 'runs'), { recursive: true });\n // Two runs started in the same second get a suffix rather than one folder.\n for (let n = 1; !dir; n++) {\n const id = `${stamp()}-${input.checklist}${n > 1 ? `-${n}` : ''}`;\n const made = await mkdir(join(this.root, 'runs', id)).then(\n () => true,\n (err: NodeJS.ErrnoException) => {\n if (err.code === 'EEXIST') return false;\n throw err;\n }\n );\n if (made) [run.id, dir] = [id, join(this.root, 'runs', id)];\n }\n }\n const gitignore = join(this.root, '.gitignore');\n if (!(await stat(gitignore).catch(() => null))) await writeFileAtomic(gitignore, '.live/\\n');\n\n this.active = {\n dir,\n run,\n checklist,\n actor,\n driver,\n trusted: new DirectBrowserTools(driver, toolOptions),\n guarded:\n config.safety === 'third_party'\n ? new DirectBrowserTools(driver, { ...toolOptions, safety: INDEX_SESSION_SAFETY })\n : null,\n trustedHosts,\n tempProfile,\n everyScreenshot: config.model_screenshots === 'every_action',\n stopped: false,\n };\n this.finished = null;\n await this.save();\n await this.markRunning();\n await this.log('start_run', input, input.resume ? 'Resumed' : 'Started');\n\n const opened = await this.browserTool('navigate', { url: run.url });\n const pending = this.pending();\n const viewer = await this.openUi(`/runs/${run.id}`);\n return [\n `Run ${run.id} ${input.resume ? 'resumed' : 'started'}. Watch it or stop it at ${viewer}`,\n unweightedNote(checklist),\n buildBrief({\n actor,\n checklist,\n url: run.url,\n checks: pending,\n resumed: Boolean(input.resume),\n }),\n // Not the navigate result: it carries the page title, and this reply is\n // read by the main session, which never reads page content.\n opened.isError\n ? `The start page did not load: ${opened.text}\\nTry navigate again, or judge the checks you cannot reach as \"blocked\".`\n : `The browser is open at ${run.url}: read the page with read_page before acting.`,\n this.nextStep(),\n ]\n .filter(Boolean)\n .join('\\n\\n');\n }\n\n /** One of the browser toolset's direct tools, on the active run's browser. */\n async browserTool(name: string, input: Record<string, unknown>): Promise<ToolOutput> {\n const active = this.requireActive();\n if (await this.stopRequested(active)) return { text: STOPPED, isError: true };\n const page = await active.driver.context.activePage();\n const host = page ? (URL.parse(await page.url())?.hostname ?? '') : '';\n const tools =\n active.guarded && !isTrustedHost(host, active.trustedHosts) ? active.guarded : active.trusted;\n const result = await tools.run(name, input);\n // The browser refused a navigation the tool did not make itself (driver.ts).\n // A redirect may have shown the page for a moment, so nothing read is kept.\n const refusal = active.driver.takeRefusal();\n if (refusal) {\n const text = `Error: Navigation refused. ${refusal}`;\n await this.log(name, redactInput(name, input), text);\n return { text, isError: true };\n }\n if (result.screenshotBase64)\n await this.saveFrame(Buffer.from(result.screenshotBase64, 'base64'));\n await this.log(name, redactInput(name, input), result.text);\n const image =\n result.screenshotBase64 && (name === 'screenshot' || active.everyScreenshot)\n ? result.screenshotBase64\n : undefined;\n return { text: result.text, ...(image ? { image } : {}), isError: Boolean(result.error) };\n }\n\n async evaluateCheck(input: {\n check: string;\n status: Verdict;\n evidence: string;\n locator?: Locator;\n }): Promise<string> {\n if (!this.active && this.finished) return this.alreadyFinished();\n const active = this.requireActive();\n if (await this.stopRequested(active)) throw new Error(STOPPED);\n const { run, checklist } = active;\n const check = checklist.checks.find((c) => c.key === input.check);\n if (!check) {\n throw new Error(\n `No check \"${input.check}\" in this run. The keys are: ${checklist.checks.map((c) => c.key).join(', ')}.`\n );\n }\n const earlier = run.results.find((r) => r.check === check.key);\n if (earlier) {\n throw new Error(\n `[${check.key}] already has a verdict (${earlier.status}). Each check gets one.`\n );\n }\n if (\n check.type === 'informational' &&\n (input.status === 'passed' || input.status === 'failed')\n ) {\n throw new Error(\n `[${check.key}] is informational: record what you observed with status \"informational\".`\n );\n }\n if (check.type !== 'informational' && input.status === 'informational') {\n throw new Error(\n `[${check.key}] is scored: judge it \"passed\" or \"failed\", or \"blocked\" if you could not reach it.`\n );\n }\n\n const page = await active.driver.context.activePage();\n const query = cleanLocator(input.locator);\n const found = page ? await focusEvidence(page, query) : null;\n if (input.status === 'passed' && !found) {\n throw new Error(\n query.text || query.selector\n ? `Not recorded: the locator ${JSON.stringify(query.text ?? query.selector)} is not visible on the current page. Go back to where you saw the evidence, or cite text that is on screen now.`\n : 'Not recorded: a passed verdict needs a locator, {\"text\": \"...\"} visible on the page or {\"selector\": \"...\"}.'\n );\n }\n let screenshot: string | undefined;\n if (page) {\n try {\n const png = await page.screenshot({ type: 'png', timeout: 10_000 });\n screenshot = `shots/${check.key}.png`;\n await writeFileAtomic(join(active.dir, screenshot), png);\n await this.saveFrame(png);\n } catch {\n screenshot = undefined;\n }\n }\n run.results.push({\n check: check.key,\n status: input.status === 'blocked' ? 'error' : input.status,\n evidence: input.evidence,\n ...(query.text || query.selector\n ? {\n locator: {\n ...(query.text ? { text: query.text } : {}),\n ...(query.selector ? { selector: query.selector } : {}),\n },\n }\n : {}),\n ...(screenshot ? { screenshot } : {}),\n at: now(),\n });\n\n // Settle what this verdict decides: a failed stop check skips every check\n // left, and an unmet run condition skips its check and the chain after it.\n const statuses = new Map(run.results.map((r) => [r.check, r.status]));\n const halted = resolveHaltedChecks({\n checks: checklist.checks.map((c) => ({\n id: c.key,\n description: c.description,\n onFailure: c.on_failure,\n })),\n statuses,\n });\n const skipped = halted.length ? halted : this.conditionSkips(statuses);\n for (const s of skipped) {\n run.results.push({ check: s.checkId, status: 'skipped', evidence: s.reason, at: now() });\n }\n await this.save();\n const recorded = `Recorded [${check.key}] as ${input.status}.`;\n await this.log('evaluate_check', input, recorded);\n const notes = skipped.map((s) => `[${s.checkId}] ${s.reason}`);\n if (halted.length) notes.push('The run has stopped.');\n return [recorded, ...notes, this.nextStep()].join('\\n');\n }\n\n async finishRun(input: { summary: string }): Promise<string> {\n if (!this.active && this.finished) return this.alreadyFinished();\n const active = this.requireActive();\n const stopped = await this.stopRequested(active);\n const { run, checklist } = active;\n for (const s of this.conditionSkips(new Map(run.results.map((r) => [r.check, r.status])))) {\n run.results.push({ check: s.checkId, status: 'skipped', evidence: s.reason, at: now() });\n }\n for (const check of this.pending()) {\n run.results.push({\n check: check.key,\n status: 'error',\n evidence: NOT_JUDGED,\n at: now(),\n });\n }\n const score = scoreLocalRun(checklist, run.results);\n run.score = {\n score: score.score,\n unit: score.scoreUnit,\n outcome: score.outcome,\n pass_threshold: score.passThreshold,\n grade: score.grade,\n };\n run.summary = input.summary;\n run.status = stopped ? 'stopped' : 'completed';\n run.finished_at = now();\n await this.save();\n await this.log('finish_run', input, `Finished: ${score.outcome}`);\n const viewer = await this.openUi(`/runs/${run.id}`);\n await this.endBrowser();\n\n const line = (r: Result) => `- [${r.check}] ${r.evidence}`;\n const failed = run.results.filter((r) => r.status === 'failed');\n const blocked = run.results.filter((r) => r.status === 'error');\n const counts = describeRunScore(score, {\n passedCount: run.results.filter((r) => r.status === 'passed').length,\n totalCount: checklist.checks.length,\n });\n // The tally is in every reply, weighted or not, so the actor can relay it.\n const t = tallyRun(run);\n const tally = score.weighted ? ` ${t.passed}/${t.total} checks passed.` : '';\n this.finished = { id: run.id, report: viewer };\n return [\n `Run ${run.id} ${run.status}: ${score.outcome}. ${counts}.${tally} ${score.weighted ? '' : `${unweightedNote(checklist, true)} `}Self-judged: the actor recorded its own verdicts.`,\n failed.length ? `Failed:\\n${failed.map(line).join('\\n')}` : '',\n blocked.length ? `Not judged:\\n${blocked.map(line).join('\\n')}` : '',\n `Report: ${viewer}`,\n ]\n .filter(Boolean)\n .join('\\n\\n');\n }\n\n /**\n * start_run while this process still has a run open, as when the person\n * interrupts the actor: resume carries on with it in the same browser.\n */\n private async resumeActive(input: { checklist: string; resume?: boolean }): Promise<string> {\n const active = this.requireActive();\n const { run } = active;\n if (!input.resume || run.checklist !== input.checklist) {\n throw new Error(\n `Run ${run.id} of ${run.checklist} is still going: call start_run with checklist \"${run.checklist}\" and resume to carry on with it, or finish_run to end it.`\n );\n }\n active.stopped = false;\n run.status = 'running';\n await this.clearStop(run.id);\n await this.save();\n await this.markRunning();\n await this.log('start_run', input, 'Resumed');\n const viewer = await this.openUi(`/runs/${run.id}`);\n return [\n `Run ${run.id} resumed. Watch it or stop it at ${viewer}`,\n buildBrief({\n actor: active.actor,\n checklist: active.checklist,\n url: run.url,\n checks: this.pending(),\n resumed: true,\n }),\n 'The browser is still open where the run left off: read the page before acting.',\n this.nextStep(),\n ].join('\\n\\n');\n }\n\n /** On shutdown: an unfinished run is marked interrupted, so it can be resumed. */\n async close(): Promise<void> {\n if (this.active) {\n this.active.run.status = this.active.stopped ? 'stopped' : 'interrupted';\n await this.save().catch(() => {});\n await this.endBrowser();\n }\n if (!this.sharedUi) await this.ui?.close();\n this.ui = null;\n }\n\n private alreadyFinished(): string {\n const { id, report } = this.finished!;\n return `Run ${id} has already finished: its report is at ${report}. Start another with start_run.`;\n }\n\n private requireActive(): Active {\n if (!this.active) throw new Error('No run is going: call start_run first.');\n return this.active;\n }\n\n /** Checks with no result yet, in run order. */\n private pending(): Check[] {\n const active = this.requireActive();\n const judged = new Set(active.run.results.map((r) => r.check));\n return active.checklist.checks.filter((c) => !judged.has(c.key));\n }\n\n private conditionSkips(statuses: Map<string, Result['status']>) {\n return resolveSkippedChecks({\n checks: this.requireActive().checklist.checks.map((c) => ({\n id: c.key,\n description: c.description,\n runCondition: c.run_condition,\n })),\n statuses,\n });\n }\n\n private nextStep(): string {\n const pending = this.pending();\n if (pending.length === 0)\n return 'Every check has a verdict: call finish_run with a short summary.';\n const next = pending[0]!;\n return `Next: [${next.key}] ${checkLabel(next)} ${next.description} (${pending.length} left).`;\n }\n\n /** The viewer's Stop writes the run id to `.live/stop`; the run notices on its next call. */\n private async stopRequested(active: Active): Promise<boolean> {\n if (active.stopped) return true;\n const flag = await readFile(join(this.root, '.live', 'stop'), 'utf8').catch(() => '');\n if (flag.trim() !== active.run.id) return false;\n active.stopped = true;\n active.run.status = 'stopped';\n await this.save();\n await this.log('stop', {}, 'Stopped from the viewer');\n return true;\n }\n\n /** Runs left `running` by a process that is gone, or by this one before a restart. */\n private async markInterrupted(): Promise<void> {\n for (const entry of await readRuns(this.root)) {\n if (!('run' in entry) || entry.run.status !== 'running') continue;\n if (entry.run.pid !== process.pid && isLive(this.root, entry.run)) continue;\n await writeJsonAtomic(join(this.root, 'runs', entry.id, 'run.json'), {\n ...entry.run,\n status: 'interrupted',\n });\n }\n }\n\n private async endBrowser(): Promise<void> {\n const active = this.active;\n if (!active) return;\n this.active = null;\n await active.driver.close().catch(() => {});\n if (active.tempProfile) await rm(active.tempProfile, { recursive: true, force: true });\n await this.clearStop(active.run.id);\n await rm(runningMarker(this.root, active.run.id), { force: true });\n }\n\n private async markRunning(): Promise<void> {\n const { run } = this.requireActive();\n await writeFileAtomic(runningMarker(this.root, run.id), String(run.pid));\n }\n\n private async clearStop(id: string): Promise<void> {\n const stopFile = join(this.root, '.live', 'stop');\n const flag = await readFile(stopFile, 'utf8').catch(() => '');\n if (flag.trim() === id) await rm(stopFile, { force: true });\n }\n\n private async save(): Promise<void> {\n const active = this.requireActive();\n await writeJsonAtomic(join(active.dir, 'run.json'), active.run);\n }\n\n private async log(tool: string, input: unknown, text: string): Promise<void> {\n const active = this.requireActive();\n await appendJsonl(join(active.dir, 'activity.jsonl'), {\n at: now(),\n tool,\n input,\n text: text.length > LOG_TEXT_CHARS ? `${text.slice(0, LOG_TEXT_CHARS)}...` : text,\n });\n }\n\n /** The viewer's live frame, best effort. ponytail: one per project, so two sessions running at once share it. */\n private async saveFrame(png: Uint8Array): Promise<void> {\n await writeFileAtomic(join(this.root, '.live', 'latest.png'), png).catch(() => {});\n }\n}\n\n/**\n * Whether a page's host is the app under test. A page with no host (about:blank,\n * data:, blob:) is not: a third party can open a blob: form, and the guard\n * costs a blank page nothing.\n */\nexport function isTrustedHost(host: string, trustedHosts: string[]): boolean {\n return host !== '' && trustedHosts.some((t) => hostMatchesDomain(host, t));\n}\n\n/**\n * Why a checklist with no weighted checks has no score. finish_run always says\n * it; start_run only when a pass_threshold is set that will not be used. Null\n * for a weighted checklist.\n */\nexport function unweightedNote(checklist: Checklist, atFinish = false): string | null {\n if (checklist.checks.some((c) => c.type === 'weighted')) return null;\n const threshold =\n checklist.pass_threshold == null\n ? ''\n : ` Its pass_threshold (${checklist.pass_threshold}) applies to weighted checks only.`;\n if (!atFinish && !threshold) return null;\n return `No score: this checklist has no weighted checks, so it passes only when every pass/fail check passes.${threshold}`;\n}\n\n/** A finished run's verdict, by the same code that scores a cloud run. */\nexport function scoreLocalRun(checklist: Checklist, results: Result[]) {\n const scoring = newChecklistScoring({\n scoreType: checklist.score_type,\n scoreUnit: checklist.score_unit,\n passThreshold: checklist.pass_threshold,\n scoreBands: checklist.score_bands,\n checks: checklist.checks.map((c) => ({ checkType: c.type, weight: c.weight })),\n });\n return scoreChecklistRun({\n checks: checklist.checks.map((c) => ({\n id: c.key,\n checkType: c.type,\n weight: c.weight ?? 1,\n onFailure: c.on_failure,\n })),\n results: results.map((r) => ({ checkId: r.check, status: r.status })),\n passThreshold: scoring.pass_threshold,\n scoreBands: scoring.score_bands,\n scoreType: scoring.score_type,\n scoreUnit: scoring.score_unit,\n });\n}\n","{\n \"name\": \"stuntdouble\",\n \"version\": \"0.2.0\",\n \"description\": \"Stunt Double Local: run Stunt Double checklists in a local Chrome from your own coding agent session, with the runs kept as files and a local viewer\",\n \"keywords\": [\n \"stunt-double\",\n \"testing\",\n \"browser\",\n \"checklist\",\n \"mcp\",\n \"playwright\",\n \"agents\"\n ],\n \"license\": \"Apache-2.0\",\n \"author\": \"Turnout Labs Ltd\",\n \"homepage\": \"https://github.com/stunt-double/stuntkit/tree/main/packages/local#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/stunt-double/stuntkit/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/stunt-double/stuntkit.git\",\n \"directory\": \"packages/local\"\n },\n \"type\": \"module\",\n \"bin\": {\n \"stuntdouble\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"type-check\": \"tsc -p tsconfig.json\",\n \"test\": \"node --conditions=@stunt-double/source --test \\\"test/**/*.test.ts\\\"\",\n \"prepack\": \"pnpm run build\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.32.2\",\n \"playwright-core\": \"^1.64.1\",\n \"zod\": \"^4.6.7\"\n },\n \"devDependencies\": {\n \"@stuntkit/browser-toolset\": \"workspace:*\"\n },\n \"engines\": {\n \"node\": \">=22.18\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org\",\n \"provenance\": true\n }\n}\n","// What the actor is told at the start of a run: who it is, what to do, the\n// checks to judge, and how a local run works. The agent reading it is the\n// person's own session, so this is the whole of the actor's instructions.\n\nimport type { Actor, Check, Checklist } from './files.ts';\n\nconst CONDITION_LABELS: Record<Check['run_condition'], string> = {\n always: 'always',\n if_previous_passed: 'only if the check before it passed',\n if_previous_failed: 'only if the check before it failed',\n};\n\n/** `(weighted x2, always)`: how a check counts and when it runs. */\nexport function checkLabel(check: Check): string {\n const kind =\n check.type === 'informational'\n ? 'informational, record what you observe'\n : check.type === 'weighted'\n ? `weighted x${check.weight ?? 1}${(check.weight ?? 1) < 0 ? ', a penalty' : ''}`\n : 'pass/fail';\n const parts = [kind, CONDITION_LABELS[check.run_condition]];\n if (check.on_failure === 'stop') parts.push('the run stops if it fails');\n return `(${parts.join(', ')})`;\n}\n\nexport function checkLine(check: Check): string {\n return `- [${check.key}] ${checkLabel(check)} ${check.description}`;\n}\n\nexport function buildBrief(input: {\n actor: Actor;\n checklist: Checklist;\n url: string;\n /** The checks still to judge: all of them, or on a resume the ones without a verdict. */\n checks: Check[];\n resumed: boolean;\n}): string {\n const { actor, checklist, url, checks, resumed } = input;\n const persona = [\n `You are ${actor.name}${actor.description ? `: ${actor.description.replace(/[.\\s]+$/, '')}` : ''}.`,\n actor.system_prompt ?? '',\n ]\n .filter(Boolean)\n .join('\\n');\n const knowledge = actor.knowledge.length\n ? [\n 'What you know:',\n ...actor.knowledge.map((k) => `### ${k.title} (${k.kind})\\n${k.content}`),\n ].join('\\n')\n : '';\n return [\n persona,\n knowledge,\n `Your task: ${checklist.instructions}`,\n `Start at ${url}${resumed ? ' (this run is resuming: earlier verdicts stand, judge only the checks below)' : ''}.`,\n `Checks to judge:\\n${checks.map(checkLine).join('\\n')}`,\n [\n 'How to work:',\n '- Stay in persona: act and judge as this person would, not as a tester.',\n '- Read the page with read_page and find. Ask for a screenshot only when how something looks is the question.',\n '- get_page_text and read_page \"all\" return up to 20,000 characters: keep them for long text such as a policy.',\n '- Judge each check with evaluate_check as soon as you have observed it, while the evidence is on screen.',\n '- A passed verdict needs a locator: text visible on the page, or a CSS selector, that shows the evidence. A failure may describe something missing.',\n '- Cite only what you saw in this run. Never pass a check on what a page probably does.',\n '- When something stops you (a sign-in wall, a broken page, a CAPTCHA), judge the checks you cannot reach as \"blocked\" and say what stopped you.',\n '- Always end with finish_run and a one or two sentence summary, even when blocked or stopped.',\n ].join('\\n'),\n ]\n .filter(Boolean)\n .join('\\n\\n');\n}\n","// A `BrowserDriver` over Playwright, adapted from the browser toolset's example\n// (`packages/browser-toolset/examples/src/playwright-driver.ts`), as its header\n// invites. The changes: playwright-core instead of playwright, a persistent\n// context on a profile directory, so a run gets the person's own Chrome with a\n// fresh profile (or the actor's kept one), never their everyday profile, and the\n// run's rule on private addresses applied to every navigation, not only the\n// navigate tool's.\n\nimport type {\n BrowserDriver,\n DriverActResult,\n DriverContext,\n DriverDomainPolicy,\n DriverLoadState,\n DriverLocator,\n DriverNavigateOptions,\n DriverPage,\n DriverScreenshotOptions,\n DriverWaitForSelectorOptions,\n} from '@stuntkit/browser-toolset/driver';\nimport { hostMatchesDomain } from '@stuntkit/browser-toolset/safety';\nimport { chromium, type BrowserContext, type Page, type Request } from 'playwright-core';\n\n/** Playwright's `Page` objects are stable, so a WeakMap gives each tab a lasting id. */\nconst pageIds = new WeakMap<Page, string>();\nlet pageSeq = 0;\n\nfunction idOf(page: Page): string {\n let id = pageIds.get(page);\n if (!id) {\n pageSeq += 1;\n id = `pw-${pageSeq}`;\n pageIds.set(page, id);\n }\n return id;\n}\n\nclass PlaywrightPage implements DriverPage {\n readonly id: string;\n readonly raw: Page;\n\n constructor(page: Page) {\n this.raw = page;\n this.id = idOf(page);\n }\n\n goto(url: string, options: DriverNavigateOptions = {}) {\n return this.raw.goto(url, {\n ...(options.waitUntil ? { waitUntil: options.waitUntil } : {}),\n ...(options.timeoutMs ? { timeout: options.timeoutMs } : {}),\n });\n }\n goBack() {\n return this.raw.goBack();\n }\n goForward() {\n return this.raw.goForward();\n }\n screenshot(options: DriverScreenshotOptions = {}): Promise<Buffer> {\n return this.raw.screenshot(options);\n }\n evaluate<R = unknown, Arg = unknown>(\n expression: string | ((arg: Arg) => R | Promise<R>),\n arg?: Arg\n ): Promise<R> {\n if (typeof expression === 'string') return this.raw.evaluate(expression) as Promise<R>;\n return this.raw.evaluate(expression as (arg: unknown) => R | Promise<R>, arg) as Promise<R>;\n }\n url() {\n return this.raw.url();\n }\n title() {\n return this.raw.title();\n }\n setViewportSize(width: number, height: number) {\n return this.raw.setViewportSize({ width, height });\n }\n waitForTimeout(ms: number) {\n return this.raw.waitForTimeout(ms);\n }\n waitForSelector(selector: string, options: DriverWaitForSelectorOptions = {}) {\n return this.raw.waitForSelector(selector, options);\n }\n waitForLoadState(state: DriverLoadState, timeoutMs?: number) {\n return this.raw.waitForLoadState(state, timeoutMs ? { timeout: timeoutMs } : undefined);\n }\n locator(selector: string): DriverLocator {\n return this.raw.locator(selector).first();\n }\n click(x: number, y: number, options: Parameters<DriverPage['click']>[2] = {}) {\n return this.raw.mouse.click(x, y, options);\n }\n hover(x: number, y: number) {\n return this.raw.mouse.move(x, y);\n }\n async scroll(x: number, y: number, deltaX: number, deltaY: number) {\n await this.raw.mouse.move(x, y);\n await this.raw.mouse.wheel(deltaX, deltaY);\n }\n async dragAndDrop(fromX: number, fromY: number, toX: number, toY: number) {\n await this.raw.mouse.move(fromX, fromY);\n await this.raw.mouse.down();\n await this.raw.mouse.move(toX, toY, { steps: 10 });\n await this.raw.mouse.up();\n }\n type(text: string) {\n return this.raw.keyboard.type(text);\n }\n keyPress(key: string) {\n return this.raw.keyboard.press(key);\n }\n close() {\n return this.raw.close();\n }\n}\n\nclass PlaywrightContext implements DriverContext {\n private readonly context: BrowserContext;\n private active: Page | undefined;\n private policy: DriverDomainPolicy = {};\n private routed = false;\n\n constructor(context: BrowserContext) {\n this.context = context;\n // Popups and `target=\"_blank\"` links become the active tab, as in a browser.\n context.on('page', (page) => {\n this.active = page;\n });\n }\n\n activePage(): DriverPage | undefined {\n const open = this.context.pages();\n if (!this.active || this.active.isClosed()) this.active = open[open.length - 1];\n return this.active ? new PlaywrightPage(this.active) : undefined;\n }\n pages(): DriverPage[] {\n return this.context.pages().map((p) => new PlaywrightPage(p));\n }\n async newPage(url?: string): Promise<DriverPage> {\n const page = await this.context.newPage();\n this.active = page;\n if (url) await page.goto(url);\n return new PlaywrightPage(page);\n }\n async setActivePage(page: DriverPage): Promise<void> {\n const raw = this.context.pages().find((p) => idOf(p) === page.id);\n if (!raw) throw new Error(`No open tab with id ${page.id}`);\n this.active = raw;\n await raw.bringToFront();\n }\n clearCookies() {\n return this.context.clearCookies();\n }\n async addInitScript(source: string): Promise<void> {\n await this.context.addInitScript(source);\n }\n /** Abort requests to blocked hosts, or to anything outside the allow list. */\n async setDomainPolicy(policy: DriverDomainPolicy): Promise<void> {\n this.policy = policy;\n if (this.routed) return;\n this.routed = true;\n await this.context.route('**/*', (route) => {\n let host = '';\n try {\n host = new URL(route.request().url()).hostname;\n } catch {\n return route.continue();\n }\n const { allowedDomains, blockedDomains } = this.policy;\n const blocked =\n (blockedDomains ?? []).some((d) => hostMatchesDomain(host, d)) ||\n (allowedDomains !== undefined &&\n allowedDomains.length > 0 &&\n !allowedDomains.some((d) => hostMatchesDomain(host, d)));\n return blocked ? route.abort('blockedbyclient') : route.continue();\n });\n }\n}\n\nexport type LaunchOptions = {\n profileDir: string;\n headless: boolean;\n viewport: { width: number; height: number };\n touch: boolean;\n locale?: string;\n /** Why the browser may not open `href`, or null when it may. */\n refuseNavigation: (href: string) => string | null;\n};\n\nexport type LocalDriver = BrowserDriver & {\n /** Why the browser refused to open a page since the last call, if it did; reading it resets it. */\n takeRefusal(): string | null;\n};\n\nexport class PlaywrightDriver implements LocalDriver {\n readonly context: PlaywrightContext;\n private readonly browserContext: BrowserContext;\n private refusal: string | null = null;\n\n private constructor(context: BrowserContext) {\n this.browserContext = context;\n this.context = new PlaywrightContext(context);\n }\n\n /**\n * The installed Google Chrome, or Playwright's own Chromium when Chrome is\n * missing and `npx playwright install chromium` has been run.\n */\n static async launch(options: LaunchOptions): Promise<PlaywrightDriver> {\n const settings = {\n headless: options.headless,\n viewport: options.viewport,\n hasTouch: options.touch,\n ...(options.locale ? { locale: options.locale } : {}),\n };\n let context: BrowserContext;\n try {\n context = await chromium.launchPersistentContext(options.profileDir, {\n ...settings,\n channel: 'chrome',\n });\n } catch (chromeError) {\n // A kept profile in use by another run is locked; another browser will not help.\n if (/ProcessSingleton|SingletonLock|already in use/i.test((chromeError as Error).message)) {\n throw new Error(\n \"Another run is already using this actor's kept login (persist_login). Finish that run first, or try again when it ends.\",\n { cause: chromeError }\n );\n }\n try {\n context = await chromium.launchPersistentContext(options.profileDir, settings);\n } catch {\n throw new Error(\n `Could not start Chrome (${(chromeError as Error).message.split('\\n')[0]}). Install Google Chrome, or run \\`npx playwright install chromium\\`.`\n );\n }\n }\n const driver = new PlaywrightDriver(context);\n // The navigate tool checks its own URL, but links, scripts, popups and frames\n // navigate without it, so every navigation is checked here too. A refusal in\n // a tab's main frame is kept for the next tool result to explain.\n const refuse = (request: Request) => {\n const reason = request.isNavigationRequest() ? options.refuseNavigation(request.url()) : null;\n if (reason && !request.frame().parentFrame()) driver.refusal = reason;\n return reason;\n };\n await context.route('**/*', (route) =>\n refuse(route.request()) ? route.abort('blockedbyclient') : route.fallback()\n );\n // A route never sees a redirect's later hops: Playwright sends them on before\n // any handler runs. So a frame redirected somewhere refused goes to about:blank.\n context.on('request', (request) => {\n if (request.redirectedFrom() && refuse(request)) {\n request\n .frame()\n .goto('about:blank')\n .catch(() => {});\n }\n });\n if (context.pages().length === 0) await driver.context.newPage();\n return driver;\n }\n\n takeRefusal(): string | null {\n const refusal = this.refusal;\n this.refusal = null;\n return refusal;\n }\n\n /** Instruction-style actions need a model of their own. Nothing here calls them. */\n async act(): Promise<DriverActResult> {\n throw new Error('act is not supported by this driver: use the browser toolset instead');\n }\n async extract(): Promise<unknown> {\n throw new Error('extract is not supported by this driver: use the browser toolset instead');\n }\n close() {\n return this.browserContext.close();\n }\n}\n","// A verdict's evidence on the page: found, scrolled into view and captured.\n//\n// Ported from the cloud runner's evidence focus: one `page.evaluate` that finds\n// the region a check was judged on (a selector first, then the smallest\n// rendered element whose text contains the cited fragment) and scrolls it to\n// the middle of the viewport, so a check about the footer is illustrated by the\n// footer rather than by wherever the actor stopped.\n//\n// Locally it also does the cloud's judge's least job without a model: a passed\n// verdict has to cite something that is on the page right now. A failure may\n// cite an absence, so for a failure the search is only for the screenshot.\n\nimport type { DriverPage } from '@stuntkit/browser-toolset/driver';\n\nimport type { Locator } from './files.ts';\n\n/** Matching is a case-insensitive substring test, so a long quote adds nothing. */\nexport const MAX_LOCATOR_TEXT = 120;\nconst MAX_LOCATOR_SELECTOR = 400;\n/** Lets lazy images and sticky headers settle after the scroll. */\nconst SETTLE_MS = 350;\n/** A hung page must not hold the run: every tool call waits on this one. */\nconst FIND_TIMEOUT_MS = 5_000;\n\ntype Query = { selector: string | null; text: string | null };\nexport type FoundVia = 'selector' | 'text' | null;\n\nexport function cleanLocator(locator: Locator | undefined): Query {\n const clean = (value: string | undefined) => value?.replace(/\\s+/g, ' ').trim() || null;\n const selector = clean(locator?.selector);\n return {\n selector: selector && selector.length <= MAX_LOCATOR_SELECTOR ? selector : null,\n text: clean(locator?.text)?.slice(0, MAX_LOCATOR_TEXT) ?? null,\n };\n}\n\n/**\n * Find the evidence and scroll it to the centre. Never throws: a page that is\n * navigating or a selector the page rejects is a miss, not a lost verdict.\n */\nexport async function focusEvidence(\n page: DriverPage,\n query: Query,\n timeoutMs = FIND_TIMEOUT_MS\n): Promise<FoundVia> {\n if (!query.selector && !query.text) return null;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const via = await Promise.race([\n page.evaluate(findAndScroll, query),\n new Promise<null>((ok) => (timer = setTimeout(() => ok(null), timeoutMs))),\n ])\n .catch(() => null)\n .finally(() => clearTimeout(timer));\n if (via) await page.waitForTimeout(SETTLE_MS).catch(() => {});\n return via;\n}\n\n/**\n * Runs in the page, serialised with `toString()`, so it closes over nothing but\n * its argument. Text is matched against what a person reads on screen: rendered\n * text (`innerText`, which leaves out hidden elements and breaks lines at `<br>`\n * and blocks), open shadow roots included, plus the labels a control shows\n * without text content (placeholder, aria-label, alt, title, an input's value).\n * ponytail: frames are not searched, and text faded to opacity 0 inside a\n * visible parent still counts; search each frame through Playwright if a\n * check's evidence lives in one.\n */\nfunction findAndScroll(query: Query): FoundVia {\n const centre: ScrollIntoViewOptions = { block: 'center', inline: 'center' };\n const shown = (el: Element) => {\n const rect = el.getBoundingClientRect();\n return (\n rect.width > 0 &&\n rect.height > 0 &&\n el.checkVisibility({ visibilityProperty: true, opacityProperty: true })\n );\n };\n const norm = (s: string | null | undefined) => (s ?? '').replace(/\\s+/g, ' ').toLowerCase();\n const wanted = query.text ? norm(query.text) : null;\n const labelled = (el: Element, fragment: string) => {\n const labels = ['placeholder', 'aria-label', 'alt', 'title'].map((a) => el.getAttribute(a));\n if ('value' in el && typeof el.value === 'string') labels.push(el.value);\n return labels.some((l) => norm(l).includes(fragment));\n };\n const rendered = (el: Element) => norm(el instanceof HTMLElement ? el.innerText : '');\n\n if (query.selector) {\n let matches: Element[] = [];\n try {\n matches = Array.from(document.querySelectorAll(query.selector));\n } catch {\n // A selector the page cannot parse is a miss.\n }\n // The page itself is not evidence of anything, and with text given the\n // element has to show it.\n const el = matches.find(\n (m) =>\n m !== document.documentElement &&\n m !== document.body &&\n m.tagName !== 'MAIN' &&\n shown(m) &&\n (!wanted || labelled(m, wanted) || rendered(m).includes(wanted))\n );\n if (el) {\n el.scrollIntoView(centre);\n return 'selector';\n }\n }\n if (!wanted) return null;\n\n const elements: Element[] = [];\n const collect = (root: ParentNode) => {\n for (const el of Array.from(root.querySelectorAll('*'))) {\n elements.push(el);\n if (el.shadowRoot) collect(el.shadowRoot);\n }\n };\n collect(document.body);\n let best: Element | null = null;\n let bestLength = Number.POSITIVE_INFINITY;\n for (const el of elements) {\n if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE'].includes(el.tagName)) continue;\n // The smallest element containing the fragment is the one that shows it:\n // every ancestor contains it too. A label belongs to the element itself.\n let length = 0;\n if (!labelled(el, wanted)) {\n const text = rendered(el);\n length = text.includes(wanted) ? text.length : Number.POSITIVE_INFINITY;\n }\n if (length >= bestLength || !shown(el)) continue;\n best = el;\n bestLength = length;\n }\n if (!best) return null;\n best.scrollIntoView(centre);\n return 'text';\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -2,21 +2,30 @@
|
|
|
2
2
|
import {
|
|
3
3
|
MIN_NODE,
|
|
4
4
|
nodeSupported
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QDA5FTUH.js";
|
|
6
|
+
import "./chunk-BFI6P5DO.js";
|
|
6
7
|
import {
|
|
7
8
|
projectDir
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-F3R3QL7V.js";
|
|
9
10
|
|
|
10
11
|
// src/cli.ts
|
|
11
12
|
import { existsSync } from "fs";
|
|
13
|
+
import { createServer } from "http";
|
|
12
14
|
import { join, resolve } from "path";
|
|
13
|
-
|
|
15
|
+
import { parseArgs } from "util";
|
|
16
|
+
var USAGE = `Usage: stuntdouble [command]
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
studio [dir] Open Stunt Double Studio for a project (the default in a terminal)
|
|
19
|
+
run <checklist> Run a checklist on your own model connection (--cloud: in Stunt Double)
|
|
20
|
+
login Sign in to Stunt Double for cloud runs (--workspace <slug>)
|
|
21
|
+
logout Sign out of Stunt Double
|
|
22
|
+
mcp Serve the local browser and run tools over stdio (your agent starts this)
|
|
23
|
+
view [dir] Open the read-only viewer over a project's .stuntdouble/ folder
|
|
24
|
+
doctor Check Node, Chrome, the viewer port and how local runs are billed
|
|
25
|
+
|
|
26
|
+
Studio runs use your own Anthropic API key or OpenAI-compatible endpoint, never a Claude plan.`;
|
|
18
27
|
async function view(dir) {
|
|
19
|
-
const { startUi } = await import("./ui-server-
|
|
28
|
+
const { startUi } = await import("./ui-server-C7CFJWRD.js");
|
|
20
29
|
const base = resolve(dir ?? projectDir());
|
|
21
30
|
const root = existsSync(join(base, ".stuntdouble")) ? join(base, ".stuntdouble") : base;
|
|
22
31
|
const ui = await startUi({ root });
|
|
@@ -27,26 +36,174 @@ async function view(dir) {
|
|
|
27
36
|
Ctrl+C to stop.`);
|
|
28
37
|
process.on("SIGINT", () => ui.close().then(() => process.exit(0)));
|
|
29
38
|
}
|
|
30
|
-
|
|
39
|
+
async function studio(dir, flags) {
|
|
40
|
+
const { startStudio } = await import("./studio-FBC66BER.js");
|
|
41
|
+
const project = resolve(dir ?? projectDir());
|
|
42
|
+
const app = await startStudio({ project, port: flags.port ? Number(flags.port) : void 0 });
|
|
43
|
+
const link = app.link();
|
|
44
|
+
console.log(`Stunt Double Studio for ${project}
|
|
45
|
+
|
|
46
|
+
${link}
|
|
47
|
+
|
|
48
|
+
Ctrl+C to stop.`);
|
|
49
|
+
if (flags.open) (await import("./open-browser-CTOGH5HQ.js")).openBrowser(link);
|
|
50
|
+
let closing;
|
|
51
|
+
const shutdown = () => closing ??= app.close().finally(() => process.exit(0));
|
|
52
|
+
process.on("SIGINT", shutdown);
|
|
53
|
+
process.on("SIGTERM", shutdown);
|
|
54
|
+
}
|
|
55
|
+
async function login(workspace) {
|
|
56
|
+
const cloud = await import("./cloud-DLLNKYND.js");
|
|
57
|
+
let port = 0;
|
|
58
|
+
let finish = () => {
|
|
59
|
+
};
|
|
60
|
+
const finished = new Promise((ok, fail) => {
|
|
61
|
+
finish = (err) => err ? fail(err) : ok();
|
|
62
|
+
});
|
|
63
|
+
const server = createServer((req, res) => {
|
|
64
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
65
|
+
if (req.headers.host !== `127.0.0.1:${port}` || url.pathname !== "/oauth/callback") {
|
|
66
|
+
res.writeHead(404).end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const code = url.searchParams.get("code");
|
|
70
|
+
const done = code ? cloud.completeLogin(url.searchParams.get("state") ?? "", code) : Promise.reject(
|
|
71
|
+
new Error(url.searchParams.get("error_description") ?? "Sign-in was cancelled.")
|
|
72
|
+
);
|
|
73
|
+
done.then(
|
|
74
|
+
() => {
|
|
75
|
+
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
76
|
+
res.end("Signed in to Stunt Double. You can close this tab.");
|
|
77
|
+
finish();
|
|
78
|
+
},
|
|
79
|
+
(err) => {
|
|
80
|
+
res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
|
|
81
|
+
res.end(err.message);
|
|
82
|
+
finish(err);
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
|
|
87
|
+
port = server.address().port;
|
|
88
|
+
try {
|
|
89
|
+
const url = await cloud.beginLogin(`http://127.0.0.1:${port}/oauth/callback`);
|
|
90
|
+
console.log(`Sign in to Stunt Double in your browser:
|
|
91
|
+
|
|
92
|
+
${url}
|
|
93
|
+
`);
|
|
94
|
+
(await import("./open-browser-CTOGH5HQ.js")).openBrowser(url);
|
|
95
|
+
await finished;
|
|
96
|
+
} finally {
|
|
97
|
+
server.closeAllConnections();
|
|
98
|
+
server.close();
|
|
99
|
+
}
|
|
100
|
+
const workspaces = await cloud.listWorkspaces();
|
|
101
|
+
const chosen = workspace ? await cloud.chooseWorkspace(workspace) : workspaces.length === 1 ? workspaces[0] : null;
|
|
102
|
+
console.log(
|
|
103
|
+
chosen ? `Signed in. Cloud runs go to ${chosen.name} (${chosen.slug}).` : `Signed in. Choose a workspace with \`stuntdouble login --workspace <slug>\`, or in the Studio's Settings: ${workspaces.map((w) => w.slug).join(", ")}.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
async function run(checklist, flags) {
|
|
107
|
+
if (!checklist) throw new Error("Name a checklist: stuntdouble run <checklist>.");
|
|
108
|
+
const project = resolve(projectDir());
|
|
109
|
+
const { readRun, sdDir } = await import("./files-UBPF7PKW.js");
|
|
110
|
+
let runId;
|
|
111
|
+
let report;
|
|
112
|
+
if (flags.cloud) {
|
|
113
|
+
const { startCloudRun } = await import("./cloud-DLLNKYND.js");
|
|
114
|
+
const started = await startCloudRun({ project, checklist });
|
|
115
|
+
console.error(`Run ${started.runId} started in the cloud. Checking every 30 seconds.`);
|
|
116
|
+
runId = started.runId;
|
|
117
|
+
report = (await started.done).report;
|
|
118
|
+
} else {
|
|
119
|
+
const { resolveConnection } = await import("./credentials-QHESDY4K.js");
|
|
120
|
+
const connection = await resolveConnection();
|
|
121
|
+
if (!connection || !connection.apiKey && connection.provider === "anthropic") {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"No model connection: set ANTHROPIC_API_KEY, or set one up in `stuntdouble studio` (Settings)."
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const { LocalSession } = await import("./run-MPIAR3LQ.js");
|
|
127
|
+
const { startStandaloneRun } = await import("./runner-E45EJ76B.js");
|
|
128
|
+
const session = new LocalSession({
|
|
129
|
+
project,
|
|
130
|
+
runner: { model: { provider: connection.provider, id: connection.model } }
|
|
131
|
+
});
|
|
132
|
+
process.on("SIGINT", () => void session.close().finally(() => process.exit(130)));
|
|
133
|
+
const started = await startStandaloneRun({
|
|
134
|
+
session,
|
|
135
|
+
connection,
|
|
136
|
+
checklist,
|
|
137
|
+
resume: flags.resume,
|
|
138
|
+
limits: flags["max-steps"] ? { maxSteps: Number(flags["max-steps"]) } : void 0,
|
|
139
|
+
onProgress: (p) => console.error(` ${p.last.split("\n")[0].slice(0, 120)}`)
|
|
140
|
+
});
|
|
141
|
+
console.error(
|
|
142
|
+
`Run ${started.runId} started on ${connection.model}. Watch it or stop it at ${await session.openUi(`/runs/${started.runId}`)}`
|
|
143
|
+
);
|
|
144
|
+
runId = started.runId;
|
|
145
|
+
const done = await started.done;
|
|
146
|
+
await session.close();
|
|
147
|
+
const cost = done.usage.cost_usd != null ? `, about $${done.usage.cost_usd.toFixed(2)}` : "";
|
|
148
|
+
console.error(
|
|
149
|
+
`${done.usage.steps} model calls, ${(done.usage.input_tokens + done.usage.output_tokens).toLocaleString("en-GB")} tokens${cost}.`
|
|
150
|
+
);
|
|
151
|
+
report = done.report.replace(
|
|
152
|
+
/http:\/\/127\.0\.0\.1:\d+\/\S*\?t=\S+/g,
|
|
153
|
+
`.stuntdouble/runs/${runId}/ (open it with \`npx stuntdouble studio\`)`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
console.log(report);
|
|
157
|
+
const entry = await readRun(sdDir(project), runId);
|
|
158
|
+
process.exitCode = entry && "run" in entry && entry.run.score?.outcome === "passed" ? 0 : 1;
|
|
159
|
+
}
|
|
160
|
+
var { positionals, values } = parseArgs({
|
|
161
|
+
allowPositionals: true,
|
|
162
|
+
strict: false,
|
|
163
|
+
options: {
|
|
164
|
+
cloud: { type: "boolean", default: false },
|
|
165
|
+
resume: { type: "boolean", default: false },
|
|
166
|
+
"max-steps": { type: "string" },
|
|
167
|
+
port: { type: "string" },
|
|
168
|
+
"no-open": { type: "boolean", default: false },
|
|
169
|
+
workspace: { type: "string" }
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
var [given, arg] = positionals;
|
|
173
|
+
var command = given ?? (process.stdin.isTTY && process.stdout.isTTY ? "studio" : void 0);
|
|
31
174
|
if (!nodeSupported() && command !== "doctor") {
|
|
32
175
|
process.stderr.write(
|
|
33
|
-
`Stunt Double Local needs Node ${MIN_NODE.join(".")} or later (this is ${process.versions.node}). Update Node, then
|
|
176
|
+
`Stunt Double Local needs Node ${MIN_NODE.join(".")} or later (this is ${process.versions.node}). Update Node, then try again.
|
|
34
177
|
`
|
|
35
178
|
);
|
|
36
179
|
process.exit(1);
|
|
37
180
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} else {
|
|
49
|
-
|
|
50
|
-
|
|
181
|
+
try {
|
|
182
|
+
if (command === "mcp") {
|
|
183
|
+
const { serveMcp } = await import("./mcp-HFIAQ6HP.js");
|
|
184
|
+
await serveMcp();
|
|
185
|
+
} else if (command === "view") {
|
|
186
|
+
await view(arg);
|
|
187
|
+
} else if (command === "studio") {
|
|
188
|
+
await studio(arg, { port: values.port, open: !values["no-open"] });
|
|
189
|
+
} else if (command === "run") {
|
|
190
|
+
await run(arg, values);
|
|
191
|
+
} else if (command === "login") {
|
|
192
|
+
await login(values.workspace);
|
|
193
|
+
} else if (command === "logout") {
|
|
194
|
+
await (await import("./cloud-DLLNKYND.js")).logout();
|
|
195
|
+
console.log("Signed out of Stunt Double.");
|
|
196
|
+
} else if (command === "doctor") {
|
|
197
|
+
const { doctor } = await import("./doctor-ZAYOOCPT.js");
|
|
198
|
+
const { ok, lines } = await doctor();
|
|
199
|
+
console.log(lines.join("\n"));
|
|
200
|
+
process.exitCode = ok ? 0 : 1;
|
|
201
|
+
} else {
|
|
202
|
+
console.log(USAGE);
|
|
203
|
+
process.exitCode = command && command !== "help" && command !== "--help" ? 1 : 0;
|
|
204
|
+
}
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error(err.message);
|
|
207
|
+
process.exitCode = 1;
|
|
51
208
|
}
|
|
52
209
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// stuntdouble mcp | view [dir] | doctor\n\nimport { existsSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nimport { MIN_NODE, nodeSupported } from './doctor.ts';\nimport { projectDir } from './files.ts';\n\nconst USAGE = `Usage: stuntdouble <command>\n\n mcp Serve the local browser and run tools over stdio (your agent starts this)\n view [dir] Open the viewer over a project's .stuntdouble/ folder (default: this folder)\n doctor Check Node, Chrome, the viewer port and how local runs are billed`;\n\nasync function view(dir: string | undefined): Promise<void> {\n const { startUi } = await import('./ui-server.ts');\n const base = resolve(dir ?? projectDir());\n const root = existsSync(join(base, '.stuntdouble')) ? join(base, '.stuntdouble') : base;\n const ui = await startUi({ root });\n console.log(`Stunt Double viewer for ${root}\\n\\n ${ui.link()}\\n\\nCtrl+C to stop.`);\n process.on('SIGINT', () => ui.close().then(() => process.exit(0)));\n}\n\nconst [command, arg] = process.argv.slice(2);\n// npx does not enforce `engines`, and on an older Node the tools fail later and\n// stranger. stderr lands in the MCP client's log.\nif (!nodeSupported() && command !== 'doctor') {\n process.stderr.write(\n `Stunt Double Local needs Node ${MIN_NODE.join('.')} or later (this is ${process.versions.node}). Update Node, then restart Claude Code.\\n`\n );\n process.exit(1);\n}\nif (command === 'mcp') {\n const { serveMcp } = await import('./mcp.ts');\n await serveMcp();\n} else if (command === 'view') {\n await view(arg);\n} else if (command === 'doctor') {\n const { doctor } = await import('./doctor.ts');\n const { ok, lines } = await doctor();\n console.log(lines.join('\\n'));\n process.exitCode = ok ? 0 : 1;\n} else {\n console.log(USAGE);\n process.exitCode = command && command !== 'help' && command !== '--help' ? 1 : 0;\n}\n"],"mappings":";;;;;;;;;;AAGA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,eAAe;AAK9B,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAMd,eAAe,KAAK,KAAwC;AAC1D,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,yBAAgB;AACjD,QAAM,OAAO,QAAQ,OAAO,WAAW,CAAC;AACxC,QAAM,OAAO,WAAW,KAAK,MAAM,cAAc,CAAC,IAAI,KAAK,MAAM,cAAc,IAAI;AACnF,QAAM,KAAK,MAAM,QAAQ,EAAE,KAAK,CAAC;AACjC,UAAQ,IAAI,2BAA2B,IAAI;AAAA;AAAA,IAAS,GAAG,KAAK,CAAC;AAAA;AAAA,gBAAqB;AAClF,UAAQ,GAAG,UAAU,MAAM,GAAG,MAAM,EAAE,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC,CAAC;AACnE;AAEA,IAAM,CAAC,SAAS,GAAG,IAAI,QAAQ,KAAK,MAAM,CAAC;AAG3C,IAAI,CAAC,cAAc,KAAK,YAAY,UAAU;AAC5C,UAAQ,OAAO;AAAA,IACb,iCAAiC,SAAS,KAAK,GAAG,CAAC,sBAAsB,QAAQ,SAAS,IAAI;AAAA;AAAA,EAChG;AACA,UAAQ,KAAK,CAAC;AAChB;AACA,IAAI,YAAY,OAAO;AACrB,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,mBAAU;AAC5C,QAAM,SAAS;AACjB,WAAW,YAAY,QAAQ;AAC7B,QAAM,KAAK,GAAG;AAChB,WAAW,YAAY,UAAU;AAC/B,QAAM,EAAE,OAAO,IAAI,MAAM,OAAO,sBAAa;AAC7C,QAAM,EAAE,IAAI,MAAM,IAAI,MAAM,OAAO;AACnC,UAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC5B,UAAQ,WAAW,KAAK,IAAI;AAC9B,OAAO;AACL,UAAQ,IAAI,KAAK;AACjB,UAAQ,WAAW,WAAW,YAAY,UAAU,YAAY,WAAW,IAAI;AACjF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// stuntdouble studio [dir] | run <checklist> | login | logout | mcp | view [dir] | doctor\n//\n// `mcp` is what Claude Code starts: the person's session is the model and the\n// tools make no model calls. Everything else here is the standalone Studio,\n// which runs on a connection the person configures, never a Claude plan.\n\nimport { existsSync } from 'node:fs';\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { join, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { MIN_NODE, nodeSupported } from './doctor.ts';\nimport { projectDir } from './files.ts';\n\nconst USAGE = `Usage: stuntdouble [command]\n\n studio [dir] Open Stunt Double Studio for a project (the default in a terminal)\n run <checklist> Run a checklist on your own model connection (--cloud: in Stunt Double)\n login Sign in to Stunt Double for cloud runs (--workspace <slug>)\n logout Sign out of Stunt Double\n mcp Serve the local browser and run tools over stdio (your agent starts this)\n view [dir] Open the read-only viewer over a project's .stuntdouble/ folder\n doctor Check Node, Chrome, the viewer port and how local runs are billed\n\nStudio runs use your own Anthropic API key or OpenAI-compatible endpoint, never a Claude plan.`;\n\nasync function view(dir: string | undefined): Promise<void> {\n const { startUi } = await import('./ui-server.ts');\n const base = resolve(dir ?? projectDir());\n const root = existsSync(join(base, '.stuntdouble')) ? join(base, '.stuntdouble') : base;\n const ui = await startUi({ root });\n console.log(`Stunt Double viewer for ${root}\\n\\n ${ui.link()}\\n\\nCtrl+C to stop.`);\n process.on('SIGINT', () => ui.close().then(() => process.exit(0)));\n}\n\nasync function studio(dir: string | undefined, flags: { port?: string; open: boolean }) {\n const { startStudio } = await import('./studio.ts');\n const project = resolve(dir ?? projectDir());\n const app = await startStudio({ project, port: flags.port ? Number(flags.port) : undefined });\n const link = app.link();\n console.log(`Stunt Double Studio for ${project}\\n\\n ${link}\\n\\nCtrl+C to stop.`);\n if (flags.open) (await import('./open-browser.ts')).openBrowser(link);\n let closing: Promise<void> | undefined;\n const shutdown = () => (closing ??= app.close().finally(() => process.exit(0)));\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nasync function login(workspace: string | undefined): Promise<void> {\n const cloud = await import('./cloud.ts');\n // A loopback listener for the one callback, on a port of the system's choosing.\n let port = 0;\n let finish: (err?: Error) => void = () => {};\n const finished = new Promise<void>((ok, fail) => {\n finish = (err) => (err ? fail(err) : ok());\n });\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);\n if (req.headers.host !== `127.0.0.1:${port}` || url.pathname !== '/oauth/callback') {\n res.writeHead(404).end();\n return;\n }\n const code = url.searchParams.get('code');\n const done = code\n ? cloud.completeLogin(url.searchParams.get('state') ?? '', code)\n : Promise.reject(\n new Error(url.searchParams.get('error_description') ?? 'Sign-in was cancelled.')\n );\n done.then(\n () => {\n res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end('Signed in to Stunt Double. You can close this tab.');\n finish();\n },\n (err: Error) => {\n res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });\n res.end(err.message);\n finish(err);\n }\n );\n });\n await new Promise<void>((ok) => server.listen(0, '127.0.0.1', ok));\n port = (server.address() as AddressInfo).port;\n try {\n const url = await cloud.beginLogin(`http://127.0.0.1:${port}/oauth/callback`);\n console.log(`Sign in to Stunt Double in your browser:\\n\\n ${url}\\n`);\n (await import('./open-browser.ts')).openBrowser(url);\n await finished;\n } finally {\n server.closeAllConnections();\n server.close();\n }\n const workspaces = await cloud.listWorkspaces();\n const chosen = workspace\n ? await cloud.chooseWorkspace(workspace)\n : workspaces.length === 1\n ? workspaces[0]!\n : null;\n console.log(\n chosen\n ? `Signed in. Cloud runs go to ${chosen.name} (${chosen.slug}).`\n : `Signed in. Choose a workspace with \\`stuntdouble login --workspace <slug>\\`, or in the Studio's Settings: ${workspaces.map((w) => w.slug).join(', ')}.`\n );\n}\n\nasync function run(\n checklist: string | undefined,\n flags: { cloud: boolean; resume: boolean; 'max-steps'?: string }\n): Promise<void> {\n if (!checklist) throw new Error('Name a checklist: stuntdouble run <checklist>.');\n const project = resolve(projectDir());\n const { readRun, sdDir } = await import('./files.ts');\n let runId: string;\n let report: string;\n if (flags.cloud) {\n const { startCloudRun } = await import('./cloud.ts');\n const started = await startCloudRun({ project, checklist });\n console.error(`Run ${started.runId} started in the cloud. Checking every 30 seconds.`);\n runId = started.runId;\n report = (await started.done).report;\n } else {\n const { resolveConnection } = await import('./credentials.ts');\n const connection = await resolveConnection();\n if (!connection || (!connection.apiKey && connection.provider === 'anthropic')) {\n throw new Error(\n 'No model connection: set ANTHROPIC_API_KEY, or set one up in `stuntdouble studio` (Settings).'\n );\n }\n const { LocalSession } = await import('./run.ts');\n const { startStandaloneRun } = await import('./runner.ts');\n const session = new LocalSession({\n project,\n runner: { model: { provider: connection.provider, id: connection.model } },\n });\n process.on('SIGINT', () => void session.close().finally(() => process.exit(130)));\n const started = await startStandaloneRun({\n session,\n connection,\n checklist,\n resume: flags.resume,\n limits: flags['max-steps'] ? { maxSteps: Number(flags['max-steps']) } : undefined,\n onProgress: (p) => console.error(` ${p.last.split('\\n')[0]!.slice(0, 120)}`),\n });\n console.error(\n `Run ${started.runId} started on ${connection.model}. Watch it or stop it at ${await session.openUi(`/runs/${started.runId}`)}`\n );\n runId = started.runId;\n const done = await started.done;\n await session.close();\n const cost = done.usage.cost_usd != null ? `, about $${done.usage.cost_usd.toFixed(2)}` : '';\n console.error(\n `${done.usage.steps} model calls, ${(done.usage.input_tokens + done.usage.output_tokens).toLocaleString('en-GB')} tokens${cost}.`\n );\n // The viewer link closes with this process: point at the Studio instead.\n report = done.report.replace(\n /http:\\/\\/127\\.0\\.0\\.1:\\d+\\/\\S*\\?t=\\S+/g,\n `.stuntdouble/runs/${runId}/ (open it with \\`npx stuntdouble studio\\`)`\n );\n }\n console.log(report);\n const entry = await readRun(sdDir(project), runId);\n process.exitCode = entry && 'run' in entry && entry.run.score?.outcome === 'passed' ? 0 : 1;\n}\n\nconst { positionals, values } = parseArgs({\n allowPositionals: true,\n strict: false,\n options: {\n cloud: { type: 'boolean', default: false },\n resume: { type: 'boolean', default: false },\n 'max-steps': { type: 'string' },\n port: { type: 'string' },\n 'no-open': { type: 'boolean', default: false },\n workspace: { type: 'string' },\n },\n});\nconst [given, arg] = positionals;\n// No command in a terminal opens the Studio; an MCP client always says `mcp`.\nconst command = given ?? (process.stdin.isTTY && process.stdout.isTTY ? 'studio' : undefined);\n// npx does not enforce `engines`, and on an older Node the tools fail later and\n// stranger. stderr lands in the MCP client's log.\nif (!nodeSupported() && command !== 'doctor') {\n process.stderr.write(\n `Stunt Double Local needs Node ${MIN_NODE.join('.')} or later (this is ${process.versions.node}). Update Node, then try again.\\n`\n );\n process.exit(1);\n}\ntry {\n if (command === 'mcp') {\n const { serveMcp } = await import('./mcp.ts');\n await serveMcp();\n } else if (command === 'view') {\n await view(arg);\n } else if (command === 'studio') {\n await studio(arg, { port: values.port as string | undefined, open: !values['no-open'] });\n } else if (command === 'run') {\n await run(arg, values as { cloud: boolean; resume: boolean; 'max-steps'?: string });\n } else if (command === 'login') {\n await login(values.workspace as string | undefined);\n } else if (command === 'logout') {\n await (await import('./cloud.ts')).logout();\n console.log('Signed out of Stunt Double.');\n } else if (command === 'doctor') {\n const { doctor } = await import('./doctor.ts');\n const { ok, lines } = await doctor();\n console.log(lines.join('\\n'));\n process.exitCode = ok ? 0 : 1;\n } else {\n console.log(USAGE);\n process.exitCode = command && command !== 'help' && command !== '--help' ? 1 : 0;\n }\n} catch (err) {\n console.error((err as Error).message);\n process.exitCode = 1;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLOUD_POLL_MS,
|
|
3
|
+
CloudRefusal,
|
|
4
|
+
beginLogin,
|
|
5
|
+
billingUrl,
|
|
6
|
+
chooseWorkspace,
|
|
7
|
+
cloudServer,
|
|
8
|
+
cloudStatus,
|
|
9
|
+
completeLogin,
|
|
10
|
+
listWorkspaces,
|
|
11
|
+
logout,
|
|
12
|
+
planGate,
|
|
13
|
+
startCloudRun
|
|
14
|
+
} from "./chunk-DFXASIGV.js";
|
|
15
|
+
import "./chunk-MSV5OLSY.js";
|
|
16
|
+
import "./chunk-XHSDRB3X.js";
|
|
17
|
+
import "./chunk-BFI6P5DO.js";
|
|
18
|
+
import "./chunk-FMZTT2MC.js";
|
|
19
|
+
import "./chunk-F3R3QL7V.js";
|
|
20
|
+
export {
|
|
21
|
+
CLOUD_POLL_MS,
|
|
22
|
+
CloudRefusal,
|
|
23
|
+
beginLogin,
|
|
24
|
+
billingUrl,
|
|
25
|
+
chooseWorkspace,
|
|
26
|
+
cloudServer,
|
|
27
|
+
cloudStatus,
|
|
28
|
+
completeLogin,
|
|
29
|
+
listWorkspaces,
|
|
30
|
+
logout,
|
|
31
|
+
planGate,
|
|
32
|
+
startCloudRun
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=cloud-DLLNKYND.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_BASE_URLS,
|
|
3
|
+
DEFAULT_MODELS,
|
|
4
|
+
PROVIDERS,
|
|
5
|
+
connectionStatus,
|
|
6
|
+
forgetCredentials,
|
|
7
|
+
readCredentials,
|
|
8
|
+
resolveConnection,
|
|
9
|
+
saveConnection,
|
|
10
|
+
stuntdoubleHome,
|
|
11
|
+
updateCredentials,
|
|
12
|
+
writeCredentials
|
|
13
|
+
} from "./chunk-MSV5OLSY.js";
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_BASE_URLS,
|
|
16
|
+
DEFAULT_MODELS,
|
|
17
|
+
PROVIDERS,
|
|
18
|
+
connectionStatus,
|
|
19
|
+
forgetCredentials,
|
|
20
|
+
readCredentials,
|
|
21
|
+
resolveConnection,
|
|
22
|
+
saveConnection,
|
|
23
|
+
stuntdoubleHome,
|
|
24
|
+
updateCredentials,
|
|
25
|
+
writeCredentials
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=credentials-QHESDY4K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActorSchema,
|
|
3
|
+
ChecklistSchema,
|
|
4
|
+
ConfigSchema,
|
|
5
|
+
RESULT_STATUSES,
|
|
6
|
+
RUN_STATUSES,
|
|
7
|
+
RunSchema,
|
|
8
|
+
appendJsonl,
|
|
9
|
+
describeIssues,
|
|
10
|
+
effectiveStatus,
|
|
11
|
+
isAlive,
|
|
12
|
+
isLive,
|
|
13
|
+
projectDir,
|
|
14
|
+
readJson,
|
|
15
|
+
readRun,
|
|
16
|
+
readRuns,
|
|
17
|
+
runningMarker,
|
|
18
|
+
sdDir,
|
|
19
|
+
tallyRun,
|
|
20
|
+
writeFileAtomic,
|
|
21
|
+
writeJsonAtomic
|
|
22
|
+
} from "./chunk-F3R3QL7V.js";
|
|
23
|
+
export {
|
|
24
|
+
ActorSchema,
|
|
25
|
+
ChecklistSchema,
|
|
26
|
+
ConfigSchema,
|
|
27
|
+
RESULT_STATUSES,
|
|
28
|
+
RUN_STATUSES,
|
|
29
|
+
RunSchema,
|
|
30
|
+
appendJsonl,
|
|
31
|
+
describeIssues,
|
|
32
|
+
effectiveStatus,
|
|
33
|
+
isAlive,
|
|
34
|
+
isLive,
|
|
35
|
+
projectDir,
|
|
36
|
+
readJson,
|
|
37
|
+
readRun,
|
|
38
|
+
readRuns,
|
|
39
|
+
runningMarker,
|
|
40
|
+
sdDir,
|
|
41
|
+
tallyRun,
|
|
42
|
+
writeFileAtomic,
|
|
43
|
+
writeJsonAtomic
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=files-UBPF7PKW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|