@asrunjs/asrun 0.0.0-stage → 0.5.2
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/.claude/commands/explore-case.md +18 -0
- package/.claude/commands/gen-spec.md +16 -0
- package/.claude/commands/heal-spec.md +26 -0
- package/.claude/commands/make-case.md +11 -0
- package/.claude/settings.json +5 -0
- package/.claude/skills/exploring-cases/SKILL.md +146 -0
- package/.claude/skills/exploring-cases/references/assertion-targets.md +103 -0
- package/.claude/skills/exploring-cases/references/handoff-card.js +128 -0
- package/.claude/skills/exploring-cases/references/open-questions.md +116 -0
- package/.claude/skills/exploring-cases/references/readiness.md +146 -0
- package/.claude/skills/exploring-cases/references/user-handoff.md +157 -0
- package/.claude/skills/generating-specs/SKILL.md +219 -0
- package/.claude/skills/generating-specs/references/conventions.md +148 -0
- package/.claude/skills/making-cases/SKILL.md +72 -0
- package/.claude/skills/making-cases/references/gaps.md +36 -0
- package/.claude/skills/making-cases/references/normalizing.md +39 -0
- package/.claude/skills/making-cases/references/splitting.md +28 -0
- package/.claude/skills/making-cases/references/writes-and-email.md +37 -0
- package/AGENTS.md +159 -0
- package/CLAUDE.md +2 -0
- package/LICENSE +21 -0
- package/README.md +511 -2
- package/bin/asrun.mjs +49 -0
- package/dist/lib/account-lock.d.ts +6 -0
- package/dist/lib/account-lock.js +52 -0
- package/dist/lib/assertions.d.ts +14 -0
- package/dist/lib/assertions.js +39 -0
- package/dist/lib/baselines.d.ts +43 -0
- package/dist/lib/baselines.js +108 -0
- package/dist/lib/build-artifacts.d.ts +8 -0
- package/dist/lib/build-artifacts.js +10 -0
- package/dist/lib/case-doc.d.ts +311 -0
- package/dist/lib/case-doc.js +1117 -0
- package/dist/lib/case-id.d.ts +11 -0
- package/dist/lib/case-id.js +17 -0
- package/dist/lib/case-ops.d.ts +26 -0
- package/dist/lib/case-ops.js +110 -0
- package/dist/lib/cases.d.ts +16 -0
- package/dist/lib/cases.js +51 -0
- package/dist/lib/cli.d.ts +10 -0
- package/dist/lib/cli.js +18 -0
- package/dist/lib/command.d.ts +24 -0
- package/dist/lib/command.js +90 -0
- package/dist/lib/downloads.d.ts +42 -0
- package/dist/lib/downloads.js +84 -0
- package/dist/lib/email.d.ts +10 -0
- package/dist/lib/email.js +19 -0
- package/dist/lib/ffmpeg.d.ts +8 -0
- package/dist/lib/ffmpeg.js +50 -0
- package/dist/lib/fixtures.d.ts +40 -0
- package/dist/lib/fixtures.js +149 -0
- package/dist/lib/login-card.d.ts +22 -0
- package/dist/lib/login-card.js +183 -0
- package/dist/lib/node-cli.d.ts +15 -0
- package/dist/lib/node-cli.js +36 -0
- package/dist/lib/paths.d.ts +25 -0
- package/dist/lib/paths.js +88 -0
- package/dist/lib/pdf.d.ts +71 -0
- package/dist/lib/pdf.js +140 -0
- package/dist/lib/profiles.d.ts +62 -0
- package/dist/lib/profiles.js +174 -0
- package/dist/lib/ready.d.ts +36 -0
- package/dist/lib/ready.js +53 -0
- package/dist/lib/records.d.ts +33 -0
- package/dist/lib/records.js +61 -0
- package/dist/lib/run-filters.d.ts +19 -0
- package/dist/lib/run-filters.js +38 -0
- package/dist/lib/run-summary.d.ts +90 -0
- package/dist/lib/run-summary.js +35 -0
- package/dist/lib/session.d.ts +30 -0
- package/dist/lib/session.js +111 -0
- package/dist/lib/summary-reporter.d.ts +16 -0
- package/dist/lib/summary-reporter.js +146 -0
- package/dist/lib/targets.d.ts +19 -0
- package/dist/lib/targets.js +26 -0
- package/dist/lib/ui-build.d.ts +10 -0
- package/dist/lib/ui-build.js +30 -0
- package/dist/lib/values.d.ts +29 -0
- package/dist/lib/values.js +85 -0
- package/dist/lib/write-gate.d.ts +41 -0
- package/dist/lib/write-gate.js +129 -0
- package/dist/playwright.config.d.ts +2 -0
- package/dist/playwright.config.js +92 -0
- package/dist/scripts/build.d.ts +1 -0
- package/dist/scripts/build.js +21 -0
- package/dist/scripts/check-cases.d.ts +1 -0
- package/dist/scripts/check-cases.js +149 -0
- package/dist/scripts/cleanup.d.ts +1 -0
- package/dist/scripts/cleanup.js +25 -0
- package/dist/scripts/cli.d.ts +1 -0
- package/dist/scripts/cli.js +28 -0
- package/dist/scripts/doctor.d.ts +1 -0
- package/dist/scripts/doctor.js +296 -0
- package/dist/scripts/explore.d.ts +1 -0
- package/dist/scripts/explore.js +75 -0
- package/dist/scripts/go.mjs +188 -0
- package/dist/scripts/heal-evidence.mjs +143 -0
- package/dist/scripts/init.d.ts +22 -0
- package/dist/scripts/init.js +332 -0
- package/dist/scripts/lint-specs.d.ts +1 -0
- package/dist/scripts/lint-specs.js +141 -0
- package/dist/scripts/login.d.ts +1 -0
- package/dist/scripts/login.js +75 -0
- package/dist/scripts/new-case.d.ts +1 -0
- package/dist/scripts/new-case.js +19 -0
- package/dist/scripts/render-pdf.d.ts +1 -0
- package/dist/scripts/render-pdf.js +44 -0
- package/dist/scripts/report.d.ts +1 -0
- package/dist/scripts/report.js +14 -0
- package/dist/scripts/review-baselines.d.ts +1 -0
- package/dist/scripts/review-baselines.js +30 -0
- package/dist/scripts/run.d.ts +1 -0
- package/dist/scripts/run.js +423 -0
- package/dist/scripts/status.d.ts +1 -0
- package/dist/scripts/status.js +31 -0
- package/dist/scripts/ui-build.d.ts +1 -0
- package/dist/scripts/ui-build.js +7 -0
- package/dist/scripts/ui-dev.d.ts +1 -0
- package/dist/scripts/ui-dev.js +3 -0
- package/dist/scripts/ui.d.ts +1 -0
- package/dist/scripts/ui.js +13 -0
- package/dist/ui/dev.d.ts +1 -0
- package/dist/ui/dev.js +36 -0
- package/dist/ui/server/actions.d.ts +95 -0
- package/dist/ui/server/actions.js +365 -0
- package/dist/ui/server/ai-models.d.ts +3 -0
- package/dist/ui/server/ai-models.js +347 -0
- package/dist/ui/server/ai-stream.d.ts +66 -0
- package/dist/ui/server/ai-stream.js +408 -0
- package/dist/ui/server/app.d.ts +4 -0
- package/dist/ui/server/app.js +395 -0
- package/dist/ui/server/cases.d.ts +23 -0
- package/dist/ui/server/cases.js +170 -0
- package/dist/ui/server/files.d.ts +5 -0
- package/dist/ui/server/files.js +67 -0
- package/dist/ui/server/index.d.ts +1 -0
- package/dist/ui/server/index.js +33 -0
- package/dist/ui/server/jobs.d.ts +80 -0
- package/dist/ui/server/jobs.js +317 -0
- package/dist/ui/server/profiles.d.ts +35 -0
- package/dist/ui/server/profiles.js +371 -0
- package/dist/ui/server/watch-files.d.ts +4 -0
- package/dist/ui/server/watch-files.js +36 -0
- package/dist/ui/shared/ai-commands.d.ts +88 -0
- package/dist/ui/shared/ai-commands.js +177 -0
- package/dist/ui/shared/case-id.d.ts +1 -0
- package/dist/ui/shared/case-id.js +1 -0
- package/dist/ui/shared/jobs.d.ts +6 -0
- package/dist/ui/shared/jobs.js +7 -0
- package/dist/ui/shared/product.d.ts +3 -0
- package/dist/ui/shared/product.js +3 -0
- package/dist/ui/shared/run-filters.d.ts +1 -0
- package/dist/ui/shared/run-filters.js +1 -0
- package/dist/ui/shared/site-address.d.ts +6 -0
- package/dist/ui/shared/site-address.js +11 -0
- package/dist/ui/shared/types.d.ts +371 -0
- package/dist/ui/shared/types.js +1 -0
- package/docs/agents/framework-dev.md +79 -0
- package/docs/theme-guide.html +922 -0
- package/package.json +109 -3
- package/templates/.claude/commands/explore-case.md +22 -0
- package/templates/.claude/commands/gen-spec.md +21 -0
- package/templates/.claude/commands/heal-spec.md +31 -0
- package/templates/.claude/commands/make-case.md +13 -0
- package/templates/.claude/settings.json +5 -0
- package/templates/.mcp.json +8 -0
- package/templates/AGENTS.md +64 -0
- package/templates/CLAUDE.md +1 -0
- package/templates/cases/_template.md +57 -0
- package/templates/gitignore +13 -0
- package/templates/opencode.jsonc +16 -0
- package/templates/package.json +31 -0
- package/templates/playwright.config.ts +3 -0
- package/templates/pnpm-workspace.yaml +5 -0
- package/templates/profiles.yaml +42 -0
- package/templates/seed.spec.ts +9 -0
- package/templates/tsconfig.json +14 -0
- package/ui/dist/apple-touch-icon.png +0 -0
- package/ui/dist/assets/apl-gfMMiTNf.js +1 -0
- package/ui/dist/assets/asciiarmor-C_eisQCp.js +1 -0
- package/ui/dist/assets/asn1-DCl_8MCW.js +1 -0
- package/ui/dist/assets/asterisk-BEn0Cwpx.js +1 -0
- package/ui/dist/assets/brainfuck-D5EjA2JK.js +1 -0
- package/ui/dist/assets/clike-xvONpD0Z.js +1 -0
- package/ui/dist/assets/clojure-BflJGmDX.js +1 -0
- package/ui/dist/assets/cmake-84nbDH0I.js +1 -0
- package/ui/dist/assets/cobol-DHLQUbN7.js +1 -0
- package/ui/dist/assets/coffeescript-De_zI4J0.js +1 -0
- package/ui/dist/assets/commonlisp-DgAKBe0r.js +1 -0
- package/ui/dist/assets/crystal-Bl6YKCG_.js +1 -0
- package/ui/dist/assets/css-CnaYoQib.js +1 -0
- package/ui/dist/assets/cypher-p9eYesGn.js +1 -0
- package/ui/dist/assets/d-DWkQyNnU.js +1 -0
- package/ui/dist/assets/diff-ChtP43wD.js +1 -0
- package/ui/dist/assets/dist-BGbe0-Aa.js +1 -0
- package/ui/dist/assets/dist-Ba-HC662.js +2 -0
- package/ui/dist/assets/dist-BtMLMthJ.js +1 -0
- package/ui/dist/assets/dist-Bw5-uxs9.js +1 -0
- package/ui/dist/assets/dist-BzulKuec.js +1 -0
- package/ui/dist/assets/dist-C-Oyo1ZT.js +1 -0
- package/ui/dist/assets/dist-C8MdhqgG.js +1 -0
- package/ui/dist/assets/dist-CG1pHn5m.js +1 -0
- package/ui/dist/assets/dist-CgIQYY1o.js +1 -0
- package/ui/dist/assets/dist-CmpteMYY.js +9 -0
- package/ui/dist/assets/dist-DDJNvivM.js +1 -0
- package/ui/dist/assets/dist-DF3F_I8N.js +1 -0
- package/ui/dist/assets/dist-DSy3dVf4.js +11 -0
- package/ui/dist/assets/dist-Dlorc3Sx.js +1 -0
- package/ui/dist/assets/dist-Dq6sW5Ur.js +1 -0
- package/ui/dist/assets/dist-Dr9crbBz.js +13 -0
- package/ui/dist/assets/dist-jjxQutEm.js +1 -0
- package/ui/dist/assets/dockerfile-DJkHQgkl.js +1 -0
- package/ui/dist/assets/dtd-DTF72YNO.js +1 -0
- package/ui/dist/assets/dylan-S7ucR4pR.js +1 -0
- package/ui/dist/assets/ebnf-D4c0_ac3.js +1 -0
- package/ui/dist/assets/ecl-BVIeWmwY.js +1 -0
- package/ui/dist/assets/eiffel-BEjRio4Q.js +1 -0
- package/ui/dist/assets/elm-Cshzl8qu.js +1 -0
- package/ui/dist/assets/erlang-BV0c4Hdn.js +1 -0
- package/ui/dist/assets/factor-LKH5gN8L.js +1 -0
- package/ui/dist/assets/fcl-ClOhbFR7.js +1 -0
- package/ui/dist/assets/forth-D6RzAl64.js +1 -0
- package/ui/dist/assets/fortran-Bt6PBEDR.js +1 -0
- package/ui/dist/assets/gas-BHEdbvp9.js +1 -0
- package/ui/dist/assets/gherkin-oBAE_ms0.js +1 -0
- package/ui/dist/assets/groovy-CUrWKPia.js +1 -0
- package/ui/dist/assets/haskell-BRsoo5mP.js +1 -0
- package/ui/dist/assets/haxe-DjVOVvNP.js +1 -0
- package/ui/dist/assets/http-CLVgA2GD.js +1 -0
- package/ui/dist/assets/idl-BUZw3wgd.js +1 -0
- package/ui/dist/assets/index-CTmnF0Wp.css +2 -0
- package/ui/dist/assets/index-P_Yo0ZZn.js +338 -0
- package/ui/dist/assets/javascript-lS2r2ifg.js +1 -0
- package/ui/dist/assets/julia-CQfgDRfP.js +1 -0
- package/ui/dist/assets/livescript-BXxG0Yva.js +1 -0
- package/ui/dist/assets/lua-DPCEkfD_.js +1 -0
- package/ui/dist/assets/mathematica-u8YMmbU0.js +1 -0
- package/ui/dist/assets/mbox-BEMVcqjs.js +1 -0
- package/ui/dist/assets/mirc-C9zpzAZU.js +1 -0
- package/ui/dist/assets/mllike-B45xgp2S.js +1 -0
- package/ui/dist/assets/modelica-CqHI_q-D.js +1 -0
- package/ui/dist/assets/mscgen-qgSLujhx.js +1 -0
- package/ui/dist/assets/mumps-BSzj6Xyz.js +1 -0
- package/ui/dist/assets/nginx-C-ORUuei.js +1 -0
- package/ui/dist/assets/nsis-viPvXFiq.js +1 -0
- package/ui/dist/assets/ntriples-CzMBulkp.js +1 -0
- package/ui/dist/assets/octave-D2Q8cUp1.js +1 -0
- package/ui/dist/assets/oz-BwTct_5T.js +1 -0
- package/ui/dist/assets/pascal-Dp_KdFgX.js +1 -0
- package/ui/dist/assets/perl-BkSkiI9m.js +1 -0
- package/ui/dist/assets/pig-AF4Hwhju.js +1 -0
- package/ui/dist/assets/powershell-D8WpQPaX.js +1 -0
- package/ui/dist/assets/properties-C357ku5U.js +1 -0
- package/ui/dist/assets/protobuf-D1ij_kNL.js +1 -0
- package/ui/dist/assets/pug-BqCHZ5xT.js +1 -0
- package/ui/dist/assets/puppet-BZyAS7I4.js +1 -0
- package/ui/dist/assets/python-B_1Jd8HM.js +1 -0
- package/ui/dist/assets/q-B9NhS7L_.js +1 -0
- package/ui/dist/assets/r-CxDmU1I4.js +1 -0
- package/ui/dist/assets/rpm-gpDK5sbt.js +1 -0
- package/ui/dist/assets/ruby-ChfNwlCY.js +1 -0
- package/ui/dist/assets/sas-C2OyD2Y6.js +1 -0
- package/ui/dist/assets/scheme-D9qYN0V5.js +1 -0
- package/ui/dist/assets/shell-DwuoZtxw.js +1 -0
- package/ui/dist/assets/sieve-tLWDfEkZ.js +1 -0
- package/ui/dist/assets/simple-mode-DXiLM1-G.js +1 -0
- package/ui/dist/assets/smalltalk-BLp5-jwC.js +1 -0
- package/ui/dist/assets/solr-B78wSQJQ.js +1 -0
- package/ui/dist/assets/sparql-Dz-mlCAC.js +1 -0
- package/ui/dist/assets/spreadsheet-CTIncYxj.js +1 -0
- package/ui/dist/assets/sql-DuQMFDvy.js +1 -0
- package/ui/dist/assets/stex-DNzd62Q_.js +1 -0
- package/ui/dist/assets/stylus-BOVgKnng.js +1 -0
- package/ui/dist/assets/swift-DKB6_1j6.js +1 -0
- package/ui/dist/assets/tcl-BGfruO1u.js +1 -0
- package/ui/dist/assets/textile-C4jVoHG1.js +1 -0
- package/ui/dist/assets/tiddlywiki-VlUiK86J.js +1 -0
- package/ui/dist/assets/tiki-CvUEFusT.js +1 -0
- package/ui/dist/assets/toml-BPTmHmyx.js +1 -0
- package/ui/dist/assets/troff-C2dAgh7P.js +1 -0
- package/ui/dist/assets/ttcn-PPSTk1ow.js +1 -0
- package/ui/dist/assets/ttcn-cfg-2b4Gb5sE.js +1 -0
- package/ui/dist/assets/turtle-Cx3rYX2g.js +1 -0
- package/ui/dist/assets/vb-BthdM_4N.js +1 -0
- package/ui/dist/assets/vbscript-D9f6rSsU.js +1 -0
- package/ui/dist/assets/velocity-CqftRsjg.js +1 -0
- package/ui/dist/assets/verilog-CjI9Y6RI.js +1 -0
- package/ui/dist/assets/vhdl-kS471soi.js +1 -0
- package/ui/dist/assets/webidl-D5jiJvOU.js +1 -0
- package/ui/dist/assets/xquery-BKJx4mf9.js +1 -0
- package/ui/dist/assets/yacas-CXXgtw0p.js +1 -0
- package/ui/dist/assets/z80-Co3PbHdE.js +1 -0
- package/ui/dist/favicon.ico +0 -0
- package/ui/dist/favicon.svg +11 -0
- package/ui/dist/help/ai-tools.png +0 -0
- package/ui/dist/help/case-explore.png +0 -0
- package/ui/dist/help/case-generate.png +0 -0
- package/ui/dist/help/cases-memo.png +0 -0
- package/ui/dist/help/profile-form.png +0 -0
- package/ui/dist/help/profiles-login.png +0 -0
- package/ui/dist/help/reports.png +0 -0
- package/ui/dist/help/run-tests.png +0 -0
- package/ui/dist/index.html +23 -0
- package/ui/dist/logos/claude.svg +1 -0
- package/ui/dist/logos/opencode.svg +1 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export const AI_TOOLS = ['claude', 'opencode'];
|
|
2
|
+
export const DEFAULT_AI_TOOL = 'claude';
|
|
3
|
+
export const AI_TOOL_LABEL = { claude: 'Claude', opencode: 'OpenCode' };
|
|
4
|
+
export const AI_COMMANDS = ['explore-case', 'gen-spec', 'heal-spec'];
|
|
5
|
+
export const AI_COMMAND_LABEL = {
|
|
6
|
+
'explore-case': 'Explore case',
|
|
7
|
+
'gen-spec': 'Generate spec',
|
|
8
|
+
'heal-spec': 'Heal spec',
|
|
9
|
+
};
|
|
10
|
+
export const AI_COMMAND_WRITES = {
|
|
11
|
+
'explore-case': 'case',
|
|
12
|
+
'gen-spec': 'spec',
|
|
13
|
+
'heal-spec': 'files',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Every command opens real pages, so every command needs an environment and there is no default.
|
|
17
|
+
* Agents never write data in any of them: a person does those steps.
|
|
18
|
+
*/
|
|
19
|
+
export const AI_ENV_HINT = {
|
|
20
|
+
'explore-case': 'Where the agent explores. It only reads; with Attended on, you do the steps that write data.',
|
|
21
|
+
'gen-spec': 'Where the agent explores and verifies the spec.',
|
|
22
|
+
'heal-spec': 'Where the failing spec is diagnosed. Only the profiles that are logged in there are run.',
|
|
23
|
+
};
|
|
24
|
+
export const slashCommand = (command, caseId, options) => {
|
|
25
|
+
const { env, force, attended, profiles, hints, report } = options;
|
|
26
|
+
const heal = command === 'heal-spec';
|
|
27
|
+
return (`/${command} ${caseId} ${env}${force && command === 'gen-spec' ? ' --force' : ''}` +
|
|
28
|
+
`${attended && command === 'explore-case' ? ' --attended' : ''}` +
|
|
29
|
+
`${heal && profiles?.length ? ` --profiles ${profiles.join(',')}` : ''}` +
|
|
30
|
+
`${heal && report ? ` --report ${report}` : ''}` +
|
|
31
|
+
`${heal && hints ? ` --hints ${hints}` : ''}`);
|
|
32
|
+
};
|
|
33
|
+
/** The MCP tools of the Playwright server that agents explore with. */
|
|
34
|
+
const PLAYWRIGHT_MCP = 'mcp__playwright-test__*';
|
|
35
|
+
// Claude Code asks before using an MCP tool or running a command; a one-shot run cannot answer, so what the agent
|
|
36
|
+
// needs is allowed up front. Edit rules are limited to the folders the command may change.
|
|
37
|
+
function claudeAllowedTools(writes) {
|
|
38
|
+
// `node --import tsx` is the same check without tsx's CLI, which needs a local socket that Claude's sandbox refuses.
|
|
39
|
+
// The scripts sit in the framework folder (a submodule, so the path varies), hence the open prefix.
|
|
40
|
+
const nodeTsx = 'Bash(node --import tsx:*)';
|
|
41
|
+
const check = ['Bash(pnpm case:check:*)', nodeTsx];
|
|
42
|
+
const common = [PLAYWRIGHT_MCP, 'Bash(pnpm status:*)', 'Bash(pnpm pdf:render:*)', ...check];
|
|
43
|
+
if (writes === 'case')
|
|
44
|
+
return [...common, 'Edit(/cases/**)'];
|
|
45
|
+
if (writes === 'cases-new')
|
|
46
|
+
return [...check, 'Edit(/cases/**)'];
|
|
47
|
+
const spec = [
|
|
48
|
+
...common,
|
|
49
|
+
'Edit(/cases/**)',
|
|
50
|
+
'Edit(/tests/**)',
|
|
51
|
+
'Bash(pnpm typecheck:*)',
|
|
52
|
+
'Bash(pnpm spec:lint:*)',
|
|
53
|
+
nodeTsx,
|
|
54
|
+
'Bash(pnpm baselines:review:*)',
|
|
55
|
+
];
|
|
56
|
+
// Running specs is a separate process: only healing does it. `pnpm asrun` runs outside Claude's sandbox via
|
|
57
|
+
// `sandbox.excludedCommands` in .claude/settings.json, because tsx and the browser need local sockets and network.
|
|
58
|
+
// Healing reads the existing report first (`pnpm heal:evidence`) and reruns only when that is not enough.
|
|
59
|
+
return writes === 'spec' ? spec : [...spec, 'Bash(pnpm asrun:*)', 'Bash(pnpm heal:evidence:*)'];
|
|
60
|
+
}
|
|
61
|
+
// Never allowed in a one-shot run, whatever the command: AGENTS.md forbids agents to lift the write confirmation.
|
|
62
|
+
const CLAUDE_DENIED_TOOLS = ['Bash(*--allow-writes*)', 'Bash(*ASRUN_ALLOW_WRITES*)'];
|
|
63
|
+
/**
|
|
64
|
+
* Arguments that make the CLI print what it does as it goes (one JSON event per line) instead of the final answer only,
|
|
65
|
+
* so the job log shows which tool call is running. The server turns the events into log lines (`ai-stream.ts`).
|
|
66
|
+
*/
|
|
67
|
+
const STREAM_ARGS = {
|
|
68
|
+
claude: ['--output-format', 'stream-json', '--verbose'],
|
|
69
|
+
opencode: ['--format', 'json'],
|
|
70
|
+
};
|
|
71
|
+
function invocation(tool, prompt, writes, session, model, thinking) {
|
|
72
|
+
// OpenCode creates the session when the id does not exist yet, and continues it when it does.
|
|
73
|
+
// A model is named on every run: without one OpenCode falls back to its built-in free model, which has a daily
|
|
74
|
+
// usage limit, and nothing in the CLI's own state (the TUI's last-used model) applies to a one-shot run.
|
|
75
|
+
if (tool === 'opencode')
|
|
76
|
+
return {
|
|
77
|
+
file: 'opencode',
|
|
78
|
+
args: [
|
|
79
|
+
'run',
|
|
80
|
+
'--auto',
|
|
81
|
+
...STREAM_ARGS.opencode,
|
|
82
|
+
...(session ? ['-s', session.id] : []),
|
|
83
|
+
...(model ? ['--model', model] : []),
|
|
84
|
+
// Without it OpenCode does not ask the model to think, so the reasoning it does would never reach the log.
|
|
85
|
+
// Claude Code has no such flag: it shows thinking when the model and its settings have it on.
|
|
86
|
+
...(thinking ? ['--thinking'] : []),
|
|
87
|
+
prompt,
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
file: 'claude',
|
|
92
|
+
args: [
|
|
93
|
+
'-p',
|
|
94
|
+
prompt,
|
|
95
|
+
...STREAM_ARGS.claude,
|
|
96
|
+
...(session ? [session.resume ? '--resume' : '--session-id', session.id] : []),
|
|
97
|
+
// `acceptEdits` approves edits anywhere in the folder, which would void the narrow Edit rules below.
|
|
98
|
+
...(writes === 'files' || writes === 'spec' ? ['--permission-mode', 'acceptEdits'] : []),
|
|
99
|
+
...(model ? ['--model', model] : []),
|
|
100
|
+
'--allowedTools',
|
|
101
|
+
...claudeAllowedTools(writes),
|
|
102
|
+
'--disallowedTools',
|
|
103
|
+
...CLAUDE_DENIED_TOOLS,
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const commandLine = ({ file, args }) => [file, ...args.map((arg) => (/^[\w@=,.:/-]+$/.test(arg) ? arg : JSON.stringify(arg)))].join(' ');
|
|
108
|
+
/**
|
|
109
|
+
* Said in every one-shot run: nobody is there to answer, so questions go into the case file and the last line
|
|
110
|
+
* is a result the UI can read. The skills define the details.
|
|
111
|
+
*/
|
|
112
|
+
const NON_INTERACTIVE = 'Non-interactive run: nobody can answer questions, so do not stop to ask. Write open questions into the case file ' +
|
|
113
|
+
'as the skill describes and end with the RESULT: line.';
|
|
114
|
+
/**
|
|
115
|
+
* An attended exploration: nobody answers questions in the chat, but a person sits at the machine and works in the
|
|
116
|
+
* browser window, so the agent hands them the steps that write data and waits, as user-handoff.md describes.
|
|
117
|
+
*/
|
|
118
|
+
const ATTENDED = 'Attended run: a person is at the machine and the browser window is visible, so hand steps that write data or ' +
|
|
119
|
+
'need a choice to them and wait, as the skill describes. Start every handoff (also a second one in the same run) ' +
|
|
120
|
+
'with its own call that sets __handoffCfg, before the call that runs handoff-card.js, and never change those options ' +
|
|
121
|
+
'inside the call that runs the card: the web UI sees that a person is needed only from that call, so without it ' +
|
|
122
|
+
'there is no "Your turn" badge and no notification. Nobody can answer questions in the chat, so write open ' +
|
|
123
|
+
'questions into the case file as the skill describes and end with the RESULT: line.';
|
|
124
|
+
/** The prompt of a one-shot run. `heal-spec` has no questions of its own, so it keeps the plain slash command. */
|
|
125
|
+
export function headlessPrompt(command, caseId, options) {
|
|
126
|
+
const slash = slashCommand(command, caseId, options);
|
|
127
|
+
if (command === 'heal-spec')
|
|
128
|
+
return slash;
|
|
129
|
+
if (command === 'explore-case' && options.attended)
|
|
130
|
+
return `${slash} ${ATTENDED}`;
|
|
131
|
+
return `${slash} ${NON_INTERACTIVE}`;
|
|
132
|
+
}
|
|
133
|
+
/** Executable and arguments of the one-shot (non-interactive) run. */
|
|
134
|
+
export function headlessInvocation(tool, command, caseId, options, session) {
|
|
135
|
+
return invocation(tool, headlessPrompt(command, caseId, options), AI_COMMAND_WRITES[command], session, options.model, options.thinking);
|
|
136
|
+
}
|
|
137
|
+
/** The one-shot command as it is typed in a shell. */
|
|
138
|
+
export function headlessCommandLine(tool, command, caseId, options, session) {
|
|
139
|
+
return commandLine(headlessInvocation(tool, command, caseId, options, session));
|
|
140
|
+
}
|
|
141
|
+
/** Folder (relative to the AsRun root, git-ignored) where the clues typed into the Heal dialog are stored. */
|
|
142
|
+
export const HINTS_DIR = '.tmp/hints';
|
|
143
|
+
export const HINTS_MAX_CHARS = 8000;
|
|
144
|
+
/** Folder (relative to the AsRun root, git-ignored) where the answers typed into the Continue box are stored. */
|
|
145
|
+
export const REPLY_DIR = '.tmp/replies';
|
|
146
|
+
export const REPLY_MAX_CHARS = 4000;
|
|
147
|
+
export const USE_DEFAULTS_REPLY = 'Accept the suggestion of every open question: delete its TODO block and keep the sentence above it as confirmed text.';
|
|
148
|
+
/**
|
|
149
|
+
* The next turn of a run that ended with `RESULT: needs-input`: the user's answers. They go into the case file, which
|
|
150
|
+
* stays the record of truth, and the task of the original command is finished. The answers come from a file
|
|
151
|
+
* (`.tmp/replies/…md`) rather than from the prompt, so a line break or a quote in them cannot break the command line.
|
|
152
|
+
*/
|
|
153
|
+
export function replyPrompt(command, replyFile) {
|
|
154
|
+
return (`The user answers your open questions (still nobody to ask, so do not stop to ask again). ` +
|
|
155
|
+
`Their answers are in ${replyFile}, relative to the project root: read that file first.\n` +
|
|
156
|
+
'Fold every answer into the case file as the skill describes: write the answer into the sentence above the ' +
|
|
157
|
+
'TODO block and remove the block, and keep a TODO block (with an empty Answer: line, emoji prefixes included) for what is still unknown. ' +
|
|
158
|
+
`Then finish the task of /${command} and end with the RESULT: line.`);
|
|
159
|
+
}
|
|
160
|
+
/** The next turn of an earlier run, in its session and with exactly the permissions of the first turn. */
|
|
161
|
+
export function continueInvocation(tool, command, replyFile, sessionId, model, thinking) {
|
|
162
|
+
return invocation(tool, replyPrompt(command, replyFile), AI_COMMAND_WRITES[command], { id: sessionId, resume: true }, model, thinking);
|
|
163
|
+
}
|
|
164
|
+
export const continueCommandLine = (tool, command, replyFile, sessionId, model, thinking) => commandLine(continueInvocation(tool, command, replyFile, sessionId, model, thinking));
|
|
165
|
+
/** Folder (relative to the AsRun root, git-ignored) where memos from the Cases page are stored. */
|
|
166
|
+
export const MEMO_DIR = '.tmp/memos';
|
|
167
|
+
export const MEMO_MAX_CHARS = 100_000;
|
|
168
|
+
/** /make-case is interactive by design; a one-shot run cannot answer questions, so it must use the defaults. */
|
|
169
|
+
export const makeCasePrompt = (memoPath) => `/make-case ${memoPath} ` +
|
|
170
|
+
'Non-interactive run: nobody can answer questions, so do not stop to ask. ' +
|
|
171
|
+
'Do not open a browser or explore pages. Write the suggested default of every gap as a normal sentence and put a ' +
|
|
172
|
+
'TODO block with an empty Answer: line under it, as open-questions.md describes (every marker starts with its emoji). ' +
|
|
173
|
+
'Never overwrite an existing case: choose a new id instead. ' +
|
|
174
|
+
'Put the cases of one area in a group folder (cases/<group>/<name>.md, id <group>/<name>), reusing an existing folder when it fits. ' +
|
|
175
|
+
'Write the cases, run pnpm case:check, and list the open questions in your final report.';
|
|
176
|
+
export const makeCaseInvocation = (tool, memoPath, model, thinking) => invocation(tool, makeCasePrompt(memoPath), 'cases-new', undefined, model, thinking);
|
|
177
|
+
export const makeCaseCommandLine = (tool, memoPath, model, thinking) => commandLine(makeCaseInvocation(tool, memoPath, model, thinking));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../lib/case-id.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../lib/case-id.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JobInfo } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The run that took over the answers of a `needs-input` run. A continuation that failed or was cancelled does not
|
|
4
|
+
* count: the questions are still open, so the earlier run can be continued again.
|
|
5
|
+
*/
|
|
6
|
+
export declare function findContinuation(jobs: readonly JobInfo[], jobId: string): JobInfo | undefined;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run that took over the answers of a `needs-input` run. A continuation that failed or was cancelled does not
|
|
3
|
+
* count: the questions are still open, so the earlier run can be continued again.
|
|
4
|
+
*/
|
|
5
|
+
export function findContinuation(jobs, jobId) {
|
|
6
|
+
return jobs.find((job) => job.continuesJobId === jobId && (job.status === 'running' || job.status === 'succeeded'));
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { casePattern } from '../../lib/run-filters.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { casePattern } from '../../lib/run-filters.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completes a site address without a URL scheme: `example.com` becomes `https://example.com`.
|
|
3
|
+
* Anything that already starts with a scheme (`https://`, `http://`, `ftp://`, ...) is left alone,
|
|
4
|
+
* so the caller still decides whether the scheme is allowed.
|
|
5
|
+
*/
|
|
6
|
+
export declare function withScheme(value: string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** `https://`, `http://` and any other scheme the user typed: kept exactly as given. */
|
|
2
|
+
const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
3
|
+
/**
|
|
4
|
+
* Completes a site address without a URL scheme: `example.com` becomes `https://example.com`.
|
|
5
|
+
* Anything that already starts with a scheme (`https://`, `http://`, `ftp://`, ...) is left alone,
|
|
6
|
+
* so the caller still decides whether the scheme is allowed.
|
|
7
|
+
*/
|
|
8
|
+
export function withScheme(value) {
|
|
9
|
+
const text = value.trim();
|
|
10
|
+
return HAS_SCHEME.test(text) ? text : `https://${text}`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import type { ResultStatus } from '../../lib/run-summary.js';
|
|
2
|
+
import type { AiCommand, AiTool } from './ai-commands.js';
|
|
3
|
+
export type JobStatus = 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
4
|
+
export type JobKind = 'signin' | 'visit' | 'run' | 'ai';
|
|
5
|
+
/** What changed on disk: case files (`cases/**`) and/or specs (`tests/**`). */
|
|
6
|
+
export interface FilesChange {
|
|
7
|
+
cases: boolean;
|
|
8
|
+
specs: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface JobInfo {
|
|
11
|
+
id: string;
|
|
12
|
+
kind: JobKind;
|
|
13
|
+
/** Set for `signin` and `visit` jobs. */
|
|
14
|
+
profile?: string;
|
|
15
|
+
/** Environment of the job (`signin`, `visit` and `run`). */
|
|
16
|
+
env?: string;
|
|
17
|
+
/** Human-readable command, e.g. `pnpm signin example-staging`. */
|
|
18
|
+
command: string;
|
|
19
|
+
status: JobStatus;
|
|
20
|
+
exitCode: number | null;
|
|
21
|
+
signal: string | null;
|
|
22
|
+
startedAt: string;
|
|
23
|
+
endedAt: string | null;
|
|
24
|
+
/** The process reads stdin (used by `signin`, which waits for Enter). */
|
|
25
|
+
acceptsInput: boolean;
|
|
26
|
+
/** Set for a test run that overwrote the screenshot baselines of these cases. */
|
|
27
|
+
baselineCases?: string[];
|
|
28
|
+
/** Set for a test run: the folder under reports/ it writes (it may only exist once the run has produced one). */
|
|
29
|
+
reportId?: string;
|
|
30
|
+
/** Set for the one-shot runs of an AI command on a case. */
|
|
31
|
+
caseId?: string;
|
|
32
|
+
/** Set for the one-shot runs of an AI command on a case: which command ran. */
|
|
33
|
+
aiCommand?: AiCommand;
|
|
34
|
+
/** The conversation of an AI run; a later job can resume it. */
|
|
35
|
+
sessionId?: string;
|
|
36
|
+
/** The model the AI run was started with; omitted = the CLI's own choice. */
|
|
37
|
+
aiModel?: string;
|
|
38
|
+
/** What the agent said in its last `RESULT:` line; null when it did not say or the run did not succeed. */
|
|
39
|
+
resultState?: ResultState | null;
|
|
40
|
+
/** The earlier job whose conversation this job continues. */
|
|
41
|
+
continuesJobId?: string;
|
|
42
|
+
/** An AI run that has handed a step to the person in the browser window and waits for them to press Done. */
|
|
43
|
+
awaitingUser?: AwaitingUserInfo | null;
|
|
44
|
+
}
|
|
45
|
+
export interface AwaitingUserInfo {
|
|
46
|
+
/** What the person is asked to do, as the agent wrote it on the card; null when unknown. */
|
|
47
|
+
message: string | null;
|
|
48
|
+
since: string;
|
|
49
|
+
}
|
|
50
|
+
/** `needs-input`: the agent wrote open questions into the case file and cannot go on without answers. */
|
|
51
|
+
export type ResultState = 'ready' | 'needs-input';
|
|
52
|
+
/**
|
|
53
|
+
* How one block of an AI job's log is presented. Absent for the plain output of every other job, which stays raw.
|
|
54
|
+
* `markdown`: what the agent said; `reasoning`: what it was thinking (not a result); `tool`: one call, started or
|
|
55
|
+
* finished; `result`: the first lines of a call's output; `error` / `hint`: what failed and what to do about it;
|
|
56
|
+
* `meta`: anything else a person should read (the waiting line, noise the CLI printed).
|
|
57
|
+
*/
|
|
58
|
+
export type JobChunkKind = 'tool' | 'result' | 'reasoning' | 'markdown' | 'error' | 'hint' | 'meta';
|
|
59
|
+
/** A tool call of an AI job, so the log can color its name, its outcome and the time it took. */
|
|
60
|
+
export interface JobChunkTool {
|
|
61
|
+
name: string;
|
|
62
|
+
/** What the call is about (the file, the command, the intent); empty when the CLI gave nothing telling. */
|
|
63
|
+
detail: string;
|
|
64
|
+
ok: boolean;
|
|
65
|
+
/** Still running: Claude reports the start of a call, OpenCode only its end. */
|
|
66
|
+
pending?: boolean;
|
|
67
|
+
/** How long it ran, as a person reads it (`0.4 s`); absent when the CLI did not report a start time. */
|
|
68
|
+
took?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface JobChunk {
|
|
71
|
+
seq: number;
|
|
72
|
+
stream: 'stdout' | 'stderr' | 'system';
|
|
73
|
+
data: string;
|
|
74
|
+
/** Set for the blocks of an AI job (see `JobChunkKind`); absent for a plain command's output. */
|
|
75
|
+
kind?: JobChunkKind;
|
|
76
|
+
/** When the block was produced, for AI jobs: shown as its own styled timestamp, not as part of the text. */
|
|
77
|
+
at?: string;
|
|
78
|
+
tool?: JobChunkTool;
|
|
79
|
+
}
|
|
80
|
+
export type StartJobRequest = {
|
|
81
|
+
action: 'signin';
|
|
82
|
+
profile: string;
|
|
83
|
+
env: string;
|
|
84
|
+
} | {
|
|
85
|
+
action: 'visit';
|
|
86
|
+
profile: string;
|
|
87
|
+
env: string;
|
|
88
|
+
} | {
|
|
89
|
+
action: 'ai';
|
|
90
|
+
tool: AiTool;
|
|
91
|
+
command: AiCommand;
|
|
92
|
+
caseId: string;
|
|
93
|
+
env: string;
|
|
94
|
+
force?: boolean;
|
|
95
|
+
attended?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Show the agent's reasoning in the log (`--thinking` for OpenCode; Claude shows it when its model has it on).
|
|
98
|
+
* Off by default: it costs tokens and time.
|
|
99
|
+
*/
|
|
100
|
+
thinking?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The model to run the agent with (as the CLI names it). Omitted = the CLI's own choice, which for OpenCode is
|
|
103
|
+
* its built-in free model. `heal-spec` keeps the model of the run it continues.
|
|
104
|
+
*/
|
|
105
|
+
model?: string;
|
|
106
|
+
/** `heal-spec` only: the profiles to heal; omitted = every profile that is logged in. */
|
|
107
|
+
profiles?: string[];
|
|
108
|
+
/** `heal-spec` only: what the user knows (optional). The server stores it as a file for the agent. */
|
|
109
|
+
hints?: string;
|
|
110
|
+
/** `heal-spec` only: the report to read first (folder name under reports/); omitted = the newest one. */
|
|
111
|
+
report?: string;
|
|
112
|
+
} | {
|
|
113
|
+
action: 'continue';
|
|
114
|
+
jobId: string;
|
|
115
|
+
reply: string;
|
|
116
|
+
} | {
|
|
117
|
+
action: 'make-cases';
|
|
118
|
+
tool: AiTool;
|
|
119
|
+
memo: string;
|
|
120
|
+
model?: string;
|
|
121
|
+
thinking?: boolean;
|
|
122
|
+
} | {
|
|
123
|
+
action: 'run';
|
|
124
|
+
env: string;
|
|
125
|
+
profiles?: string[];
|
|
126
|
+
/** Only the profiles that carry one of these tags. */
|
|
127
|
+
tags?: string[];
|
|
128
|
+
suites?: string[];
|
|
129
|
+
grep?: string;
|
|
130
|
+
stepText?: boolean;
|
|
131
|
+
/** Leave out the cases that write data. */
|
|
132
|
+
readOnly?: boolean;
|
|
133
|
+
/** Cases (ids) whose screenshot baselines are overwritten with what the page shows now (`--update-baselines`). */
|
|
134
|
+
updateBaselines?: string[];
|
|
135
|
+
/** The environment the user confirmed write cases for. Must equal `env`; checked again on the server. */
|
|
136
|
+
confirmWritesIn?: string;
|
|
137
|
+
};
|
|
138
|
+
/** A case that writes data and would run, as listed in the confirmation. */
|
|
139
|
+
export interface WriteCaseInfo {
|
|
140
|
+
id: string;
|
|
141
|
+
note: string;
|
|
142
|
+
profiles: string[];
|
|
143
|
+
}
|
|
144
|
+
export interface RunPreviewRequest {
|
|
145
|
+
env: string;
|
|
146
|
+
profiles?: string[];
|
|
147
|
+
tags?: string[];
|
|
148
|
+
suites?: string[];
|
|
149
|
+
grep?: string;
|
|
150
|
+
readOnly?: boolean;
|
|
151
|
+
}
|
|
152
|
+
export interface RunPreview {
|
|
153
|
+
env: string;
|
|
154
|
+
/** True when the environment is protected and the run includes write cases: the user must confirm. */
|
|
155
|
+
needsConfirmation: boolean;
|
|
156
|
+
writeCases: WriteCaseInfo[];
|
|
157
|
+
/** Cases that write only in other environments; they are skipped in this run. */
|
|
158
|
+
skippedWriteCases: WriteCaseInfo[];
|
|
159
|
+
}
|
|
160
|
+
export interface PlannedCaseInfo {
|
|
161
|
+
id: string;
|
|
162
|
+
profiles: string[];
|
|
163
|
+
writes: boolean;
|
|
164
|
+
/** Why the case is skipped in this environment; null when it runs. */
|
|
165
|
+
skip: string | null;
|
|
166
|
+
/** How many baseline images the case has on disk (all profiles and environments). */
|
|
167
|
+
baselineCount: number;
|
|
168
|
+
}
|
|
169
|
+
/** One screenshot baseline, with what it must show (from the target card of the case). */
|
|
170
|
+
export interface BaselineItem {
|
|
171
|
+
caseId: string;
|
|
172
|
+
file: string;
|
|
173
|
+
profile: string | null;
|
|
174
|
+
env: string | null;
|
|
175
|
+
/** New or modified in git. */
|
|
176
|
+
changed: boolean;
|
|
177
|
+
mtimeMs: number;
|
|
178
|
+
targetId: string | null;
|
|
179
|
+
card: {
|
|
180
|
+
id: string;
|
|
181
|
+
name: string;
|
|
182
|
+
what: string | null;
|
|
183
|
+
where: string | null;
|
|
184
|
+
not: string | null;
|
|
185
|
+
pages: string | null;
|
|
186
|
+
region: string | null;
|
|
187
|
+
} | null;
|
|
188
|
+
problem: string | null;
|
|
189
|
+
}
|
|
190
|
+
/** The cases a run with the chosen filters would list. */
|
|
191
|
+
export interface RunPlan {
|
|
192
|
+
env: string;
|
|
193
|
+
/** Playwright could not list the tests (for example an invalid search expression). */
|
|
194
|
+
error: string | null;
|
|
195
|
+
/** Cases that run (skipped ones not counted). */
|
|
196
|
+
totalCases: number;
|
|
197
|
+
/** Case x profile combinations that run. */
|
|
198
|
+
totalTests: number;
|
|
199
|
+
cases: PlannedCaseInfo[];
|
|
200
|
+
}
|
|
201
|
+
export type ProfileStatus = 'missing' | 'unverified' | 'ok' | 'expired' | 'error';
|
|
202
|
+
/** One profile: a row of the Profiles page. */
|
|
203
|
+
export interface SessionCheckInfo {
|
|
204
|
+
/** Where a visitor without a valid session is sent: a path on the site (`/login`) or a full URL (single sign-on). */
|
|
205
|
+
loggedOutUrl: string;
|
|
206
|
+
/** A selector that only exists while signed in; null when the profile does not name one. */
|
|
207
|
+
loggedInSelector: string | null;
|
|
208
|
+
/** A selector that proves the visitor is signed out, e.g. the password field of a login form. */
|
|
209
|
+
loggedOutSelector: string | null;
|
|
210
|
+
/** How long to wait for one of those signals, in milliseconds. */
|
|
211
|
+
timeoutMs: number;
|
|
212
|
+
}
|
|
213
|
+
export interface ProfileInfo {
|
|
214
|
+
id: string;
|
|
215
|
+
name: string;
|
|
216
|
+
env: string;
|
|
217
|
+
baseURL: string;
|
|
218
|
+
account: string | null;
|
|
219
|
+
tags: string[];
|
|
220
|
+
/** The page "Visit" opens after a login, e.g. `/app/home`; empty = the site root. */
|
|
221
|
+
startPath: string;
|
|
222
|
+
/** A page only a signed-in visitor sees; `/account/` unless set in profiles.yaml. */
|
|
223
|
+
sessionCheckPath: string;
|
|
224
|
+
/** How that page shows a missing login. */
|
|
225
|
+
sessionCheck: SessionCheckInfo;
|
|
226
|
+
/** Tests may send real email in this environment. Always false for production. */
|
|
227
|
+
email: boolean;
|
|
228
|
+
status: ProfileStatus;
|
|
229
|
+
verifying: boolean;
|
|
230
|
+
/** When the session file was last written. */
|
|
231
|
+
savedAt: string | null;
|
|
232
|
+
checkedAt: string | null;
|
|
233
|
+
error?: string;
|
|
234
|
+
}
|
|
235
|
+
export interface AddProfileRequest {
|
|
236
|
+
id: string;
|
|
237
|
+
name: string;
|
|
238
|
+
/** The environment this profile points at, e.g. `production`, `staging`. */
|
|
239
|
+
env: string;
|
|
240
|
+
baseURL: string;
|
|
241
|
+
/** A label for humans (a mailbox or username). Never a password. */
|
|
242
|
+
account?: string;
|
|
243
|
+
tags?: string[];
|
|
244
|
+
/** The page "Visit" opens after a login. Defaults to the site root. */
|
|
245
|
+
startPath?: string;
|
|
246
|
+
/** Page that redirects to /login when logged out. Defaults to /account/. */
|
|
247
|
+
sessionCheckPath?: string;
|
|
248
|
+
/** How that page shows a missing login. Omitted keys keep their default. */
|
|
249
|
+
sessionCheck?: Partial<SessionCheckInfo>;
|
|
250
|
+
/** Only for environments other than production. Defaults to false. */
|
|
251
|
+
email?: boolean;
|
|
252
|
+
}
|
|
253
|
+
/** Changes to one existing profile. The id itself cannot change: commands, session files and baselines use it. */
|
|
254
|
+
export interface EditProfileRequest {
|
|
255
|
+
name: string;
|
|
256
|
+
/** The environment this profile points at, e.g. `production`, `staging`. */
|
|
257
|
+
env: string;
|
|
258
|
+
baseURL: string;
|
|
259
|
+
/** A label for humans (a mailbox or username). Never a password. */
|
|
260
|
+
account?: string;
|
|
261
|
+
tags?: string[];
|
|
262
|
+
/** The page "Visit" opens after a login. Defaults to the site root. */
|
|
263
|
+
startPath?: string;
|
|
264
|
+
/** Page that redirects to /login when logged out. Defaults to /account/. */
|
|
265
|
+
sessionCheckPath?: string;
|
|
266
|
+
/** How that page shows a missing login. Omitted keys keep their default. */
|
|
267
|
+
sessionCheck?: Partial<SessionCheckInfo>;
|
|
268
|
+
/** Only for environments other than production. Defaults to false. */
|
|
269
|
+
email?: boolean;
|
|
270
|
+
}
|
|
271
|
+
export interface EditProfileResult {
|
|
272
|
+
profile: ProfileInfo;
|
|
273
|
+
/**
|
|
274
|
+
* True when the site address changed: the saved login belonged to the old site, was deleted, and the
|
|
275
|
+
* profile has to log in again.
|
|
276
|
+
*/
|
|
277
|
+
sessionCleared: boolean;
|
|
278
|
+
}
|
|
279
|
+
export interface ProfilesResponse {
|
|
280
|
+
profiles: ProfileInfo[];
|
|
281
|
+
/** Every environment used in profiles.yaml, `production` first. */
|
|
282
|
+
envs: string[];
|
|
283
|
+
verifying: boolean;
|
|
284
|
+
}
|
|
285
|
+
export interface CaseSummary {
|
|
286
|
+
/** Path below `cases/` without `.md`, e.g. `documents/landing-page`. */
|
|
287
|
+
id: string;
|
|
288
|
+
/** The folder of the case (`documents`), `null` for a case directly in `cases/`. */
|
|
289
|
+
group: string | null;
|
|
290
|
+
title: string | null;
|
|
291
|
+
login: string | null;
|
|
292
|
+
suites: string[];
|
|
293
|
+
/** Environments the case may write in; empty = read-only. */
|
|
294
|
+
writes: string[];
|
|
295
|
+
writesNote: string | null;
|
|
296
|
+
/** Environments the case may run in; null = all. */
|
|
297
|
+
envs: string[] | null;
|
|
298
|
+
/** Set when the front matter is invalid; the case is still listed so it can be fixed. */
|
|
299
|
+
problem: string | null;
|
|
300
|
+
hasSpec: boolean;
|
|
301
|
+
updatedAt: string;
|
|
302
|
+
}
|
|
303
|
+
export interface CaseDetail {
|
|
304
|
+
id: string;
|
|
305
|
+
content: string;
|
|
306
|
+
mtimeMs: number;
|
|
307
|
+
}
|
|
308
|
+
/** The spec of a case. */
|
|
309
|
+
export interface SpecDetail {
|
|
310
|
+
id: string;
|
|
311
|
+
/** Path relative to the project root, for display. */
|
|
312
|
+
file: string;
|
|
313
|
+
/** Absolute path, to paste into a terminal. */
|
|
314
|
+
path: string;
|
|
315
|
+
content: string;
|
|
316
|
+
mtimeMs: number;
|
|
317
|
+
}
|
|
318
|
+
export interface ReportInfo {
|
|
319
|
+
/** Folder name under reports/. */
|
|
320
|
+
id: string;
|
|
321
|
+
/** Environment the run targeted; null for runs from before environments existed (those were production). */
|
|
322
|
+
env: string | null;
|
|
323
|
+
createdAt: string;
|
|
324
|
+
isLatest: boolean;
|
|
325
|
+
/** Pass/fail counts; null for runs from before summaries existed. */
|
|
326
|
+
counts: Record<ResultStatus, number> | null;
|
|
327
|
+
}
|
|
328
|
+
export type { ParsedError, ResultStatus, RunSettings, RunSummary, TestRunResult } from '../../lib/run-summary.js';
|
|
329
|
+
export interface InfoResponse {
|
|
330
|
+
root: string;
|
|
331
|
+
envs: string[];
|
|
332
|
+
suites: string[];
|
|
333
|
+
caseIds: string[];
|
|
334
|
+
}
|
|
335
|
+
/** Everything that belongs to one case. Shown before a delete or rename, and returned by them. */
|
|
336
|
+
export interface CaseImpact {
|
|
337
|
+
id: string;
|
|
338
|
+
caseFile: string;
|
|
339
|
+
specFile: string | null;
|
|
340
|
+
snapshotDir: string | null;
|
|
341
|
+
snapshotCount: number;
|
|
342
|
+
}
|
|
343
|
+
/** One model an AI CLI can be run with, as the CLI itself names it. */
|
|
344
|
+
export interface AiModelOption {
|
|
345
|
+
/** Exactly what `--model` takes: `provider/model` or `provider/model#variant` (OpenCode), an alias or an id (Claude). */
|
|
346
|
+
id: string;
|
|
347
|
+
label: string;
|
|
348
|
+
/** Where the row comes from: a provider id, or `alias` / `settings` / `gateway` for a Claude CLI. */
|
|
349
|
+
group: string;
|
|
350
|
+
/** A second line: what the model is, or what an alias resolves to. */
|
|
351
|
+
detail?: string;
|
|
352
|
+
}
|
|
353
|
+
/** The models one AI CLI offers on this machine, read from the CLI itself or from its configuration. */
|
|
354
|
+
export interface AiModelsResponse {
|
|
355
|
+
tool: AiTool;
|
|
356
|
+
models: AiModelOption[];
|
|
357
|
+
/**
|
|
358
|
+
* What the CLI would run when asrun passes no `--model`; null when it cannot be known (OpenCode picks a built-in
|
|
359
|
+
* free model, which is not stored anywhere).
|
|
360
|
+
*/
|
|
361
|
+
clientDefault: string | null;
|
|
362
|
+
/**
|
|
363
|
+
* What to preselect while the user has not chosen: the CLI's default (Claude), or the model the CLI's own last
|
|
364
|
+
* session used (OpenCode). Null when neither is known.
|
|
365
|
+
*/
|
|
366
|
+
suggested: string | null;
|
|
367
|
+
/** Parts of the list that could not be read, in the words of the CLI: shown above the list, never fatal. */
|
|
368
|
+
notes: string[];
|
|
369
|
+
/** Set when there is no list at all (the CLI is not installed, or its answer could not be read). */
|
|
370
|
+
error: string | null;
|
|
371
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|