@qawolf/cli 1.38.0 → 1.40.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/dist/cli.js CHANGED
@@ -16749,6 +16749,11 @@ var makeRequestFileDownloadContract = (ids) => {
16749
16749
  readUrl: string2().describe('Fetch the file\'s bytes from here, for example `curl -o journeys.csv "<readUrl>"`. Write it to a file and read that file, rather than printing the contents, so a large file costs one line of output instead of all of it.')
16750
16750
  });
16751
16751
  return {
16752
+ annotationJustifications: {
16753
+ destructiveHint: "Does not change, overwrite or delete the stored file.",
16754
+ openWorldHint: "Access is limited to QA Wolf team storage; the caller cannot supply an arbitrary external destination.",
16755
+ readOnlyHint: "Returns an expiring read URL for an existing file in the authorized workspace storage."
16756
+ },
16752
16757
  annotations: {
16753
16758
  destructiveHint: false,
16754
16759
  openWorldHint: false,
@@ -16775,6 +16780,11 @@ var makeRequestFileUploadContract = (ids) => {
16775
16780
  uploadUrl: string2().describe(`Send the file's bytes here with a single HTTP PUT carrying the returned contentType, for example \`curl -H 'Content-Type: application/octet-stream' --upload-file "journeys.csv" "<uploadUrl>"\`. Upload the bytes from the shell rather than through this API, so a large file never has to be written out as text.`)
16776
16781
  });
16777
16782
  return {
16783
+ annotationJustifications: {
16784
+ destructiveHint: "Using the returned upload URL with an existing file name replaces that stored file.",
16785
+ openWorldHint: "The upload target is QA Wolf team storage, not an arbitrary external service or recipient.",
16786
+ readOnlyHint: "Returns an expiring upload URL that permits writing a file to the authorized workspace storage."
16787
+ },
16778
16788
  annotations: {
16779
16789
  destructiveHint: true,
16780
16790
  openWorldHint: false,
@@ -16890,6 +16900,11 @@ var makeCreateRunContract = (ids) => {
16890
16900
  tracking: _enum(["registered", "failed", "not-requested"]).describe('Whether the conversation named by aiTaskId or chatSessionId will receive run status updates: "registered" when it will, "failed" when the run was created but the registration failed, "not-requested" when neither id was given or the AI task has no conversation to notify.')
16891
16901
  }, { urlFieldDescription: "Absolute URL of the run page." });
16892
16902
  return {
16903
+ annotationJustifications: {
16904
+ destructiveHint: "Test code can overwrite or delete application data.",
16905
+ openWorldHint: "Test execution can submit forms or change the application under test and its connected services.",
16906
+ readOnlyHint: "Creates a run and enqueues execution of selected flows."
16907
+ },
16893
16908
  annotations: {
16894
16909
  destructiveHint: true,
16895
16910
  openWorldHint: true,
@@ -16956,6 +16971,11 @@ var makeListScreenshotComparisonsContract = (ids) => {
16956
16971
  truncated: boolean2().describe(`True when the selected flows have more than ${maxListedScreenshotAttempts} failed attempts. Only the ${maxListedScreenshotAttempts} most recent are listed, and the older attempts cannot be listed.`)
16957
16972
  });
16958
16973
  return {
16974
+ annotationJustifications: {
16975
+ destructiveHint: "Does not change baselines, results or the run.",
16976
+ openWorldHint: "Reads recorded QA Wolf screenshots without changing the application under test.",
16977
+ readOnlyHint: "Creates missing JPEG previews of the compared images and keeps them in team storage."
16978
+ },
16959
16979
  annotations: {
16960
16980
  destructiveHint: false,
16961
16981
  openWorldHint: false,
@@ -17029,6 +17049,7 @@ var browserActionSchema = discriminatedUnion("type", [
17029
17049
  type: literal("type")
17030
17050
  })
17031
17051
  ]);
17052
+
17032
17053
  // node_modules/@qawolf/api-contracts/dist/v1/runner/environment.js
17033
17054
  var reservedRunEnvironmentVariableName = "QAWOLF_TEAM_ID";
17034
17055
  function refusalForRunEnvironmentVariableName(name) {
@@ -17149,6 +17170,11 @@ var makeInspectOnRunnerContract = (ids) => {
17149
17170
  })
17150
17171
  ]);
17151
17172
  return {
17173
+ annotationJustifications: {
17174
+ destructiveHint: "Does not edit test code or application data, although the activity refresh can extend billed runtime.",
17175
+ openWorldHint: "Inspects the private runner session without submitting application actions.",
17176
+ readOnlyHint: "Retrieves browser inspection data and refreshes activity, which can cancel an inactivity shutdown."
17177
+ },
17152
17178
  annotations: {
17153
17179
  destructiveHint: false,
17154
17180
  openWorldHint: false,
@@ -17313,6 +17339,11 @@ var makeInspectMobileOnRunnerContract = (ids) => {
17313
17339
  ])
17314
17340
  ]);
17315
17341
  return {
17342
+ annotationJustifications: {
17343
+ destructiveHint: "Does not change application data, edit tests, or stop the runner.",
17344
+ openWorldHint: "Inspects the existing QA Wolf mobile runner without submitting application actions or contacting arbitrary services.",
17345
+ readOnlyHint: "Reads mobile session status, contexts, page source, or elements without refreshing the runner's inactivity timer."
17346
+ },
17316
17347
  annotations: {
17317
17348
  destructiveHint: false,
17318
17349
  openWorldHint: false,
@@ -17487,6 +17518,11 @@ var makePerformActionOnRunnerContract = (ids) => {
17487
17518
  failure
17488
17519
  ]);
17489
17520
  return {
17521
+ annotationJustifications: {
17522
+ destructiveHint: "Actions can submit irreversible transactions or delete existing data, so uncertain outcomes must be inspected before retrying.",
17523
+ openWorldHint: "Clicks, typing and navigation can submit external forms or change application state.",
17524
+ readOnlyHint: "Performs a browser or device action and can start a browser when needed."
17525
+ },
17490
17526
  annotations: {
17491
17527
  destructiveHint: true,
17492
17528
  openWorldHint: true,
@@ -17538,6 +17574,7 @@ var failedStepSchema = discriminatedUnion("failureReason", [
17538
17574
  failureReason: _enum([
17539
17575
  ...screenFailureReasons,
17540
17576
  "action-not-supported-on-mobile",
17577
+ "action-not-supported-on-browser",
17541
17578
  outOfTimeFailureReason
17542
17579
  ]),
17543
17580
  index: actionIndexSchema,
@@ -17552,6 +17589,7 @@ var sequenceFailureReasons = [
17552
17589
  "action-failed",
17553
17590
  ...screenFailureReasons,
17554
17591
  "action-not-supported-on-mobile",
17592
+ "action-not-supported-on-browser",
17555
17593
  outOfTimeFailureReason,
17556
17594
  unconfirmedFailureReason,
17557
17595
  runnerUnreachableFailureReason
@@ -17560,11 +17598,11 @@ var sequenceFailureReasons = [
17560
17598
  // node_modules/@qawolf/api-contracts/dist/v1/runner/performActions.js
17561
17599
  var actionsDescription = `The actions to perform, in order, each exactly what \`runner.performAction\` takes: coordinates are whole pixels on the runner's virtual desktop, in the same space as \`runner.takeScreenshot\`, and the shapes follow the computer-use vocabulary. At most ${maxActionsPerRequest} per request. For when the next several steps are already known — click a field, type into it, press Enter — and one look at the screen at the end is enough. They run back to back on the runner, with no pause between them: put in one request only steps whose targets are all on the screen you last saw and are not moved by the steps before them, and when a step changes the page — a submit, a navigation, opening a menu — make it the last one and read the frame.`;
17562
17600
  var screenshotModeDescription = "`final`, the default, answers with one screenshot in `imageJpegBase64`: the screen after the last action, or, after a stop, as it stands. `each` puts a screenshot after every action on its entry in `results`, for when each step must be checked; every frame is a full image, so keep such sequences short. `none` sends no screenshot.";
17563
- var stopOnFailureDescription = "`true`, the default, leaves the remaining actions unperformed once one fails. `false` carries on past an action that reached the runner and did not take effect (`action-failed`, `action-not-supported-on-mobile`), so use it only for actions that do not depend on each other: a `type` after a `click` that failed goes to whatever has focus. A runner that cannot be reached, a screen that cannot serve, or running out of time ends the sequence either way.";
17601
+ var stopOnFailureDescription = "`true`, the default, leaves the remaining actions unperformed once one fails. `false` carries on past an action that reached the runner and did not take effect (`action-failed`, `action-not-supported-on-mobile`, `action-not-supported-on-browser`), so use it only for actions that do not depend on each other: a `type` after a `click` that failed goes to whatever has focus. A runner that cannot be reached, a screen that cannot serve, or running out of time ends the sequence either way.";
17564
17602
  var finalImageDescription = "Present with `screenshotMode: final` when the runner still answered: the screen after the last action performed, or, after a stop, as it stands.";
17565
17603
  var makePerformActionsOnRunnerContract = (ids) => {
17566
17604
  const input = object({
17567
- actions: array(browserActionSchema).min(1).max(maxActionsPerRequest).describe(actionsDescription),
17605
+ actions: array(runnerActionSchema).min(1).max(maxActionsPerRequest).describe(actionsDescription),
17568
17606
  id: runnerIdSchema.describe("Id of the runner to act on."),
17569
17607
  screenshotMode: _enum(screenshotModes).optional().describe(screenshotModeDescription),
17570
17608
  stopOnFailure: boolean2().optional().describe(stopOnFailureDescription),
@@ -17589,12 +17627,17 @@ var makePerformActionsOnRunnerContract = (ids) => {
17589
17627
  })
17590
17628
  ]);
17591
17629
  return {
17630
+ annotationJustifications: {
17631
+ destructiveHint: "Actions can submit irreversible transactions or delete existing data, so uncertain outcomes must be inspected before retrying.",
17632
+ openWorldHint: "Clicks, typing and navigation can submit external forms or change application state.",
17633
+ readOnlyHint: "Performs a sequence of browser or device actions on a runner."
17634
+ },
17592
17635
  annotations: {
17593
17636
  destructiveHint: true,
17594
17637
  openWorldHint: true,
17595
17638
  readOnlyHint: false
17596
17639
  },
17597
- description: "Perform a sequence of raw browser actions on an interactive runner in one request, one after another, and answer with what happened to each. A `success` means every action took effect. A `failure` names the first action that did not (`failedIndex`, `failureReason`), the last one that did (`lastCompletedIndex`), and whether actions were left unperformed (`stoppedEarly`); `results` has one entry per action reached, in order. Read `effect` on an entry before sending its action again: `performed` means the runner did it; `not-performed` means the runner answered that it did not take effect, though a `navigate` that timed out may still be loading; `unknown` means the runner stopped answering with the action in flight (`runner-unreachable`), or its screen went quiet mid-action (`action-unconfirmed`), and it may have taken effect — a click that submitted a form, say — so take a screenshot instead of repeating it. A sequence gets about a minute to start its actions; an action it did not reach in time is `out-of-time`, nothing after it was attempted, and the rest can go in a new request. Everything `runner.performAction` says about a runner that has never run anything, a mobile runner, and the `screen-needs-a-run`, `screen-not-ready`, `runner-has-no-screen` and `action-not-supported-on-mobile` reasons holds for each action here.",
17640
+ description: "Perform a sequence of raw actions on an interactive runner in one request, one after another, and answer with what happened to each. A `success` means every action took effect. A `failure` names the first action that did not (`failedIndex`, `failureReason`), the last one that did (`lastCompletedIndex`), and whether actions were left unperformed (`stoppedEarly`); `results` has one entry per action reached, in order. Read `effect` on an entry before sending its action again: `performed` means the runner did it; `not-performed` means the runner answered that it did not take effect, though a `navigate` that timed out may still be loading; `unknown` means the runner stopped answering with the action in flight (`runner-unreachable`), or its screen went quiet mid-action (`action-unconfirmed`), and it may have taken effect — a click that submitted a form, say — so take a screenshot instead of repeating it. A sequence gets about a minute to start its actions; an action it did not reach in time is `out-of-time`, nothing after it was attempted, and the rest can go in a new request. Everything `runner.performAction` says about a runner that has never run anything, a mobile runner, and the `screen-needs-a-run`, `screen-not-ready`, `runner-has-no-screen`, `action-not-supported-on-mobile` and `action-not-supported-on-browser` reasons holds for each action here.",
17598
17641
  input,
17599
17642
  kind: "write",
17600
17643
  name: "runner.performActions",
@@ -17662,6 +17705,11 @@ var makeRecordOnRunnerContract = (ids) => {
17662
17705
  url: url()
17663
17706
  });
17664
17707
  return {
17708
+ annotationJustifications: {
17709
+ destructiveHint: "Starts or stops a video recording without changing the application under test.",
17710
+ openWorldHint: "Controls recording on a QA Wolf runner without starting external work.",
17711
+ readOnlyHint: "Starts or stops video recording and changes automatic capture settings."
17712
+ },
17665
17713
  annotations: {
17666
17714
  destructiveHint: false,
17667
17715
  openWorldHint: false,
@@ -17689,6 +17737,11 @@ var makeRunnerRecordingsContract = (ids) => {
17689
17737
  }))
17690
17738
  });
17691
17739
  return {
17740
+ annotationJustifications: {
17741
+ destructiveHint: "Does not delete or change any recording.",
17742
+ openWorldHint: "Reads QA Wolf workspace storage without changing the application under test.",
17743
+ readOnlyHint: "Lists or retrieves a runner's finalized recordings."
17744
+ },
17692
17745
  annotations: {
17693
17746
  destructiveHint: false,
17694
17747
  openWorldHint: false,
@@ -17918,6 +17971,11 @@ var makeCreateTriggerContract = (ids) => {
17918
17971
  ]).superRefine(checkTriggerConfiguration);
17919
17972
  const output = object({ trigger: makeTriggerResourceSchema(ids) });
17920
17973
  return {
17974
+ annotationJustifications: {
17975
+ destructiveHint: "Enables automatic execution of test code that may overwrite or delete application data.",
17976
+ openWorldHint: "Triggered runs can change the application under test and update connected integrations.",
17977
+ readOnlyHint: "Creates an active schedule or deployment trigger that can start test runs."
17978
+ },
17921
17979
  annotations: {
17922
17980
  destructiveHint: true,
17923
17981
  openWorldHint: true,
@@ -17941,6 +17999,11 @@ var makeFindTriggersContract = (ids) => {
17941
17999
  triggers: array(makeTriggerResourceSchema(ids)).describe("The team's triggers, newest first.")
17942
18000
  });
17943
18001
  return {
18002
+ annotationJustifications: {
18003
+ destructiveHint: "Does not modify or delete triggers or their runs.",
18004
+ openWorldHint: "Reads private QA Wolf trigger records without changing external systems.",
18005
+ readOnlyHint: "Lists trigger configuration in the authorized workspace without changing it."
18006
+ },
17944
18007
  annotations: {
17945
18008
  destructiveHint: false,
17946
18009
  openWorldHint: false,
@@ -17959,6 +18022,11 @@ var makeGetTriggerContract = (ids) => {
17959
18022
  const input = object({ triggerId: ids.trigger });
17960
18023
  const output = object({ trigger: makeTriggerResourceSchema(ids) });
17961
18024
  return {
18025
+ annotationJustifications: {
18026
+ destructiveHint: "Does not modify or delete the trigger or its runs.",
18027
+ openWorldHint: "Reads a private QA Wolf trigger without changing external systems.",
18028
+ readOnlyHint: "Retrieves one authorized trigger and its configuration without changing it."
18029
+ },
17962
18030
  annotations: {
17963
18031
  destructiveHint: false,
17964
18032
  openWorldHint: false,
@@ -17977,6 +18045,11 @@ var makePauseTriggerContract = (ids) => {
17977
18045
  const input = object({ triggerId: ids.trigger });
17978
18046
  const output = object({ trigger: makeTriggerResourceSchema(ids) });
17979
18047
  return {
18048
+ annotationJustifications: {
18049
+ destructiveHint: "Disables future automatic execution for the selected trigger until it is resumed.",
18050
+ openWorldHint: "Changes private QA Wolf scheduling state without starting external work.",
18051
+ readOnlyHint: "Pauses an existing trigger so it stops starting new work."
18052
+ },
17980
18053
  annotations: {
17981
18054
  destructiveHint: true,
17982
18055
  openWorldHint: false,
@@ -17993,6 +18066,11 @@ var makeResumeTriggerContract = (ids) => {
17993
18066
  const input = object({ triggerId: ids.trigger });
17994
18067
  const output = object({ trigger: makeTriggerResourceSchema(ids) });
17995
18068
  return {
18069
+ annotationJustifications: {
18070
+ destructiveHint: "Enables automatic test execution that may overwrite or delete application data.",
18071
+ openWorldHint: "Resumed execution can change the application under test and update connected integrations.",
18072
+ readOnlyHint: "Reactivates a paused trigger so future matching schedules or deployments can start runs."
18073
+ },
17996
18074
  annotations: {
17997
18075
  destructiveHint: true,
17998
18076
  openWorldHint: true,
@@ -18011,6 +18089,11 @@ var makeDeleteTriggerContract = (ids) => {
18011
18089
  triggerId: ids.trigger.describe("The id of the deleted trigger.")
18012
18090
  });
18013
18091
  return {
18092
+ annotationJustifications: {
18093
+ destructiveHint: "Permanently deletes the selected trigger configuration.",
18094
+ openWorldHint: "Removes private QA Wolf trigger configuration without starting external work.",
18095
+ readOnlyHint: "Permanently removes a trigger while preserving runs it already created."
18096
+ },
18014
18097
  annotations: {
18015
18098
  destructiveHint: true,
18016
18099
  openWorldHint: false,
@@ -18047,6 +18130,11 @@ var makeUpdateTriggerContract = (ids) => {
18047
18130
  ]).superRefine(checkTriggerConfiguration);
18048
18131
  const output = object({ trigger: makeTriggerResourceSchema(ids) });
18049
18132
  return {
18133
+ annotationJustifications: {
18134
+ destructiveHint: "Overwrites trigger configuration and can enable tests that change or delete application data.",
18135
+ openWorldHint: "The updated trigger can execute tests against external applications and update connected integrations.",
18136
+ readOnlyHint: "Replaces an existing trigger configuration and can change future automatic runs."
18137
+ },
18050
18138
  annotations: {
18051
18139
  destructiveHint: true,
18052
18140
  openWorldHint: true,
@@ -18088,6 +18176,11 @@ var makeAgentGetContract = (ids) => {
18088
18176
  urlFieldDescription: "Absolute URL of the live session in the QA Wolf app."
18089
18177
  });
18090
18178
  return {
18179
+ annotationJustifications: {
18180
+ destructiveHint: "Does not cancel work or edit tests and messages.",
18181
+ openWorldHint: "Reads a QA Wolf session without posting to external services.",
18182
+ readOnlyHint: "Retrieves session status, replies and the live link without sending work."
18183
+ },
18091
18184
  annotations: {
18092
18185
  destructiveHint: false,
18093
18186
  openWorldHint: false,
@@ -18119,6 +18212,11 @@ var makeAgentSendContract = (ids) => {
18119
18212
  urlFieldDescription: "Absolute URL of the live session in the QA Wolf app."
18120
18213
  });
18121
18214
  return {
18215
+ annotationJustifications: {
18216
+ destructiveHint: "Delegated execution can overwrite test code or change and delete application data.",
18217
+ openWorldHint: "Delegated work can submit application actions and publish code to connected repositories.",
18218
+ readOnlyHint: "Creates or continues AI work that can implement, run and publish tests."
18219
+ },
18122
18220
  annotations: {
18123
18221
  destructiveHint: true,
18124
18222
  openWorldHint: true,
@@ -18157,6 +18255,11 @@ var makeFindCodeHostIntegrationsContract = (ids) => {
18157
18255
  settingsUrl: url().describe("Absolute URL of the workspace's integrations settings page.")
18158
18256
  });
18159
18257
  return {
18258
+ annotationJustifications: {
18259
+ destructiveHint: "Does not disconnect integrations or change repository access.",
18260
+ openWorldHint: "Reads QA Wolf integration records without contacting or changing GitHub or GitLab.",
18261
+ readOnlyHint: "Lists the workspace's stored code host integrations and settings link without modifying them."
18262
+ },
18160
18263
  annotations: {
18161
18264
  destructiveHint: false,
18162
18265
  openWorldHint: false,
@@ -18189,6 +18292,11 @@ var makeListCodeHostRepositoriesContract = (ids) => {
18189
18292
  repositories: array(makeCodeHostRepositoryResourceSchema()).describe("The repositories the workspace's code host integrations cover, alphabetical by full name.")
18190
18293
  });
18191
18294
  return {
18295
+ annotationJustifications: {
18296
+ destructiveHint: "Does not edit repositories, integration settings, or access permissions.",
18297
+ openWorldHint: "Reads repositories already recorded for the workspace's integrations, without accessing arbitrary external repositories.",
18298
+ readOnlyHint: "Lists repository metadata from the workspace's last code host sync without triggering a new sync."
18299
+ },
18192
18300
  annotations: {
18193
18301
  destructiveHint: false,
18194
18302
  openWorldHint: false,
@@ -18232,6 +18340,11 @@ var makeFindDeploymentsContract = (ids) => {
18232
18340
  nextCursor: nextCursorSchema
18233
18341
  });
18234
18342
  return {
18343
+ annotationJustifications: {
18344
+ destructiveHint: "Does not modify deployments, environments, triggers, or runs.",
18345
+ openWorldHint: "Reads QA Wolf deployment records without contacting deployment providers or starting tests.",
18346
+ readOnlyHint: "Lists stored workspace deployments without reporting or changing their status."
18347
+ },
18235
18348
  annotations: {
18236
18349
  destructiveHint: false,
18237
18350
  openWorldHint: false,
@@ -18306,6 +18419,11 @@ var makeListDeploymentTriggerEvaluationsContract = (ids) => {
18306
18419
  object({ state: literal("not-evaluated") }).describe("Triggers were never evaluated for this deployment. Only a deployment's first success report evaluates triggers, so a deployment that never reported success carries no verdicts.")
18307
18420
  ]);
18308
18421
  return {
18422
+ annotationJustifications: {
18423
+ destructiveHint: "Does not alter recorded verdicts, trigger configuration, or existing runs.",
18424
+ openWorldHint: "Reads stored QA Wolf verdicts without starting tests or updating external services.",
18425
+ readOnlyHint: "Retrieves the recorded trigger verdicts for one deployment without evaluating triggers again."
18426
+ },
18309
18427
  annotations: {
18310
18428
  destructiveHint: false,
18311
18429
  openWorldHint: false,
@@ -18377,6 +18495,11 @@ var makeReportDeploymentStatusContract = (ids) => {
18377
18495
  }, { urlFieldDescription: "Absolute URL of the environment's runs page." });
18378
18496
  const output = object({ deployment });
18379
18497
  return {
18498
+ annotationJustifications: {
18499
+ destructiveHint: "Can replace deployment details and variable overrides, and trigger tests that overwrite or delete application data.",
18500
+ openWorldHint: "Successful deployments can start tests against external applications and update connected integrations.",
18501
+ readOnlyHint: "Creates or updates a deployment, can create an environment, and evaluates triggers on its first success report."
18502
+ },
18380
18503
  annotations: {
18381
18504
  destructiveHint: true,
18382
18505
  openWorldHint: true,
@@ -18413,6 +18536,11 @@ var makeFindEmailsContract = (ids) => {
18413
18536
  nextCursor: nextCursorSchema
18414
18537
  });
18415
18538
  return {
18539
+ annotationJustifications: {
18540
+ destructiveHint: "Does not send, edit, or delete email messages.",
18541
+ openWorldHint: "Reads the workspace's QA Wolf inbox records without contacting recipients or arbitrary mailboxes.",
18542
+ readOnlyHint: "Searches stored workspace emails and returns matching message summaries."
18543
+ },
18416
18544
  annotations: {
18417
18545
  destructiveHint: false,
18418
18546
  openWorldHint: false,
@@ -18434,6 +18562,11 @@ var makeGetEmailContract = (ids) => {
18434
18562
  });
18435
18563
  const output = makeEmailResourceSchema();
18436
18564
  return {
18565
+ annotationJustifications: {
18566
+ destructiveHint: "Does not alter or delete the message or its attachments.",
18567
+ openWorldHint: "Reads a stored workspace message without forwarding it or contacting external recipients.",
18568
+ readOnlyHint: "Retrieves the content of an existing workspace email."
18569
+ },
18437
18570
  annotations: {
18438
18571
  destructiveHint: false,
18439
18572
  openWorldHint: false,
@@ -18462,6 +18595,11 @@ var makeGetEmailAttachmentContract = (ids) => {
18462
18595
  type: string2().optional().describe("The MIME type.")
18463
18596
  }, { urlFieldDescription: emailUrlFieldDescription });
18464
18597
  return {
18598
+ annotationJustifications: {
18599
+ destructiveHint: "Does not modify or delete the email or attachment.",
18600
+ openWorldHint: "Access is limited to attachments of authorized workspace emails, not arbitrary external files.",
18601
+ readOnlyHint: "Reads one attachment from an existing workspace email and returns its content."
18602
+ },
18465
18603
  annotations: {
18466
18604
  destructiveHint: false,
18467
18605
  openWorldHint: false,
@@ -18486,6 +18624,11 @@ var makeListEmailAddressesContract = (ids) => {
18486
18624
  nextCursor: nextCursorSchema
18487
18625
  });
18488
18626
  return {
18627
+ annotationJustifications: {
18628
+ destructiveHint: "Does not register, replace, or remove inbox addresses.",
18629
+ openWorldHint: "Reads QA Wolf inbox configuration without contacting external mail services or recipients.",
18630
+ readOnlyHint: "Lists the workspace's registered inbox addresses without changing them."
18631
+ },
18489
18632
  annotations: {
18490
18633
  destructiveHint: false,
18491
18634
  openWorldHint: false,
@@ -18513,6 +18656,11 @@ var makeRegisterEmailAddressContract = (ids) => {
18513
18656
  });
18514
18657
  const output = makeEmailAddressResourceSchema();
18515
18658
  return {
18659
+ annotationJustifications: {
18660
+ destructiveHint: "Adds an address without replacing or deleting existing inbox addresses or emails.",
18661
+ openWorldHint: "Adds a QA Wolf workspace inbox address without sending messages to external recipients.",
18662
+ readOnlyHint: "Registers an additional inbox address for the workspace."
18663
+ },
18516
18664
  annotations: {
18517
18665
  destructiveHint: false,
18518
18666
  openWorldHint: false,
@@ -18551,6 +18699,11 @@ var makeSendEmailContract = (ids) => {
18551
18699
  urlFieldDescription: emailUrlFieldDescription
18552
18700
  });
18553
18701
  return {
18702
+ annotationJustifications: {
18703
+ destructiveHint: "Sending an email is irreversible; recipients may act on it, and the tool cannot recall it.",
18704
+ openWorldHint: "Delivers message content and attachments to caller-selected email recipients outside QA Wolf.",
18705
+ readOnlyHint: "Sends an email from a workspace inbox and stores the sent message."
18706
+ },
18554
18707
  annotations: {
18555
18708
  destructiveHint: true,
18556
18709
  openWorldHint: true,
@@ -18596,6 +18749,11 @@ var makeCreateEnvironmentContract = (ids) => {
18596
18749
  });
18597
18750
  const output = makeEnvironmentResourceSchema(ids);
18598
18751
  return {
18752
+ annotationJustifications: {
18753
+ destructiveHint: "Adds a new environment and branch rather than deleting or overwriting an existing one.",
18754
+ openWorldHint: "Creates a branch in the connected Git provider rather than changing only QA Wolf records.",
18755
+ readOnlyHint: "Creates a QA Wolf environment and its remote flow-code branch."
18756
+ },
18599
18757
  annotations: {
18600
18758
  destructiveHint: false,
18601
18759
  openWorldHint: true,
@@ -18620,6 +18778,11 @@ var makeDeleteEnvironmentVariableContract = (ids) => {
18620
18778
  name: string2().describe("The canonicalized name that was removed. Returned even when the environment had no such variable.")
18621
18779
  });
18622
18780
  return {
18781
+ annotationJustifications: {
18782
+ destructiveHint: "Deletes an existing variable value, while an already-absent variable is left absent.",
18783
+ openWorldHint: "Changes private test configuration without submitting it to an external application.",
18784
+ readOnlyHint: "Removes a named environment variable from QA Wolf storage."
18785
+ },
18623
18786
  annotations: {
18624
18787
  destructiveHint: true,
18625
18788
  openWorldHint: false,
@@ -18647,6 +18810,11 @@ var makeFindEnvironmentsContract = (ids) => {
18647
18810
  nextCursor: nextCursorSchema
18648
18811
  });
18649
18812
  return {
18813
+ annotationJustifications: {
18814
+ destructiveHint: "Does not edit environments, variables or branches.",
18815
+ openWorldHint: "Reads private QA Wolf environment metadata without publishing changes.",
18816
+ readOnlyHint: "Lists workspace environments and their configuration summaries."
18817
+ },
18650
18818
  annotations: {
18651
18819
  destructiveHint: false,
18652
18820
  openWorldHint: false,
@@ -18675,6 +18843,11 @@ var makeGetEnvironmentVariableContract = (ids) => {
18675
18843
  })).describe("The found variables, sorted by name.")
18676
18844
  });
18677
18845
  return {
18846
+ annotationJustifications: {
18847
+ destructiveHint: "Does not replace or remove variable values.",
18848
+ openWorldHint: "Reads private stored configuration without submitting it to an external application.",
18849
+ readOnlyHint: "Retrieves decrypted values of named environment variables and reports missing names."
18850
+ },
18678
18851
  annotations: {
18679
18852
  destructiveHint: false,
18680
18853
  openWorldHint: false,
@@ -18696,6 +18869,11 @@ var makeGetEnvironmentContract = (ids) => {
18696
18869
  });
18697
18870
  const output = makeEnvironmentResourceSchema(ids);
18698
18871
  return {
18872
+ annotationJustifications: {
18873
+ destructiveHint: "Does not alter environment settings or concurrency limits.",
18874
+ openWorldHint: "Reads QA Wolf environment state without updating the connected Git provider.",
18875
+ readOnlyHint: "Retrieves an environment's configuration, run health and code-reconciliation state."
18876
+ },
18699
18877
  annotations: {
18700
18878
  destructiveHint: false,
18701
18879
  openWorldHint: false,
@@ -18717,6 +18895,11 @@ var makeListEnvironmentVariableNamesContract = (ids) => {
18717
18895
  variableNames: array(string2()).describe("Names of the environment's variables, sorted alphabetically. Values are never returned.")
18718
18896
  });
18719
18897
  return {
18898
+ annotationJustifications: {
18899
+ destructiveHint: "Does not alter variables or expose their stored values.",
18900
+ openWorldHint: "Reads private configuration names without changing external systems.",
18901
+ readOnlyHint: "Lists available environment variable names without retrieving their values."
18902
+ },
18720
18903
  annotations: {
18721
18904
  destructiveHint: false,
18722
18905
  openWorldHint: false,
@@ -18740,6 +18923,11 @@ var makeSetEnvironmentVariableContract = (ids) => {
18740
18923
  name: string2().describe("The stored variable name after whitespace is replaced with underscores and letters are uppercased.")
18741
18924
  });
18742
18925
  return {
18926
+ annotationJustifications: {
18927
+ destructiveHint: "Can overwrite an existing variable value, while an unchanged value is skipped.",
18928
+ openWorldHint: "Changes stored QA Wolf configuration rather than submitting forms or publishing content.",
18929
+ readOnlyHint: "Creates or replaces a named environment variable without returning its value."
18930
+ },
18743
18931
  annotations: {
18744
18932
  destructiveHint: true,
18745
18933
  openWorldHint: false,
@@ -18767,6 +18955,11 @@ var makeUpdateEnvironmentContract = (ids) => {
18767
18955
  });
18768
18956
  const output = makeEnvironmentResourceSchema(ids);
18769
18957
  return {
18958
+ annotationJustifications: {
18959
+ destructiveHint: "Can overwrite existing environment settings and affect future run capacity.",
18960
+ openWorldHint: "Changes private environment settings without publishing code or contacting application users.",
18961
+ readOnlyHint: "Updates an environment's name or run concurrency limit."
18962
+ },
18770
18963
  annotations: {
18771
18964
  destructiveHint: true,
18772
18965
  openWorldHint: false,
@@ -18794,6 +18987,11 @@ var makeCreateTagContract = (ids) => {
18794
18987
  });
18795
18988
  const output = makeTagResourceSchema();
18796
18989
  return {
18990
+ annotationJustifications: {
18991
+ destructiveHint: "Creates a tag without deleting existing tags or flow associations.",
18992
+ openWorldHint: "Adds private QA Wolf metadata without publishing to an external application.",
18993
+ readOnlyHint: "Creates a workspace tag for grouping and selecting flows."
18994
+ },
18797
18995
  annotations: {
18798
18996
  destructiveHint: false,
18799
18997
  openWorldHint: false,
@@ -18827,6 +19025,11 @@ var makeAddTagToFlowsContract = (ids) => {
18827
19025
  })
18828
19026
  });
18829
19027
  return {
19028
+ annotationJustifications: {
19029
+ destructiveHint: "Adds associations without removing existing tags.",
19030
+ openWorldHint: "Changes private flow-tag associations without running flows or publishing code.",
19031
+ readOnlyHint: "Adds an existing tag to selected flows and skips flows already carrying it."
19032
+ },
18830
19033
  annotations: {
18831
19034
  destructiveHint: false,
18832
19035
  openWorldHint: false,
@@ -18881,6 +19084,11 @@ var makeListFlowsContract = (ids) => {
18881
19084
  }))
18882
19085
  });
18883
19086
  return {
19087
+ annotationJustifications: {
19088
+ destructiveHint: "Does not change flow code, readiness or tags.",
19089
+ openWorldHint: "Reads flow records without pushing code or executing application actions.",
19090
+ readOnlyHint: "Lists flows at an environment's reconciled commit or on a selected AI task branch."
19091
+ },
18884
19092
  annotations: {
18885
19093
  destructiveHint: false,
18886
19094
  openWorldHint: false,
@@ -18907,6 +19115,11 @@ var makeRemoveTagFromFlowsContract = (ids) => {
18907
19115
  })
18908
19116
  });
18909
19117
  return {
19118
+ annotationJustifications: {
19119
+ destructiveHint: "Deletes existing tag associations and skips flows that do not carry the tag.",
19120
+ openWorldHint: "Changes private flow-tag associations without updating external services.",
19121
+ readOnlyHint: "Removes a selected tag from specified flows."
19122
+ },
18910
19123
  annotations: {
18911
19124
  destructiveHint: true,
18912
19125
  openWorldHint: false,
@@ -18936,6 +19149,11 @@ var makeUpdateFlowContract = (ids) => {
18936
19149
  })
18937
19150
  });
18938
19151
  return {
19152
+ annotationJustifications: {
19153
+ destructiveHint: "Overwrites readiness and can deactivate a flow that was active.",
19154
+ openWorldHint: "Changes private readiness state without itself executing the flow or publishing code.",
19155
+ readOnlyHint: "Changes a flow between draft and active readiness."
19156
+ },
18939
19157
  annotations: {
18940
19158
  destructiveHint: true,
18941
19159
  openWorldHint: false,
@@ -18958,6 +19176,7 @@ var issuePrioritySchema = _enum([
18958
19176
  "urgent"
18959
19177
  ]);
18960
19178
  var issueStatusSchema = _enum([
19179
+ "backlog",
18961
19180
  "pending",
18962
19181
  "inProgress",
18963
19182
  "paused",
@@ -18966,7 +19185,12 @@ var issueStatusSchema = _enum([
18966
19185
  "archived"
18967
19186
  ]);
18968
19187
  var publicIssueTypeSchema = _enum(["bug", "coverageRequest", "maintenance"]);
18969
- var openIssueStatuses = ["pending", "inProgress", "paused"];
19188
+ var openIssueStatuses = [
19189
+ "backlog",
19190
+ "pending",
19191
+ "inProgress",
19192
+ "paused"
19193
+ ];
18970
19194
  var makeIssueResourceSchema = (ids) => resource({
18971
19195
  coveredFlowIds: array(ids.flow).describe("The flows a coverage request covers. Always empty on bug and maintenance reports, whose flows are listed under reproductions."),
18972
19196
  createdAt: exports_coerce.date().describe("When the issue was created."),
@@ -18988,7 +19212,7 @@ var makeIssueResourceSchema = (ids) => resource({
18988
19212
  var makeFindIssuesContract = (ids) => {
18989
19213
  const input = object({
18990
19214
  ...makePaginationInputFields({ defaultLimit: 20, maxLimit: 100 }),
18991
- statuses: array(issueStatusSchema).min(1).default([...openIssueStatuses]).describe("Issue statuses to include. Defaults to pending, in-progress, and paused issues."),
19215
+ statuses: array(issueStatusSchema).min(1).default([...openIssueStatuses]).describe("Issue statuses to include. Defaults to backlog, pending, in-progress, and paused issues."),
18992
19216
  type: publicIssueTypeSchema.describe("The issue type to include: bug report, maintenance report, or coverage request."),
18993
19217
  workspaceId: ids.workspace.optional().describe("The workspace whose issues to list. Required when authenticating with an organization or user API key.")
18994
19218
  });
@@ -18997,6 +19221,11 @@ var makeFindIssuesContract = (ids) => {
18997
19221
  nextCursor: nextCursorSchema
18998
19222
  });
18999
19223
  return {
19224
+ annotationJustifications: {
19225
+ destructiveHint: "Does not edit issue details, statuses or coverage associations.",
19226
+ openWorldHint: "Reads QA Wolf issue records without publishing updates to linked trackers.",
19227
+ readOnlyHint: "Lists workspace bug reports, maintenance reports or coverage requests."
19228
+ },
19000
19229
  annotations: {
19001
19230
  destructiveHint: false,
19002
19231
  openWorldHint: false,
@@ -19017,6 +19246,11 @@ var makeGetIssueContract = (ids) => {
19017
19246
  issue: makeIssueResourceSchema(ids)
19018
19247
  });
19019
19248
  return {
19249
+ annotationJustifications: {
19250
+ destructiveHint: "Does not modify or remove the issue.",
19251
+ openWorldHint: "Reads the QA Wolf issue without changing linked external systems.",
19252
+ readOnlyHint: "Retrieves one issue and its associated details."
19253
+ },
19020
19254
  annotations: {
19021
19255
  destructiveHint: false,
19022
19256
  openWorldHint: false,
@@ -19086,9 +19320,18 @@ var makeGetInvestigationContract = (ids) => {
19086
19320
  findings: array(makeInvestigationFindingSchema(ids)),
19087
19321
  runId: ids.run,
19088
19322
  sessionId: ids.chatSession.optional().describe("The investigation session. Absent when the run has none."),
19089
- status: _enum(["investigating", "finished", "not-investigated"]).describe('Whether the investigation session is still working. "not-investigated" when the run has no investigation.')
19323
+ status: _enum(["investigating", "finished", "not-investigated"]).describe('Whether the lead or any child investigator is still working. "not-investigated" when the run has no investigation.'),
19324
+ subSessions: array(object({
19325
+ sessionId: ids.chatSession,
19326
+ status: agentSessionStatusSchema
19327
+ })).optional().describe("All child investigators, including sessions not attached to findings yet. Send instructions to each sessionId with agent.send.")
19090
19328
  }, { urlFieldDescription: "The run in the QA Wolf app." });
19091
19329
  return {
19330
+ annotationJustifications: {
19331
+ destructiveHint: "Does not answer, dispute or change the investigation.",
19332
+ openWorldHint: "Reads recorded QA Wolf findings without changing the application under test.",
19333
+ readOnlyHint: "Retrieves an investigation's findings, open questions and pending answers."
19334
+ },
19092
19335
  annotations: {
19093
19336
  destructiveHint: false,
19094
19337
  openWorldHint: false,
@@ -19117,6 +19360,11 @@ var makeAddFlowsToIssueContract = (ids) => {
19117
19360
  issue: makeIssueResourceSchema(ids)
19118
19361
  });
19119
19362
  return {
19363
+ annotationJustifications: {
19364
+ destructiveHint: "Adds coverage associations without removing existing ones.",
19365
+ openWorldHint: "Changes private coverage associations without publishing an external issue update.",
19366
+ readOnlyHint: "Adds existing flows to a coverage request's covered-flow set."
19367
+ },
19120
19368
  annotations: {
19121
19369
  destructiveHint: false,
19122
19370
  openWorldHint: false,
@@ -19160,6 +19408,11 @@ var makeCreateIssueContract = (ids) => {
19160
19408
  issue: makeIssueResourceSchema(ids)
19161
19409
  });
19162
19410
  return {
19411
+ annotationJustifications: {
19412
+ destructiveHint: "Configured notifications can send irreversible messages even though the QA Wolf issue itself is new.",
19413
+ openWorldHint: "Issue creation can send messages or synchronize records through connected communication and issue-tracking services.",
19414
+ readOnlyHint: "Creates a bug or coverage request and can start configured issue notifications."
19415
+ },
19163
19416
  annotations: {
19164
19417
  destructiveHint: true,
19165
19418
  openWorldHint: true,
@@ -19184,6 +19437,11 @@ var makeRemoveFlowsFromIssueContract = (ids) => {
19184
19437
  issue: makeIssueResourceSchema(ids)
19185
19438
  });
19186
19439
  return {
19440
+ annotationJustifications: {
19441
+ destructiveHint: "Deletes existing coverage associations while leaving already-unassociated flows unchanged.",
19442
+ openWorldHint: "Changes private coverage associations without publishing to an external tracker.",
19443
+ readOnlyHint: "Removes selected flows from a coverage request's covered-flow set."
19444
+ },
19187
19445
  annotations: {
19188
19446
  destructiveHint: true,
19189
19447
  openWorldHint: true,
@@ -19214,6 +19472,11 @@ var makeUpdateIssueContract = (ids) => {
19214
19472
  issue: makeIssueResourceSchema(ids)
19215
19473
  });
19216
19474
  return {
19475
+ annotationJustifications: {
19476
+ destructiveHint: "Can overwrite existing issue fields or send irreversible integration messages.",
19477
+ openWorldHint: "Issue changes can update connected issue trackers and communication services.",
19478
+ readOnlyHint: "Updates issue details, priority or status and can trigger integration updates."
19479
+ },
19217
19480
  annotations: {
19218
19481
  destructiveHint: true,
19219
19482
  openWorldHint: true,
@@ -19290,6 +19553,11 @@ var makeFindLegacyTriggersContract = (ids) => {
19290
19553
  legacyTriggers: array(makeLegacyTriggerSchema(ids)).describe("The workspace's legacy triggers, by name.")
19291
19554
  });
19292
19555
  return {
19556
+ annotationJustifications: {
19557
+ destructiveHint: "Does not pause, resume or change any trigger.",
19558
+ openWorldHint: "Reads private QA Wolf trigger configuration without starting external work.",
19559
+ readOnlyHint: "Lists a workspace's legacy per-environment triggers."
19560
+ },
19293
19561
  annotations: {
19294
19562
  destructiveHint: false,
19295
19563
  openWorldHint: false,
@@ -19314,6 +19582,11 @@ var makeLifecycleShape = (ids) => ({
19314
19582
  var makePauseLegacyTriggerContract = (ids) => {
19315
19583
  const { input, output } = makeLifecycleShape(ids);
19316
19584
  return {
19585
+ annotationJustifications: {
19586
+ destructiveHint: "Disables future automatic execution for the legacy trigger and its pull request copies until it is resumed.",
19587
+ openWorldHint: "Changes private QA Wolf scheduling state without starting external work.",
19588
+ readOnlyHint: "Pauses an existing legacy trigger so it stops starting new work."
19589
+ },
19317
19590
  annotations: {
19318
19591
  destructiveHint: true,
19319
19592
  openWorldHint: false,
@@ -19329,6 +19602,11 @@ var makePauseLegacyTriggerContract = (ids) => {
19329
19602
  var makeResumeLegacyTriggerContract = (ids) => {
19330
19603
  const { input, output } = makeLifecycleShape(ids);
19331
19604
  return {
19605
+ annotationJustifications: {
19606
+ destructiveHint: "Enables automatic test execution that can overwrite or delete application data.",
19607
+ openWorldHint: "Resumed execution can change the application under test and update connected integrations.",
19608
+ readOnlyHint: "Reactivates a legacy trigger and its pull request copies so future schedules or deployments can start runs."
19609
+ },
19332
19610
  annotations: {
19333
19611
  destructiveHint: true,
19334
19612
  openWorldHint: true,
@@ -19379,6 +19657,11 @@ var makeAcceptScreenshotBaselineContract = (ids) => {
19379
19657
  })
19380
19658
  ]);
19381
19659
  return {
19660
+ annotationJustifications: {
19661
+ destructiveHint: "Replaces the workspace's screenshot baseline, which changes what every later run compares with.",
19662
+ openWorldHint: "Changes private QA Wolf baseline storage without starting external work.",
19663
+ readOnlyHint: "Makes the new screenshot of a failed comparison the baseline."
19664
+ },
19382
19665
  annotations: {
19383
19666
  destructiveHint: true,
19384
19667
  openWorldHint: false,
@@ -19403,6 +19686,11 @@ var makeDiagnoseRunContract = (ids) => {
19403
19686
  issue: makeIssueResourceSchema(ids)
19404
19687
  });
19405
19688
  return {
19689
+ annotationJustifications: {
19690
+ destructiveHint: "Can replace a flow's existing diagnosis association and change externally reported run information.",
19691
+ openWorldHint: "Recorded diagnosis changes can update run-related messages in configured external integrations.",
19692
+ readOnlyHint: "Records failed flows as reproductions of a selected bug or maintenance issue."
19693
+ },
19406
19694
  annotations: {
19407
19695
  destructiveHint: true,
19408
19696
  openWorldHint: true,
@@ -19433,6 +19721,11 @@ var makeFindRunsContract = (ids) => {
19433
19721
  }, { urlFieldDescription: "Absolute URL of the run page." })).describe("The environment's runs, newest first. Per-flow results are available via run.get.")
19434
19722
  });
19435
19723
  return {
19724
+ annotationJustifications: {
19725
+ destructiveHint: "Does not stop runs, overwrite results or change diagnoses.",
19726
+ openWorldHint: "Reads private run records without executing application actions.",
19727
+ readOnlyHint: "Lists recent environment runs without starting or retrying them."
19728
+ },
19436
19729
  annotations: {
19437
19730
  destructiveHint: false,
19438
19731
  openWorldHint: false,
@@ -19494,6 +19787,7 @@ var makeGetRunContract = (ids) => {
19494
19787
  }),
19495
19788
  object({
19496
19789
  attemptId: ids.runAttempt.optional(),
19790
+ canceledReason: string2().min(1).optional().describe("Why the attempt was canceled. userCanceled means a person stopped it; do not immediately retry that activity."),
19497
19791
  completedAt: exports_iso.datetime().optional(),
19498
19792
  kind: automatedKind,
19499
19793
  startedAt: exports_iso.datetime().optional(),
@@ -19530,6 +19824,11 @@ var makeGetRunContract = (ids) => {
19530
19824
  runId: ids.run.describe("The run this response describes. Treat it as canonical: it can differ from the id you asked for. A deploy notification returns a run id before the run exists, and if a second notification for the same commit is folded into an earlier run, that id resolves to the earlier run instead.")
19531
19825
  }, { urlFieldDescription: "Absolute URL of the run page." });
19532
19826
  return {
19827
+ annotationJustifications: {
19828
+ destructiveHint: "Does not retry, cancel or alter the run.",
19829
+ openWorldHint: "Reads recorded QA Wolf results without changing the application under test.",
19830
+ readOnlyHint: "Retrieves a run's status, per-flow results and result links."
19831
+ },
19533
19832
  annotations: {
19534
19833
  destructiveHint: false,
19535
19834
  openWorldHint: false,
@@ -19607,6 +19906,11 @@ var makeGetRunAttemptArtifactsContract = (ids) => {
19607
19906
  })
19608
19907
  ]);
19609
19908
  return {
19909
+ annotationJustifications: {
19910
+ destructiveHint: "Does not retry, cancel or alter the run attempt.",
19911
+ openWorldHint: "Reads recorded QA Wolf artifacts without changing the application under test.",
19912
+ readOnlyHint: "Retrieves a finished attempt's metadata and signed artifact URLs."
19913
+ },
19610
19914
  annotations: {
19611
19915
  destructiveHint: false,
19612
19916
  openWorldHint: false,
@@ -19631,6 +19935,11 @@ var makeOptOutOfInvestigationRunContract = (ids) => {
19631
19935
  runId: ids.run
19632
19936
  }, { urlFieldDescription: "Absolute URL of the run page." });
19633
19937
  return {
19938
+ annotationJustifications: {
19939
+ destructiveHint: "Ends the investigation of the selected failures and changes externally reported run information.",
19940
+ openWorldHint: "Recorded opt-outs can update run-related messages in configured external integrations.",
19941
+ readOnlyHint: "Records failed flows as needing no bug or maintenance report."
19942
+ },
19634
19943
  annotations: {
19635
19944
  destructiveHint: true,
19636
19945
  openWorldHint: true,
@@ -19655,6 +19964,11 @@ var makeReattemptRunContract = (ids) => {
19655
19964
  runId: ids.run
19656
19965
  }, { urlFieldDescription: "Absolute URL of the run page." });
19657
19966
  return {
19967
+ annotationJustifications: {
19968
+ destructiveHint: "Repeated execution can overwrite or delete application data and creates additional billed work.",
19969
+ openWorldHint: "New attempts execute against the application under test and can affect connected services.",
19970
+ readOnlyHint: "Enqueues new attempts for eligible failed or canceled flows using the latest code."
19971
+ },
19658
19972
  annotations: {
19659
19973
  destructiveHint: true,
19660
19974
  openWorldHint: true,
@@ -19694,6 +20008,11 @@ var makeRestoreScreenshotBaselineContract = (ids) => {
19694
20008
  })
19695
20009
  ]);
19696
20010
  return {
20011
+ annotationJustifications: {
20012
+ destructiveHint: "Replaces the current screenshot baseline with the one it replaced, which changes what every later run compares with.",
20013
+ openWorldHint: "Changes private QA Wolf baseline storage without starting external work.",
20014
+ readOnlyHint: "Puts back the screenshot baseline that an accepted comparison replaced."
20015
+ },
19697
20016
  annotations: {
19698
20017
  destructiveHint: true,
19699
20018
  openWorldHint: false,
@@ -19715,6 +20034,11 @@ var makeStopRunContract = (ids) => {
19715
20034
  status: _enum(["accepted", "already-finished", "execution-not-found"]).describe('"accepted" means stopping was requested; poll run.get for the final result. "already-finished" means the run has already finished and its result is unchanged. "execution-not-found" means the run exists but no execution was found to stop; it may still be starting or may no longer be available. No stop request was accepted in that case.')
19716
20035
  }, { urlFieldDescription: "Absolute URL of the run page." });
19717
20036
  return {
20037
+ annotationJustifications: {
20038
+ destructiveHint: "Interrupts unfinished work, while finished results remain intact and repeated stop requests are supported.",
20039
+ openWorldHint: "Run status changes can update messages or commit statuses in configured external integrations.",
20040
+ readOnlyHint: "Requests asynchronous cancellation of a run, queued flows and automatic retries."
20041
+ },
19718
20042
  annotations: {
19719
20043
  destructiveHint: true,
19720
20044
  openWorldHint: true,
@@ -19753,6 +20077,11 @@ var makeEvaluateSnippetOnRunnerContract = (ids) => {
19753
20077
  ])
19754
20078
  ]);
19755
20079
  return {
20080
+ annotationJustifications: {
20081
+ destructiveHint: "Code can overwrite or delete runner or application data, and a timeout does not prove execution stopped.",
20082
+ openWorldHint: "Supplied code can submit application actions or contact and change external services.",
20083
+ readOnlyHint: "Executes supplied code against the interactive runner's current session."
20084
+ },
19756
20085
  annotations: {
19757
20086
  destructiveHint: true,
19758
20087
  openWorldHint: true,
@@ -19786,6 +20115,11 @@ var makeGetRunnerContract = (ids) => {
19786
20115
  })
19787
20116
  ]);
19788
20117
  return {
20118
+ annotationJustifications: {
20119
+ destructiveHint: "Does not stop the runner or change its execution.",
20120
+ openWorldHint: "Reads private runner placement state without acting on external applications.",
20121
+ readOnlyHint: "Checks whether a runner exists without starting it or refreshing its inactivity timer."
20122
+ },
19789
20123
  annotations: {
19790
20124
  destructiveHint: false,
19791
20125
  openWorldHint: false,
@@ -19829,6 +20163,11 @@ var makeHighlightSelectorOnRunnerContract = (ids) => {
19829
20163
  })
19830
20164
  ]);
19831
20165
  return {
20166
+ annotationJustifications: {
20167
+ destructiveHint: "Only replaces or clears the inspection overlay rather than deleting application data.",
20168
+ openWorldHint: "Draws a private inspection overlay without submitting an application form.",
20169
+ readOnlyHint: "Changes the visual selector highlight on the runner's live page."
20170
+ },
19832
20171
  annotations: {
19833
20172
  destructiveHint: false,
19834
20173
  openWorldHint: false,
@@ -19862,6 +20201,11 @@ var makeImportPackageOnRunnerContract = (ids) => {
19862
20201
  })
19863
20202
  ]);
19864
20203
  return {
20204
+ annotationJustifications: {
20205
+ destructiveHint: "Can replace existing dependencies or execute commands that overwrite or delete runner or application data.",
20206
+ openWorldHint: "Installation can contact external services and execute commands that change third-party systems.",
20207
+ readOnlyHint: "Installs caller-selected dependencies into a live runner and can execute commands with the runner's access."
20208
+ },
19865
20209
  annotations: {
19866
20210
  destructiveHint: true,
19867
20211
  openWorldHint: true,
@@ -19891,9 +20235,14 @@ var makeLaunchRunnerContract = (ids) => {
19891
20235
  outcome: literal("success")
19892
20236
  });
19893
20237
  return {
20238
+ annotationJustifications: {
20239
+ destructiveHint: "Starts billable resource consumption that cannot be undone by later termination.",
20240
+ openWorldHint: "Allocates a private QA Wolf runner without itself navigating to an application or submitting forms.",
20241
+ readOnlyHint: "Starts an interactive runner and allocates billed resources unless the requested runner is already running."
20242
+ },
19894
20243
  annotations: {
19895
20244
  destructiveHint: true,
19896
- openWorldHint: false,
20245
+ openWorldHint: true,
19897
20246
  readOnlyHint: false
19898
20247
  },
19899
20248
  description: "Launch an interactive runner on the caller's team under an id the caller chooses. Send `initialUrl` so its browser comes up on the page the work starts from, which saves a separate navigation and the browser's cold start. Launching the same id again returns the runner already running rather than starting a second one, and the same id with a different runnerName is refused. A runner is not permanent: it terminates on its own after a period of inactivity, and launching the same id after that starts and bills a new runner, so read `alreadyRunning` to tell which happened. A success means the runner is answering: the call waits for the pod it started to come up, so the very next call to the runner reaches it. If the pod does not come up in time the call fails instead, leaving the runner running, and launching the same id again attaches to it.",
@@ -19914,6 +20263,11 @@ var makeTerminateRunnerContract = (ids) => {
19914
20263
  wasRunning: boolean2().describe("False when no running runner had this id: it was already terminated, was never launched, or terminated on its own after inactivity. These are not distinguished, because the run system keeps no record of a runner once it is gone. Not an error, and a retry needs no special handling.")
19915
20264
  });
19916
20265
  return {
20266
+ annotationJustifications: {
20267
+ destructiveHint: "Terminates running work and discards the live session, while an absent runner is left absent.",
20268
+ openWorldHint: "Stops a private QA Wolf runner without itself publishing to third-party services.",
20269
+ readOnlyHint: "Ends a selected interactive runner and the resources hosting it."
20270
+ },
19917
20271
  annotations: {
19918
20272
  destructiveHint: true,
19919
20273
  openWorldHint: false,
@@ -19937,6 +20291,11 @@ var makeListRunnersContract = (ids) => {
19937
20291
  runners: array(makeRunnerSchema()).describe("The runners running right now, in no particular order. Each entry carries the same fields a launch reports, so any of them can be addressed like a runner you launched yourself.")
19938
20292
  });
19939
20293
  return {
20294
+ annotationJustifications: {
20295
+ destructiveHint: "Does not interrupt or terminate any listed runner.",
20296
+ openWorldHint: "Reads private runner state without acting on third-party applications.",
20297
+ readOnlyHint: "Lists running workspace runners without starting them or refreshing their inactivity timers."
20298
+ },
19940
20299
  annotations: {
19941
20300
  destructiveHint: false,
19942
20301
  openWorldHint: false,
@@ -19971,6 +20330,11 @@ var makePromoteSnapshotOnRunnerContract = (ids) => {
19971
20330
  })
19972
20331
  ]);
19973
20332
  return {
20333
+ annotationJustifications: {
20334
+ destructiveHint: "Overwrites an existing baseline, while a missing source snapshot leaves it unchanged.",
20335
+ openWorldHint: "Updates a stored test baseline rather than publishing to an external application.",
20336
+ readOnlyHint: "Replaces a named image-diff baseline with a screenshot produced by the runner."
20337
+ },
19974
20338
  annotations: {
19975
20339
  destructiveHint: true,
19976
20340
  openWorldHint: false,
@@ -19996,6 +20360,11 @@ var makeReadRunnerJournalContract = (ids) => {
19996
20360
  makeRunnerFailureSchema([runnerUnreachableFailureReason])
19997
20361
  ]);
19998
20362
  return {
20363
+ annotationJustifications: {
20364
+ destructiveHint: "Does not erase journal entries or change run results, although the activity refresh can extend billed runtime.",
20365
+ openWorldHint: "Reads private runner history without submitting actions to external applications.",
20366
+ readOnlyHint: "Retrieves journal entries and refreshes activity, which can cancel an inactivity shutdown."
20367
+ },
19999
20368
  annotations: {
20000
20369
  destructiveHint: false,
20001
20370
  openWorldHint: false,
@@ -20074,6 +20443,11 @@ var makeRunFlowOnRunnerContract = (ids) => {
20074
20443
  failure
20075
20444
  ]);
20076
20445
  return {
20446
+ annotationJustifications: {
20447
+ destructiveHint: "Test code can overwrite or delete application data, and uncertain acceptance must be checked before resubmission.",
20448
+ openWorldHint: "Executed tests can submit forms and change the application under test or its connected services.",
20449
+ readOnlyHint: "Submits test files for full-flow or selected-line execution on an interactive runner."
20450
+ },
20077
20451
  annotations: {
20078
20452
  destructiveHint: true,
20079
20453
  openWorldHint: true,
@@ -20101,6 +20475,11 @@ var makeStopRunOnRunnerContract = (ids) => {
20101
20475
  makeRunnerFailureSchema([runnerUnreachableFailureReason])
20102
20476
  ]);
20103
20477
  return {
20478
+ annotationJustifications: {
20479
+ destructiveHint: "Stops unfinished work where it is, while an already-idle runner is left idle.",
20480
+ openWorldHint: "Changes private execution state without itself posting external messages.",
20481
+ readOnlyHint: "Interrupts current execution while leaving the runner available."
20482
+ },
20104
20483
  annotations: {
20105
20484
  destructiveHint: true,
20106
20485
  openWorldHint: false,
@@ -20131,6 +20510,11 @@ var makeTakeScreenshotOnRunnerContract = (ids) => {
20131
20510
  ])
20132
20511
  ]);
20133
20512
  return {
20513
+ annotationJustifications: {
20514
+ destructiveHint: "Does not change application data or stop execution, although the activity refresh can extend billed runtime.",
20515
+ openWorldHint: "Reads the private runner display without submitting an external application action.",
20516
+ readOnlyHint: "Captures the runner screen and refreshes activity, which can cancel an inactivity shutdown."
20517
+ },
20134
20518
  annotations: {
20135
20519
  destructiveHint: false,
20136
20520
  openWorldHint: false,
@@ -20182,6 +20566,11 @@ var makeListSkillsContract = () => {
20182
20566
  skills: array(skillSummary).describe("Every skill the server serves, in catalog order.")
20183
20567
  });
20184
20568
  return {
20569
+ annotationJustifications: {
20570
+ destructiveHint: "Does not change any skill or workspace data.",
20571
+ openWorldHint: "Reads the QA Wolf skill catalog without calling external services.",
20572
+ readOnlyHint: "Lists the QA Wolf skills with their names and descriptions."
20573
+ },
20185
20574
  annotations: readOnlyAnnotations,
20186
20575
  description: "List the QA Wolf skills, each with its name and the description that says when to use it. A skill is the instructions a coding agent follows for one kind of QA Wolf work, such as onboarding an application, creating a flow, repairing a failing flow, or setting up triggers. Call this before any QA Wolf work, pick the skill whose description matches the request, and read it with skill.get.",
20187
20576
  input,
@@ -20200,6 +20589,11 @@ var makeGetSkillContract = () => {
20200
20589
  })).describe("The files the skill links to under its directory.")
20201
20590
  });
20202
20591
  return {
20592
+ annotationJustifications: {
20593
+ destructiveHint: "Does not change any skill or workspace data.",
20594
+ openWorldHint: "Reads the QA Wolf skill catalog without calling external services.",
20595
+ readOnlyHint: "Retrieves one QA Wolf skill's instructions and reference files."
20596
+ },
20203
20597
  annotations: readOnlyAnnotations,
20204
20598
  description: "Read a QA Wolf skill: the instructions a coding agent follows for one kind of QA Wolf work. The reply carries the skill's SKILL.md and every file under its references directory, so nothing else has to be fetched for it. Read the skill before starting the work it covers and follow it. A link in the reply of the form ../<skill>/SKILL.md names another skill, which this call reads by that name.",
20205
20599
  input,
@@ -20228,6 +20622,11 @@ var makeListTagsContract = (ids) => {
20228
20622
  })).describe("The team's tags, alphabetical by name.")
20229
20623
  });
20230
20624
  return {
20625
+ annotationJustifications: {
20626
+ destructiveHint: "Does not create, rename or remove tags.",
20627
+ openWorldHint: "Reads private tag metadata without changing connected services.",
20628
+ readOnlyHint: "Lists existing workspace tags by name."
20629
+ },
20231
20630
  annotations: {
20232
20631
  destructiveHint: false,
20233
20632
  openWorldHint: false,
@@ -22820,7 +23219,7 @@ function startUpdateCheck(deps) {
22820
23219
  // package.json
22821
23220
  var package_default = {
22822
23221
  name: "@qawolf/cli",
22823
- version: "1.38.0",
23222
+ version: "1.40.0",
22824
23223
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
22825
23224
  keywords: [
22826
23225
  "automation",
@@ -22891,7 +23290,7 @@ var package_default = {
22891
23290
  "@clack/prompts": "1.5.1",
22892
23291
  "@napi-rs/keyring": "1.3.0",
22893
23292
  "@oxc-node/core": "0.1.0",
22894
- "@qawolf/api-contracts": "0.77.0",
23293
+ "@qawolf/api-contracts": "0.81.0",
22895
23294
  "@qawolf/emails": "1.1.1",
22896
23295
  "@qawolf/flow-targets": "1.0.0",
22897
23296
  "@qawolf/flows": "0.1.4",
@@ -35386,6 +35785,106 @@ function registerFlowsCommand(program, signals, deps = { withResolvedEnv }) {
35386
35785
  registerFlowsPullCommand(flows, signals);
35387
35786
  }
35388
35787
 
35788
+ // src/commands/help/commandTree.ts
35789
+ function listVisibleSubcommands(command) {
35790
+ return new Help2().visibleCommands(command).filter((child) => child.name() !== "help");
35791
+ }
35792
+ function commandPath(command) {
35793
+ return command.parent === null ? [command.name()] : [...commandPath(command.parent), command.name()];
35794
+ }
35795
+ function findSubcommand(root, path) {
35796
+ return path.reduce((command, segment) => command?.commands.find((child) => child.name() === segment || child.aliases().includes(segment)), root);
35797
+ }
35798
+
35799
+ // src/commands/help/referenceGuide.ts
35800
+ var guides = new WeakMap;
35801
+ function declareReferenceGuide(command, markdown) {
35802
+ guides.set(command, markdown);
35803
+ return command;
35804
+ }
35805
+ function shiftHeadings(markdown, levels) {
35806
+ const lines = markdown.trim().split(`
35807
+ `);
35808
+ const shifted = lines.reduce((state, line) => {
35809
+ if (line.startsWith("```")) {
35810
+ return { inFence: !state.inFence, lines: [...state.lines, line] };
35811
+ }
35812
+ const isHeading = !state.inFence && /^#{1,6} /.test(line);
35813
+ const shiftedLine = isHeading ? `${"#".repeat(levels)}${line}` : line;
35814
+ return { inFence: state.inFence, lines: [...state.lines, shiftedLine] };
35815
+ }, { inFence: false, lines: [] });
35816
+ return shifted.lines.join(`
35817
+ `);
35818
+ }
35819
+ function renderReferenceGuide(command, depth) {
35820
+ const guide = guides.get(command);
35821
+ return guide === undefined ? undefined : shiftHeadings(guide, depth - 1);
35822
+ }
35823
+
35824
+ // src/commands/help/reference.ts
35825
+ var referenceHelpWidth = 100;
35826
+ var deepestHeadingLevel = 6;
35827
+ function formatCommandHelp(command) {
35828
+ const originalOutput = command.configureOutput();
35829
+ const chunks = [];
35830
+ command.configureOutput({
35831
+ getOutHasColors: () => false,
35832
+ getOutHelpWidth: () => referenceHelpWidth,
35833
+ writeOut: (text) => chunks.push(text)
35834
+ });
35835
+ try {
35836
+ command.outputHelp();
35837
+ } finally {
35838
+ command.configureOutput(originalOutput);
35839
+ }
35840
+ return chunks.join("").trimEnd();
35841
+ }
35842
+ function renderSections(command, path, depth) {
35843
+ const heading = `${"#".repeat(Math.min(depth, deepestHeadingLevel))} ${path.join(" ")}`;
35844
+ const help = ["```text", formatCommandHelp(command), "```"].join(`
35845
+ `);
35846
+ const guide = renderReferenceGuide(command, depth);
35847
+ const section = [heading, ...guide === undefined ? [] : [guide], help].join(`
35848
+
35849
+ `);
35850
+ return [
35851
+ section,
35852
+ ...listVisibleSubcommands(command).flatMap((child) => renderSections(child, [...path, child.name()], depth + 1))
35853
+ ];
35854
+ }
35855
+ function renderHelpReference(command, path) {
35856
+ return `${renderSections(command, path, 1).join(`
35857
+
35858
+ `)}
35859
+ `;
35860
+ }
35861
+
35862
+ // src/commands/help/index.ts
35863
+ var referenceExamples = `
35864
+ Examples:
35865
+ $ qawolf help ref runner
35866
+ $ qawolf help ref runner inspect
35867
+ $ qawolf help reference`;
35868
+ function resolveCommand(program, path) {
35869
+ const command = findSubcommand(program, path);
35870
+ if (command === undefined) {
35871
+ program.error(`error: unknown command '${path.join(" ")}'`, {
35872
+ code: "commander.unknownCommand",
35873
+ exitCode: exitCodes.invalidArgs
35874
+ });
35875
+ }
35876
+ return command;
35877
+ }
35878
+ function registerHelpCommand(program) {
35879
+ const help = program.helpCommand(false).command("help").description("Display help for a command").usage("[command...]").argument("[command...]", "Command to show help for").action((path) => {
35880
+ resolveCommand(program, path).outputHelp();
35881
+ });
35882
+ help.command("ref").alias("reference").description("Print the full help of a command and of every command under it, as Markdown. Omit the command for the whole CLI").argument("[command...]", "Command whose subtree to print").addHelpText("after", referenceExamples).action((path) => {
35883
+ const command = resolveCommand(program, path);
35884
+ program.configureOutput().writeOut?.(renderHelpReference(command, commandPath(command)));
35885
+ });
35886
+ }
35887
+
35389
35888
  // src/domains/init/init.ts
35390
35889
  import { dirname as dirname14, join as join50, relative as relative9 } from "node:path";
35391
35890
 
@@ -36239,6 +36738,11 @@ function describeReason(step) {
36239
36738
  exitCode: exitCodes.invalidArgs,
36240
36739
  why: "has no touchscreen equivalent on a mobile runner."
36241
36740
  };
36741
+ case "action-not-supported-on-browser":
36742
+ return {
36743
+ exitCode: exitCodes.invalidArgs,
36744
+ why: "is a touchscreen action, which only a mobile runner performs."
36745
+ };
36242
36746
  case "screen-needs-a-run":
36243
36747
  return {
36244
36748
  exitCode: exitCodes.invalidArgs,
@@ -36476,19 +36980,13 @@ function buildRunnerAction(type, flags) {
36476
36980
  };
36477
36981
  return parseRunnerAction(candidate);
36478
36982
  }
36479
- function parseWith(schema, candidate) {
36480
- const parsed = schema.safeParse(candidate);
36983
+ function parseRunnerAction(candidate) {
36984
+ const parsed = runnerActionSchema.safeParse(candidate);
36481
36985
  if (!parsed.success) {
36482
36986
  return { error: prettifyError(parsed.error), ok: false };
36483
36987
  }
36484
36988
  return { action: parsed.data, ok: true };
36485
36989
  }
36486
- function parseRunnerAction(candidate) {
36487
- return parseWith(runnerActionSchema, candidate);
36488
- }
36489
- function parseBrowserAction(candidate) {
36490
- return parseWith(browserActionSchema, candidate);
36491
- }
36492
36990
 
36493
36991
  // src/domains/interactiveRunner/readActions.ts
36494
36992
  var stdinArgument = "-";
@@ -36508,7 +37006,7 @@ async function readActions(argument, deps) {
36508
37006
  ok: false
36509
37007
  };
36510
37008
  }
36511
- const built = parsed.items.map(parseBrowserAction);
37009
+ const built = parsed.items.map(parseRunnerAction);
36512
37010
  const firstRefused = built.findIndex((action) => !action.ok);
36513
37011
  const refused = built[firstRefused];
36514
37012
  if (refused !== undefined && !refused.ok) {
@@ -37024,15 +37522,351 @@ function runnerDeps(ctx) {
37024
37522
  });
37025
37523
  }
37026
37524
 
37525
+ // src/commands/runner/guides/act.txt
37526
+ var act_default = `# Seeing and acting
37527
+
37528
+ There is no hosted vision loop: you look with screenshot, decide with your own model, and act. act performs exactly one action per call, in the computer-use tool vocabulary a vision model already emits. The names and the field names are unchanged from that vocabulary on purpose, so you can forward a tool call rather than translate it with \`act -\`.
37529
+
37530
+ Every action in a see-and-act loop is followed by a look at the result, so ask for it in the same call with --screenshot. Prefer it over act and then screenshot: each step is one call instead of two, with no delay to guess at between them. The screen the runner answers with is taken once it has changed from just before the action or half a second has passed, whichever comes first. An action that reached the screen and did not take effect answers with one too, so an exit 1 still leaves you a picture of why the click missed. A 4 whose message starts with "Performed" means the action happened and only the picture is missing: take a screenshot, never send the action again to get it. As with \`screenshot --out -\`, a terminal on stdout is refused.
37531
+
37532
+ Coordinates are pixels on the same screenshot you just read. The runner serves one see-or-act request at a time, so never have two in flight; when the next few steps are already known, send them as one \`runner actions\` sequence instead. Bounds are checked before anything is sent, so an over-long --text or an out-of-range coordinate comes back immediately naming the limit.
37533
+
37534
+ On a 4, the answer may have been lost with the action in flight: take a screenshot before repeating a click, and repeat only what the screen says did not happen.
37535
+
37536
+ # Mobile
37537
+
37538
+ A mobile runner has a touchscreen, not a mouse, so it has actions of its own:
37539
+ - tap touches a point (--x, --y) or the element --selector names. Check the selector first with \`inspect elements --selector\`, and pass --strategy ios-predicate or shadow when it is not XPath.
37540
+ - swipe --from x,y --to x,y moves in a straight line between two points. --duration-ms sets how long it takes, up to 10000: a slow swipe scrolls, a fast one flings.
37541
+ - fill --selector ... --text ... replaces the value of that field, and --text "" clears it. To add to what a field already holds, tap it and then type.
37542
+ - type types into whatever the last tap focused, the same as on a browser.
37543
+
37544
+ On mobile, send tap and swipe, never click or drag. The other browser actions, double_click, scroll, move, keypress and navigate, answer action-not-supported-on-mobile rather than doing something approximate. navigate is the one to watch for, since it works on a browser runner without a run first but has no meaning on mobile at all. A browser runner answers tap, swipe and fill with action-not-supported-on-browser.
37545
+
37546
+ # Before the first run
37547
+
37548
+ A runner has no screen until its first run, so act exits 2 until then, except navigate, which exits 1 (action-failed): it skips the screen but still needs the runner to have run something. With no runner named at all, act launches one and says so on stderr.
37549
+ `;
37550
+
37551
+ // src/commands/runner/guides/actions.txt
37552
+ var actions_default = `# Batching
37553
+
37554
+ Use a sequence for the steps you already know: click the field, type into it, press Enter. One round trip instead of three, with no delay to guess at between them. On a mobile runner the steps are tap, swipe, fill and type, the same as act, and a step the runner does not take fails with the same reason act would give, action-not-supported-on-mobile or action-not-supported-on-browser.
37555
+
37556
+ Batch only steps whose targets are all on the screen you last saw and are not moved by the steps before them, and make the step that changes the page, a submit, a navigation, opening a menu, the last one. Then read the frame and decide the next batch from it.
37557
+
37558
+ # Screenshots
37559
+
37560
+ --screenshot after.jpg writes the screen after the last action, as \`act --screenshot\` does, and - puts those bytes on stdout with the confirmation on stderr. Stdout then carries the image and nothing else, so - trades the per-action results for the frame: give --screenshot a file path whenever you need to read \`effect\` per action. --screenshot-mode each --screenshot step.jpg writes one frame per action instead, step-0.jpg, step-1.jpg and so on; each is a full image, so keep those sequences short.
37561
+
37562
+ # Reading the answer
37563
+
37564
+ The answer holds one entry per action reached, and the field to read on each is \`effect\`:
37565
+ - performed means the runner did it.
37566
+ - not-performed means the runner answered that it did not take effect.
37567
+ - unknown means the runner stopped answering with the action in flight, or its screen went quiet mid-action, so it may have taken effect. Take a screenshot before repeating anything from that step on, and never send it again blind.
37568
+
37569
+ The sequence stops at the first action that fails. --continue-on-failure carries on past one that reached the runner and did not take effect, which is only safe for actions that do not depend on each other: a type after a failed click goes to whatever has focus. A runner that cannot be reached, a screen that cannot serve, or running out of time ends the sequence either way, and the message names every action that did not succeed.
37570
+
37571
+ # Exit codes
37572
+
37573
+ A sequence exits by the worst thing that happened to any one of its actions: an effect nobody can confirm (4) first, then running out of time (6), then a refusal (2), then an action that plainly did not happen (1). Whatever the code, the message names the last action that took effect, and only what follows it goes in a new request. A sequence also exits 2 before it sends anything for an argument that is not a JSON array, an array of more than ten actions, and --screenshot flags that contradict each other.
37574
+ `;
37575
+
37576
+ // src/commands/runner/guides/events.txt
37577
+ var events_default = `# Streams
37578
+
37579
+ Everything observable is an append-only stream on the pod, read by cursor or tail rather than subscribed to, so attaching late still gets you the history that is still there. QA Wolf writes recorder, console, run-events, run-logs and run-status; a stream nobody has written reads as empty rather than as an error, and a stream this CLI version does not know about is still readable by name. One payload per line, so shell tools compose:
37580
+
37581
+ $ qawolf runner events console --tail 20 | jq -r '.message'
37582
+ $ qawolf runner events run-logs --run <runId> --follow > run.log
37583
+
37584
+ --tail N takes the newest N, --since <sequence> reads everything after a cursor, and --run <id> narrows the run-scoped streams.
37585
+
37586
+ History is not unbounded: a size cap drops the oldest entries on a long-lived runner, and a --tail N read can stop early and hand back fewer than N even when more matched. Both are warned about on stderr, dropped entries only once a read holds a cursor, a stopped-early read with a pointer at --since. Watch stderr, treat a short answer as "at least this" rather than "all there was", and read what you care about as you go rather than at the end.
37587
+
37588
+ # The recorder
37589
+
37590
+ \`events recorder\` is what has no equivalent in a screenshot. As you drive the browser, the runner records each interaction and publishes \`locator\` (the real Playwright locator it resolved), \`alternates\` (the others that matched the same element) and \`code\` (the generated Playwright call), alongside \`type\`, \`sourceUrl\` and \`timestamp\`:
37591
+
37592
+ $ qawolf runner events recorder --tail 5 | jq -r '.code // .type'
37593
+ $ qawolf runner events recorder --tail 5 | jq -r '[.locator] + (.alternates // []) | @tsv'
37594
+
37595
+ \`code\` is absent on events with no call of their own, such as a navigation, which is why the first line falls back to \`type\`. Use these to turn a session you drove by pixel coordinates into durable selectors, and to check that a click landed on the element you meant rather than near it. The stream is empty until the runner's first run gives it a browser context, so an early empty answer means "not yet", not "broken". Do not add --json here: it wraps each line in an envelope and these field paths stop matching.
37596
+
37597
+ # Following
37598
+
37599
+ --follow polls and prints as entries arrive. It is tail -f with a bound: it ends only at its --timeout (an hour by default, exit 6), because reading keeps the runner alive and billing. It does not end when a run settles, so it cannot be used to wait for a run: use \`runner run --follow\`, or poll run-status. Redirect it to a file and stop it yourself, or use repeated --since reads when you need the command to end sooner.
37600
+
37601
+ Where --follow wins is the cursor. The pod reports how far a read scanned rather than how far it matched, and --follow carries that number, so a filtered read that matched nothing still moves forward. A caller paging by hand cannot see it, because the CLI does not print it, and the best available substitute is the highest \`sequence\` you actually saw. So a narrow --run filter over a busy stream stalls: with nothing matching, there is no new \`sequence\` to move on to, and you re-read the same window until something matches.
37602
+
37603
+ events never launches a runner.
37604
+ `;
37605
+
37606
+ // src/commands/runner/guides/exec.txt
37607
+ var exec_default = `# Getting a value back
37608
+
37609
+ exec does not return what the snippet evaluated to, only whether it ran. To get a value back, print it and read the \`console\` stream with \`qawolf runner events console\`. Print it behind a marker you chose, and match on that rather than taking the newest line: the page logs to the same stream, so anything it prints after your snippet would be what --tail 1 hands back. Entries carry \`source\`, which is \`serverConsole\` for your snippet and \`browserConsole\` for the page, so filtering on both is what pins the value down:
37610
+
37611
+ $ echo 'console.log("qw-title:", await page.title())' | qawolf runner exec -
37612
+ $ qawolf runner events console --tail 20 | jq -r 'select(.source == "serverConsole" and (.message | contains("qw-title:"))) | .message'
37613
+
37614
+ To read a top-level variable of the running workflow, \`qawolf runner inspect variable\` is one call and the value, where exec is two calls and a marker.
37615
+
37616
+ # Scope
37617
+
37618
+ The snippet imports nothing of yours by default. Pass --file <path> to evaluate it in that file's scope, which also ships the directory's other files, so the snippet can use your own page objects and helpers.
37619
+
37620
+ # Failures
37621
+
37622
+ exec exits 2 until the runner's first run, since there is no page to evaluate against. On a 4 the message says the snippet could not be evaluated, but a lost answer looks the same from outside, so treat a 4 from a snippet that changes something as "may have run" rather than "did not run", and check its effect before running it again. With no runner named at all, exec launches one and says so on stderr.
37623
+ `;
37624
+
37625
+ // src/commands/runner/guides/formatGuide.ts
37626
+ var guideWidth = 80;
37627
+ function wrapWords(text, firstPrefix, restPrefix) {
37628
+ const words = text.split(/\s+/).filter((word) => word.length > 0);
37629
+ return words.reduce((lines, word) => {
37630
+ const last = lines.at(-1);
37631
+ if (last === undefined)
37632
+ return [`${firstPrefix}${word}`];
37633
+ if (last.length + 1 + word.length <= guideWidth) {
37634
+ return [...lines.slice(0, -1), `${last} ${word}`];
37635
+ }
37636
+ return [...lines, `${restPrefix}${word}`];
37637
+ }, []);
37638
+ }
37639
+ function formatLine(line) {
37640
+ if (line.startsWith("# "))
37641
+ return [`${line.slice(2)}:`];
37642
+ if (line.startsWith("$ "))
37643
+ return [` ${line}`];
37644
+ if (line.startsWith("- "))
37645
+ return wrapWords(line.slice(2), " - ", " ");
37646
+ return wrapWords(line, " ", " ");
37647
+ }
37648
+ function formatGuide(source) {
37649
+ const paragraphs = source.trim().split(/\n\s*\n/).map((paragraph) => paragraph.split(`
37650
+ `));
37651
+ const rendered = paragraphs.map((lines, index) => {
37652
+ const text = lines.flatMap(formatLine).join(`
37653
+ `);
37654
+ const followsTitle = paragraphs[index - 1]?.[0]?.startsWith("# ") === true;
37655
+ return index === 0 ? text : `${followsTitle ? `
37656
+ ` : `
37657
+
37658
+ `}${text}`;
37659
+ });
37660
+ return `
37661
+ ${rendered.join("")}`;
37662
+ }
37663
+
37664
+ // src/commands/runner/guides/highlightSelector.txt
37665
+ var highlightSelector_default = `# Reading the result
37666
+
37667
+ \`inspect element-html\` tells you what a selector matched; highlight-selector shows you where it is, by drawing on the page itself. The highlight stays until it is replaced or cleared, which is the point: you cannot see the runner's screen, so the only way to read the result is the next screenshot.
37668
+
37669
+ Three answers are worth telling apart. A selector that matched prints how many elements it hit and exits 0. A selector the page read fine but that matched nothing also exits 0, because the call did what was asked and the count is the answer; the message says the syntax was fine so you look at the page, not the locator. A selector the page could not read at all exits 2, because that one is yours to correct and retrying will not change it.
37670
+
37671
+ runner-cannot-highlight-selectors exits 2 and means the runner has no browser to draw on. no-answer exits 4: a highlight runs inside the page, so a page that is gone or mid-navigation does not answer at all rather than answering slowly.
37672
+ `;
37673
+
37674
+ // src/commands/runner/guides/importPackage.txt
37675
+ var importPackage_default = `# Installing mid-session
37676
+
37677
+ The package goes into the runner's live run, so a snippet or a selection can import it without a whole run to reinstall dependencies. The flag is --package-version because --version belongs to the CLI itself. The install resolves against your project's own dependencies, read from package.json, so it needs a run already going: there is no live run on a runner that has not run anything. npm's own refusal comes back verbatim on an exit 2, which is a name or a version to correct rather than something to retry.
37678
+ `;
37679
+
37680
+ // src/commands/runner/guides/inspect.txt
37681
+ var inspect_default = `# Browser
37682
+
37683
+ inspect answers one question about the live page and prints the answer on stdout by itself, so you can redirect or pipe it. \`page-html\` is simplified for a model to read rather than being the browser's exact markup, and --selector narrows it to one subtree. \`variable\` reads a top-level variable of the running workflow and prints it as JSON, which is how you see what your own code computed rather than what the page shows. Use it before reaching for exec: reading a value through a snippet means printing it and then fishing it back out of the console stream, which is two calls and a marker.
37684
+
37685
+ One failure covers three causes, because a runner cannot tell them apart: no live page, no element matching the selector, no variable under that name. All three exit 2 and none clears by waiting, so read the message, which carries whatever the runner said. An unreachable runner exits 4 and is worth retrying; a runner that is not running at all exits 8 and is not.
37686
+
37687
+ # Mobile
37688
+
37689
+ element-html and page-html are a browser's shapes, and a mobile runner answers them with runner-is-not-a-browser. It answers session, contexts, page-source and elements instead, and variable works on both.
37690
+
37691
+ session prints one summary line, ready or why not, because that line is the whole answer. contexts, page-source and elements print their answer as JSON on stdout, on its own, so you can pipe it (| jq .current, | jq .matches, | jq .pageSource) rather than getting a count with no way to see what was found.
37692
+
37693
+ session is the one subcommand that never answers screen-needs-a-run: readiness is the question it exists to answer, so it reports ready (with the platform, device and session id), unreachable, ambiguous (more than one session is somehow live) or no-session. The other three need a live session first. screen-needs-a-run exits 2 and means no Appium session has started on this runner yet: run a flow that opens one, then inspect again. screen-not-ready exits 4 and means the session exists but did not answer this instant, or more than one is somehow live: retry once, and relaunch the runner if it persists.
37694
+
37695
+ elements takes one of three ways to search: whole-pixel --x/--y on the device's own screen, the same coordinates a screenshot is measured in; --text, which matches exactly unless --partial is passed; or --selector, resolved the same way a screen object's own selector is, with --strategy naming how (xpath, ios-predicate or shadow; defaults to xpath). Prefer --selector when checking a selector you are about to write: it answers what that exact string resolves to, where --text and --x/--y only approximate it. The three do not mix: flags from more than one are refused before a runner is addressed. An unparseable selector answers invalid-selector, exit 2, distinctly from a selector that parsed fine but matched nothing, which answers an empty \`matches\` list, exit 0. --context on page-source or elements reads a context other than the current one, once contexts has told you which are available.
37696
+
37697
+ A browser runner answers runner-is-not-mobile to session, contexts, page-source and elements, exit 2, since retrying never helps: launch with --name android or --name ios instead. runner-unreachable exits 4 and is worth retrying.
37698
+ `;
37699
+
37700
+ // src/commands/runner/guides/keepalive.txt
37701
+ var keepalive_default = `# When to call it
37702
+
37703
+ A runner is reaped after a period of inactivity, and every command that talks to the runner counts as activity, including a journal read. keepalive exists for the gap that creates: a harness that thinks, or waits on a human, for minutes between actions would otherwise come back to a pod that is gone. It resets the clock and tells you the runner is still there. It never launches a runner.
37704
+
37705
+ It is a read with a cost: keeping the clock reset keeps a billed pod alive. Call it while you are genuinely still working, not on a timer you forget, and call \`qawolf runner terminate\` when you are done rather than leaving a pod to time out. A loop that keeps a runner alive and never stops it bills until someone notices.
37706
+ `;
37707
+
37708
+ // src/commands/runner/guides/launch.txt
37709
+ var launch_default = `# Ids
37710
+
37711
+ Runner ids are yours to choose and are scoped to your team, so agent-1 is a fine id. Launching an id that is already running attaches to that runner instead of starting and billing a second one, and the answer says which happened: read \`alreadyRunning\`. Reusing one id is therefore the cheap and safe pattern. The same id with a different --name is refused rather than silently ignored.
37712
+
37713
+ launch takes its id from --id and never reads QAWOLF_RUNNER_ID. A bare \`qawolf runner launch\` invents a random id, bills a pod under it and stores it, so a harness that exported the variable and then launched without --id ends up with a pod it is not addressing. Pass --id whenever you have an id in mind.
37714
+
37715
+ Launching an id that differs from QAWOLF_RUNNER_ID prints a warning on stderr naming both ids: the variable still outranks the directory default, so commands that omit --runner keep going to whatever it names, not the runner you just launched. That is expected when you launch an additional runner on purpose. Address that one with --runner rather than re-exporting the variable, which would repoint every other runner-less command too.
37716
+
37717
+ # The page
37718
+
37719
+ Either answer carries a \`url\`, which launch prints, as does a command that launched its own runner. It is a QA Wolf page showing what the runner is doing, where a person can also take over with their own mouse and keyboard. Hand it to a person who asks what your runner is up to. The page opens for anyone on the runner's team, however the runner was launched. There is nothing to see until the runner's first run starts its screen, so the page waits until then. You read the screen with \`screenshot\`, not with the page.
37720
+
37721
+ # Browser or mobile
37722
+
37723
+ --name also chooses between a browser and a mobile device: --name android or --name ios starts an Appium session instead of a browser. A command built for the other kind answers a failureReason naming the mismatch rather than doing something approximate: runner-is-not-mobile from inspect session, contexts, page-source and elements on a browser runner, and runner-is-not-a-browser from inspect element-html and page-html on a mobile one. Launch the right family up front rather than discovering it from a refusal mid-session.
37724
+ `;
37725
+
37726
+ // src/commands/runner/guides/list.txt
37727
+ var list_default = `# What the list holds
37728
+
37729
+ The list names the runners this directory has launched that are still running, and marks the one a command with no --runner would reach. It also includes the runner named by QAWOLF_RUNNER_ID even though this directory did not launch it, so a harness handed a runner sees it alongside the ones it started itself. Use the id column with --runner to address any of them; addressing one does not make it the default.
37730
+
37731
+ Every runner is looked up before it is listed, so a runner that idled out is absent rather than reported. The lookup neither starts a runner nor resets an inactivity clock, which is what separates list from keepalive: listing tells you what is there and changes nothing. A lookup that cannot be answered fails the command rather than returning a shorter list, because a short list reads as the whole truth.
37732
+
37733
+ Nothing is billed by listing, but everything in the list is billing. Terminate what you are done with.
37734
+
37735
+ The table leaves out the page address, which beside a 63-character id outgrows a terminal. --json carries it as \`url\` on every runner:
37736
+
37737
+ $ qawolf runner list --json | jq -r '.[] | [.id, .url] | @tsv'
37738
+ `;
37739
+
37740
+ // src/commands/runner/guides/listRecordings.txt
37741
+ var listRecordings_default = "# Reading pages\n\n$ qawolf runner list-recordings --runner ci --json\n$ qawolf runner list-recordings --runner ci --recording-id <uuid> --json\n$ qawolf runner list-recordings --runner ci --page-token '<nextPageToken>' --json\n\nEach response is one page with `recordings` and an optional `nextPageToken`. Follow that token for more; the order is storage-key order, not newest-first. Entries include status, run ids, a stable platform `url`, and an expiring `videoUrl` when video is available. An empty lookup means the recording has not been published or does not exist. Abrupt runner loss may leave video unavailable. Keep the runner id for history lookups after termination clears the local default.\n";
37742
+
37743
+ // src/commands/runner/guides/promoteSnapshot.txt
37744
+ var promoteSnapshot_default = `# Paths
37745
+
37746
+ Use it when a run's image diff fails and the new screenshot is the one you want. Both paths are the ones the diff reported in its imageDiffArtifact run event, and both are named rather than positional, because two paths with one unlabelled is easy to get backwards and swapping them promotes the wrong image. They are paths inside the run's own screenshot storage, not files on your machine.
37747
+
37748
+ snapshot-not-found exits 2 and means the run wrote no screenshot at that path, which nearly always means the paths did not come from a diff this run produced. Nothing is changed, so correcting the path and repeating is safe. Promoting twice is also safe, so an unreachable runner is worth retrying.
37749
+ `;
37750
+
37751
+ // src/commands/runner/guides/record.txt
37752
+ var record_default = `# Recording
37753
+
37754
+ Start a flow first so the runner has a screen, then start a manual capture and keep \`result.state.active.id\` from the \`record start --json\` answer to stop that capture:
37755
+
37756
+ $ qawolf runner record start --runner ci --json
37757
+ $ qawolf runner record stop <recording-id> --runner ci
37758
+ $ qawolf runner record status --runner ci
37759
+ $ qawolf runner record auto on --runner ci
37760
+
37761
+ Start generates a UUID unless you pass --recording-id <uuid>. If a start response is lost, the error includes that UUID: check \`record status\` and reuse the UUID when retrying. Stop always names a specific recording, so retrying it cannot stop a later capture. Manual recordings span runs and suppress automatic capture until stopped. The auto setting affects subsequent full runs. An active automatic recording ends with its run; \`record stop\` cannot interrupt it. If a stop publishes a recording with status "failed", the CLI exits 1 and still prints the manifest so callers can inspect it.
37762
+
37763
+ Published recordings stay readable after the runner terminates, with \`qawolf runner list-recordings\`.
37764
+ `;
37765
+
37766
+ // src/commands/runner/guides/run.txt
37767
+ var run_default = `# What travels
37768
+
37769
+ run ships the flow file, everything it imports, and your package.json and tsconfig.json. Nothing else travels, so you can run from the root of a large project without sending it. The runner holds no copy of your project, so what runs is exactly what is on disk at that moment, uncommitted edits included.
37770
+
37771
+ Imports are followed the same way a run from the QA Wolf app follows them: relative paths and tsconfig.json path aliases, resolving .ts and .js. An \`export ... from\` re-export is not followed, and neither is require(), so a barrel file does not pull in what it re-exports. A package.json has to be there, since the run reads its npm dependencies from it, and the files may carry at most 30 MiB in total. A missing file, a missing package.json and files over the cap are all refused before any runner is resolved or launched, so a typo costs nothing.
37772
+
37773
+ After the first run on a runner, later runs send only the files whose content changed, so iterating on one flow costs a small request rather than the whole graph again. --json reports which happened in \`fileSync\`: delta or full. Nothing about this needs managing: the baseline lives in .qawolf/runner-files.json, a switch to another runner ignores it, and a runner that turns out not to hold what was claimed gets the whole set resent automatically.
37774
+
37775
+ # Waiting for the outcome
37776
+
37777
+ The call answers with a run id as soon as the run is accepted. The outcome is not in that answer; it is in the run-status stream, whose entries carry \`runId\`, \`status\` and an \`errorMessage\` when there is one.
37778
+
37779
+ Pass --follow and let run wait for you. It reports the run's status, in progress, then passed or failed, and ends on the settled status. Exit 1 means the run did not pass. --logs, --run-events and --recorder-events mirror more streams into the follow, and each implies --follow on its own. The recorder is runner-wide rather than run-scoped, so --recorder-events carries whatever is recorded after an anchor taken just before submission. Whatever mirrors are on, the follow still ends on the status, never on them, so a run that prints nothing still ends the follow and a run that dies mid-sentence still reports how. Combining mirror flags interleaves their lines with nothing saying which stream a line came from: fine for eyeballs; when parsing, follow one stream at a time.
37780
+
37781
+ To submit and come back later, poll with the same rule the CLI uses: \`status\` is in-progress while the run is going, and any other value means it has settled. \`events --follow\` does not end when the run settles, so it cannot be used to wait for one.
37782
+
37783
+ $ qawolf runner run flows/checkout.flow.ts --json
37784
+ $ qawolf runner events run-status --run <runId> --tail 1 | jq -r '.status'
37785
+
37786
+ # When the runner could not be reached
37787
+
37788
+ If run reports that the runner could not be reached, that does not mean the run did not start. The runner may have accepted it and been too slow to answer, and resubmitting bills and journals a second run.
37789
+
37790
+ There is no clean recovery. The journal lives on the same pod, so while the runner stays unreachable a run-status read fails the same way. Wait for the runner to answer again, then read run-status without --run and look at the newest \`runId\`. Nothing ties that id back to your submission, and a runner takes work from anyone addressing it, so treat the newest id as your run only if you know nothing else submits to this runner. An empty read is not proof the run did not start either: run returns the moment the run is accepted, and its first run-status entry may not be written yet. A resubmit always risks a second billed run, so prefer polling run-status a while longer, and only submit again once you are willing to accept that risk.
37791
+
37792
+ # Running part of a flow
37793
+
37794
+ --lines 12-40 runs those lines against the browser as it stands, so nothing is re-navigated and nothing is signed in again. Use it to iterate on a step without paying for the whole flow to reach it again.
37795
+
37796
+ The two file paths are the thing to get right, because getting them backwards runs the wrong code and nothing reports it:
37797
+ - the positional is always the flow file. It is the run's entry point, and it is required for every run, selection or not.
37798
+ - --lines-file is where the lines live. It defaults to the positional, so pass it only when the range is in another file, typically a page object whose method you want to run against the instance your last run left alive.
37799
+
37800
+ The lines-file has to be one of the files that travel, so it lives under the directory you run from. A range whose file is not collected is refused before a runner is addressed, naming the path.
37801
+
37802
+ A --lines selection is the one call that does not need a run first. If the runner had no browser, one is started before your lines run, and the command says so on stderr. Those lines then ran against a fresh page rather than the one an earlier run left, which is worth reading before you act on what you see.
37803
+
37804
+ # Environment variables
37805
+
37806
+ A run takes one of two. --env-id names a QA Wolf environment by id or alias, the same reference \`qawolf flows\` takes as --env. --env-file .env gives the run the variables in a dotenv file, in the format \`qawolf flows pull\` writes. Passing both is refused: each gives the run its whole environment, so there is no order in which they would combine.
37807
+
37808
+ A run with neither flag falls back to QAWOLF_ENVIRONMENT, the same variable \`qawolf flows\` reads, so one export covers both. The run says on stderr which environment it picked up, because those variables reach your flow's code and a run should never be given an environment silently. --env-id wins over it, and --env-file suppresses it, so a run reading a dotenv file is not handed a second environment on top.
37809
+
37810
+ Prefer --env-id. QA Wolf reads and decrypts the environment itself, so the values never leave the server, nothing has to be pulled to disk first, and no size limit applies to them. It is the only way to run a flow whose environment holds something large, such as a session cookie.
37811
+
37812
+ A run that sends its own variables with --env-file may carry at most 200 of them, each value at most 16 KiB. Names follow what a shell accepts, and QAWOLF_TEAM_ID is reserved because QA Wolf sets it from the key you authenticated with. All of that is refused before a runner is addressed, naming the variable at fault.
37813
+ `;
37814
+
37815
+ // src/commands/runner/guides/screenshot.txt
37816
+ var screenshot_default = `# Reading the screen
37817
+
37818
+ The image is a real JPEG on disk, decoded, because every coding harness can open an image file. Read it with whatever vision you have. --out - writes the JPEG bytes to stdout instead, on their own, for a caller that is a process rather than an agent: the confirmation, and the JSON line under --json, goes to stderr so nothing follows the image on stdout. A terminal on stdout is refused: redirect or pipe it.
37819
+
37820
+ A runner has no screen until its first run, so until then screenshot exits 2. It never launches a runner. When you are about to act anyway, \`act --screenshot\` performs the action and returns the screen in one call.
37821
+ `;
37822
+
37823
+ // src/commands/runner/guides/stopOrTerminate.txt
37824
+ var stopOrTerminate_default = `# Stopping a run or ending a runner
37825
+
37826
+ Two different things, and the names are the only warning you get:
37827
+ - stop-run stops what the runner is executing and leaves the runner up, its browser on whatever page the run reached. The run settles as stopped rather than passed or failed. Use it to abandon a run and keep the browser you were working against.
37828
+ - terminate ends the runner and the pod with it. Everything on it is gone, and the next command under that id launches and bills a new one.
37829
+
37830
+ Both succeed when there was nothing to do, and say which: \`wasRunning\` is false when no run was going, and when no runner was running. Neither is an error, so a retry needs no special handling. terminate never launches a runner in order to end it.
37831
+ `;
37832
+
37833
+ // src/commands/runner/guides/workflow.md
37834
+ var workflow_default = "An interactive runner is a live pod with a browser or a mobile device in it. You launch one, look at it, act on it, run flows on it, and read what it recorded. Everything is a plain request to one host, so there is no connection to hold open. This guide covers what spans commands; each command's own help below covers the rest.\n\n## Which runner a command reaches\n\nCommands that target a runner find one in this order: `--runner`, then `QAWOLF_RUNNER_ID`, then the runner stored for the current directory, which `qawolf runner launch` sets. Setting the environment variable once is the most robust for a harness whose working directory may not be stable, with two catches:\n\n- `qawolf runner launch` is not in that order: it takes its id from `--id` and never reads `QAWOLF_RUNNER_ID`. Pass `--id` whenever you have an id in mind.\n- A runner id that is set is treated as found, whether or not anything is running under it. Exporting `QAWOLF_RUNNER_ID=agent-1` turns off the auto-launch described next: instead of starting `agent-1`, commands try to reach it and fail with exit code `8`, naming the id and saying the variable is what chose it. Launch that id once yourself and the rest follows.\n\nIf nothing names a runner, the commands that change something launch one and say so on stderr, naming it: `run`, `act` and `exec`. Read that announcement. The browser it just started is fresh: nothing has been run on it, nothing is signed in, and no page is open. Acting as though your earlier setup survived is the single most likely way to drive the wrong page.\n\nNo read command ever launches a runner. `screenshot`, `events` and `keepalive` tell you there is no runner rather than quietly billing one, and so does `terminate`.\n\n## The order that matters\n\nA freshly launched runner has no screen. The virtual desktop starts with the runner's first run and nothing else starts it, so until you have run something:\n\n- `screenshot` and `act` fail with exit code `2`, except `navigate`, which fails with exit code `1` (`action-failed`): it skips the screen but still needs the runner to have run something\n- `exec` fails with exit code `2`\n- `events recorder` reads as empty\n\nNone of that is a fault, and none of it clears on its own. Only `qawolf runner run <flow>` starts the screen. A bare navigate does not: it fails until the first run, however long you wait.\n\nSo the first thing you do to a new runner has to put a browser on it. That means a flow file and a `package.json` on disk, even if all you want is to drive the browser by hand; there is no \"just give me a screen\" call. Once one run has happened, the screenshot-and-act loop works for the rest of the runner's life. A `--lines` selection is the one call that does not need a run first.\n\n## Exit codes\n\nRetry on the exit code, not on the message text:\n\n- `4` is transient, with one exception. The screen is up but cannot serve this instant: restarting after a display-size change, or busy with another request. Retry in a second or two, and bound the retries. The exception is a command that changes something, where a `4` can instead mean the answer was lost with the work in flight. For `act` and `actions`, take a screenshot first and repeat only what the screen says did not happen. For `run`, poll `run-status` instead of submitting again, since a second submission risks a second billed run. For `exec`, check the effect the snippet was meant to have before running it again.\n- `6` means the work ran out of the time it is given.\n- `8` means there is no such runner. It was never launched, or it was terminated, or it idled out. Retrying never brings one back, so stop and launch the id or name one that is running. The message says which runner was meant and whether `--runner`, `QAWOLF_RUNNER_ID` or this directory's stored default chose it; read that line before you pick an id to launch.\n- `2` will not clear on its own. Nothing has run on this runner yet, so run a flow; or the runner has no browser at all, so launch with `--name playwright` instead; or the action belongs to the other runner family, so send `tap`, `swipe` or `fill` to a mobile runner and the browser actions to a browser runner. The message says which.\n\n`act`, `actions`, `run` and `exec` are the commands whose lost answer may still have taken effect.\n\n## End to end\n\nRun from a directory holding a flow and a `package.json`. The run is what starts the screen, so it is not optional even though the goal here is to drive by hand.\n\n```sh\nexport QAWOLF_API_KEY=... # the only credential\nexport QAWOLF_RUNNER_ID=agent-1 # so no command below needs --runner\n\nqawolf runner launch --id agent-1 --json # --id, not the variable; read .alreadyRunning and .url\nqawolf runner run flows/smoke.flow.ts --follow # starts the screen; exit 1 if it failed\n\nqawolf runner act navigate --url https://example.com/login --screenshot step-1.jpg # then read step-1.jpg yourself\nqawolf runner act click --button left --x 480 --y 260 --screenshot step-2.jpg\nqawolf runner act type --text \"someone@example.com\" --screenshot step-3.jpg\n\nqawolf runner inspect element-html --selector \"#email\"\nqawolf runner inspect variable --name cart | jq .total\n\nqawolf runner run flows/smoke.flow.ts --lines 12-40 --follow # just those lines\nqawolf runner events recorder --tail 5 | jq -r '[.locator] + (.alternates // []) | @tsv'\nqawolf runner terminate\n```\n\nOn a mobile runner, launch with `--name android` or `--name ios`, and drive it with `tap`, `swipe`, `fill` and `type` instead of the browser actions.\n";
37835
+
37836
+ // src/commands/runner/guides/index.ts
37837
+ var runnerGuides = {
37838
+ act: formatGuide(act_default),
37839
+ actions: formatGuide(actions_default),
37840
+ events: formatGuide(events_default),
37841
+ exec: formatGuide(exec_default),
37842
+ highlightSelector: formatGuide(highlightSelector_default),
37843
+ importPackage: formatGuide(importPackage_default),
37844
+ inspect: formatGuide(inspect_default),
37845
+ keepalive: formatGuide(keepalive_default),
37846
+ launch: formatGuide(launch_default),
37847
+ list: formatGuide(list_default),
37848
+ listRecordings: formatGuide(listRecordings_default),
37849
+ promoteSnapshot: formatGuide(promoteSnapshot_default),
37850
+ record: formatGuide(record_default),
37851
+ run: formatGuide(run_default),
37852
+ screenshot: formatGuide(screenshot_default),
37853
+ stopOrTerminate: formatGuide(stopOrTerminate_default)
37854
+ };
37855
+ var runnerWorkflowGuide = workflow_default;
37856
+
37027
37857
  // src/commands/runner/actions.register.ts
37028
37858
  var actionsExamples = `
37029
37859
  Examples:
37030
37860
  $ qawolf runner actions '[{"type":"click","button":"left","x":480,"y":260},{"type":"type","text":"hello@example.com"},{"type":"keypress","keys":["Enter"]}]' --screenshot after-login.jpg
37031
37861
  $ echo '[{"type":"click","button":"left","x":1,"y":2},{"type":"type","text":"hi"}]' | qawolf runner actions -
37032
37862
  $ qawolf runner actions '[{"type":"click","button":"left","x":480,"y":260},{"type":"type","text":"hello"}]' --screenshot-mode each --screenshot step.jpg
37033
- $ qawolf runner actions '[{"type":"scroll","x":480,"y":260,"scroll_x":0,"scroll_y":600},{"type":"click","button":"left","x":120,"y":700}]' --continue-on-failure`;
37863
+ $ qawolf runner actions '[{"type":"scroll","x":480,"y":260,"scroll_x":0,"scroll_y":600},{"type":"click","button":"left","x":120,"y":700}]' --continue-on-failure
37864
+
37865
+ Mobile:
37866
+ $ qawolf runner actions '[{"type":"tap","selector":"//*[@content-desc=\\"Email\\"]"},{"type":"type","text":"hello@example.com"},{"type":"tap","x":540,"y":1650}]' --screenshot after-login.jpg
37867
+ $ qawolf runner actions '[{"type":"fill","selector":"name == \\"Postal code\\"","strategy":"ios-predicate","text":"94107"},{"type":"swipe","from":{"x":540,"y":1600},"to":{"x":540,"y":600}}]'`;
37034
37868
  function registerRunnerActionsCommand(runner, signals) {
37035
- declareCommandKind(runner.command("actions <sequence>"), "write").description("Perform a sequence of up to ten raw actions on a runner's screen in one request, as a JSON array of the same actions `runner act` takes. Use - to read the array from stdin. Actions run back to back, so batch only steps whose targets are on the screen you last saw, and end the sequence at the step that changes the page. Each result carries an effect: performed, not-performed, or unknown when the runner stopped answering and the action may have landed").option("--continue-on-failure", "Carry on past an action that reached the runner and did not take effect; a runner that cannot be reached or a screen that cannot serve still ends the sequence").option("--runner <id>", runnerFlagDescription).option("--screenshot <path>", "Save a JPEG of the screen after the last action to this file. With --screenshot-mode each, one file per action, with the action's index before the extension. - writes the final frame to stdout and moves the confirmation to stderr").addOption(new Option2("--screenshot-mode <mode>", "Defaults to final when --screenshot is given, none otherwise").choices([...screenshotModes])).addHelpText("after", actionsExamples).action((sequence, opts, command) => withAuthContext(signals, (ctx) => handleRunnerActions(ctx, {
37869
+ declareCommandKind(runner.command("actions <sequence>"), "write").description("Perform a sequence of up to ten raw actions on a runner's screen in one request, as a JSON array of the same actions `runner act` takes. Use - to read the array from stdin. Actions run back to back, so batch only steps whose targets are on the screen you last saw, and end the sequence at the step that changes the page. Each result carries an effect: performed, not-performed, or unknown when the runner stopped answering and the action may have landed").option("--continue-on-failure", "Carry on past an action that reached the runner and did not take effect; a runner that cannot be reached or a screen that cannot serve still ends the sequence").option("--runner <id>", runnerFlagDescription).option("--screenshot <path>", "Save a JPEG of the screen after the last action to this file. With --screenshot-mode each, one file per action, with the action's index before the extension. - writes the final frame to stdout and moves the confirmation to stderr").addOption(new Option2("--screenshot-mode <mode>", "Defaults to final when --screenshot is given, none otherwise").choices([...screenshotModes])).addHelpText("after", actionsExamples).addHelpText("after", runnerGuides.actions).action((sequence, opts, command) => withAuthContext(signals, (ctx) => handleRunnerActions(ctx, {
37036
37870
  actions: sequence,
37037
37871
  continueOnFailure: opts.continueOnFailure === true,
37038
37872
  runner: opts.runner,
@@ -37203,7 +38037,7 @@ Examples:
37203
38037
  $ echo 'console.log(await page.title())' | qawolf runner exec -
37204
38038
  $ qawolf runner exec snippet.ts --file flows/checkout.flow.ts`;
37205
38039
  function registerRunnerExecCommand(runner, signals) {
37206
- declareCommandKind(runner.command("exec <file>"), "write").description("Evaluate a snippet against a runner's live page. Use - to read the snippet from stdin").option("--file <path>", "File whose scope the snippet is evaluated in; it and the directory's other files travel with it").option("--runner <id>", runnerFlagDescription).addHelpText("after", execExamples).action((file, opts, command) => withAuthContext(signals, (ctx) => handleRunnerExec(ctx, { contextFile: opts.file, runner: opts.runner, source: file }, runnerDeps(ctx)))(opts, command));
38040
+ declareCommandKind(runner.command("exec <file>"), "write").description("Evaluate a snippet against a runner's live page. Use - to read the snippet from stdin").option("--file <path>", "File whose scope the snippet is evaluated in; it and the directory's other files travel with it").option("--runner <id>", runnerFlagDescription).addHelpText("after", execExamples).addHelpText("after", runnerGuides.exec).action((file, opts, command) => withAuthContext(signals, (ctx) => handleRunnerExec(ctx, { contextFile: opts.file, runner: opts.runner, source: file }, runnerDeps(ctx)))(opts, command));
37207
38041
  }
37208
38042
 
37209
38043
  // src/domains/interactiveRunner/highlightSelector.ts
@@ -37278,7 +38112,7 @@ Examples:
37278
38112
  $ qawolf runner highlight-selector "#checkout" && qawolf runner screenshot
37279
38113
  $ qawolf runner highlight-selector`;
37280
38114
  function registerRunnerHighlightSelectorCommand(runner, signals) {
37281
- declareCommandKind(runner.command("highlight-selector [selector]"), "write").description("Highlight what a selector matches on a runner's live page, so the next screenshot shows it. Omit the selector to clear the highlight").option("--runner <id>", runnerFlagDescription).addHelpText("after", highlightExamples).action((selector, opts, command) => withAuthContext(signals, (ctx) => handleRunnerHighlightSelector(ctx, { runner: opts.runner, selector }, runnerDeps(ctx)))(opts, command));
38115
+ declareCommandKind(runner.command("highlight-selector [selector]"), "write").description("Highlight what a selector matches on a runner's live page, so the next screenshot shows it. Omit the selector to clear the highlight").option("--runner <id>", runnerFlagDescription).addHelpText("after", highlightExamples).addHelpText("after", runnerGuides.highlightSelector).action((selector, opts, command) => withAuthContext(signals, (ctx) => handleRunnerHighlightSelector(ctx, { runner: opts.runner, selector }, runnerDeps(ctx)))(opts, command));
37282
38116
  }
37283
38117
 
37284
38118
  // src/domains/interactiveRunner/importPackage.ts
@@ -37389,7 +38223,7 @@ Examples:
37389
38223
  $ qawolf runner import-package dayjs
37390
38224
  $ qawolf runner import-package dayjs --package-version 1.11.13`;
37391
38225
  function registerRunnerImportPackageCommand(runner, signals) {
37392
- declareCommandKind(runner.command("import-package <name>"), "write").description("Install a package into a runner's live run, so a snippet or a selection can import it").option("--package-version <version>", "Version to install", "latest").option("--runner <id>", runnerFlagDescription).addHelpText("after", importPackageExamples).action((name, opts, command) => withAuthContext(signals, (ctx) => handleRunnerImportPackage(ctx, { name, runner: opts.runner, version: opts.packageVersion }, runnerDeps(ctx)))(opts, command));
38226
+ declareCommandKind(runner.command("import-package <name>"), "write").description("Install a package into a runner's live run, so a snippet or a selection can import it").option("--package-version <version>", "Version to install", "latest").option("--runner <id>", runnerFlagDescription).addHelpText("after", importPackageExamples).addHelpText("after", runnerGuides.importPackage).action((name, opts, command) => withAuthContext(signals, (ctx) => handleRunnerImportPackage(ctx, { name, runner: opts.runner, version: opts.packageVersion }, runnerDeps(ctx)))(opts, command));
37393
38227
  }
37394
38228
 
37395
38229
  // src/core/interactiveRunner/inspectRequest.ts
@@ -37649,7 +38483,7 @@ Examples:
37649
38483
  $ qawolf runner inspect elements --text "Sign in" --partial
37650
38484
  $ qawolf runner inspect elements --selector "//android.widget.Button[@text='Sign in']"`;
37651
38485
  function registerRunnerInspectCommands(runner, signals) {
37652
- const inspect = runner.command("inspect").description("Read one thing off a runner's live page (browser) or Appium session (mobile)").addHelpText("after", inspectExamples);
38486
+ const inspect = runner.command("inspect").description("Read one thing off a runner's live page (browser) or Appium session (mobile)").addHelpText("after", inspectExamples).addHelpText("after", runnerGuides.inspect);
37653
38487
  declareCommandKind(inspect.command("element-html"), "read").description("Print the HTML of the first element a selector matches").requiredOption("--selector <selector>", "Playwright selector to inspect").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerInspect(ctx, {
37654
38488
  flags: { name: undefined, selector: opts.selector },
37655
38489
  runner: opts.runner,
@@ -37943,8 +38777,8 @@ Examples:
37943
38777
  $ qawolf runner screenshot --out - > step-3.jpg
37944
38778
  $ qawolf runner screenshot --out - | my-vision-tool`;
37945
38779
  function registerRunnerInteractCommands(runner, signals) {
37946
- declareCommandKind(runner.command("screenshot"), "read").description("Save a JPEG of an interactive runner's screen to a file, or write it to stdout with --out -").option("--out <path>", "File to write the image to. - writes the JPEG bytes to stdout on their own and moves the confirmation, JSON included, to stderr", defaultScreenshotPath).option("--runner <id>", runnerFlagDescription).addHelpText("after", screenshotExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerScreenshot(ctx, { out: opts.out, runner: opts.runner }, runnerDeps(ctx)))(opts, command));
37947
- declareCommandKind(runner.command("act <action>"), "write").description("Perform one raw action on a runner's screen. A browser runner takes click, double_click, scroll, move, drag, keypress, navigate and type; a mobile runner takes tap, swipe, fill and type. A browser runner answers mobile actions with action-not-supported-on-browser; a mobile runner answers the other browser actions with action-not-supported-on-mobile. Use - to read a whole action as JSON from stdin").option("--button <button>", "click: left, right, wheel, back or forward").option("--duration-ms <ms>", "swipe: how long it takes, up to 10000. Slow scrolls, fast flings").option("--from <x,y>", "swipe: the point it starts at").option("--keys <keys...>", "keypress: modifiers and the key, e.g. Control a").option("--path <json>", "drag: JSON array of points to drag through").option("--runner <id>", runnerFlagDescription).option("--screenshot <path>", "Also save a JPEG of the screen, taken after the action, to this file, in place of a separate screenshot. An action that did not take effect answers with one too. - writes it to stdout and moves the confirmation, JSON included, to stderr").option("--scroll-x <delta>", "scroll: horizontal wheel delta").option("--scroll-y <delta>", "scroll: vertical wheel delta").option("--selector <selector>", "tap or fill: the element to act on, as a screen object would find it. tap takes it in place of --x and --y").option("--strategy <strategy>", "how --selector is resolved: xpath (default), ios-predicate or shadow").option("--text <text>", "type: the text to type into what has focus. fill: the field's new value").option("--to <x,y>", "swipe: the point it ends at").option("--url <url>", "navigate: the http or https URL to go to").option("--x <pixels>", "click, tap and the like: x, in screenshot pixels").option("--y <pixels>", "click, tap and the like: y, in screenshot pixels").addHelpText("after", actExamples).action((action, opts, command) => withAuthContext(signals, (ctx) => handleRunnerAct(ctx, {
38780
+ declareCommandKind(runner.command("screenshot"), "read").description("Save a JPEG of an interactive runner's screen to a file, or write it to stdout with --out -").option("--out <path>", "File to write the image to. - writes the JPEG bytes to stdout on their own and moves the confirmation, JSON included, to stderr", defaultScreenshotPath).option("--runner <id>", runnerFlagDescription).addHelpText("after", screenshotExamples).addHelpText("after", runnerGuides.screenshot).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerScreenshot(ctx, { out: opts.out, runner: opts.runner }, runnerDeps(ctx)))(opts, command));
38781
+ declareCommandKind(runner.command("act <action>"), "write").description("Perform one raw action on a runner's screen. A browser runner takes click, double_click, scroll, move, drag, keypress, navigate and type; a mobile runner takes tap, swipe, fill and type. A browser runner answers mobile actions with action-not-supported-on-browser; a mobile runner answers the other browser actions with action-not-supported-on-mobile. Use - to read a whole action as JSON from stdin").option("--button <button>", "click: left, right, wheel, back or forward").option("--duration-ms <ms>", "swipe: how long it takes, up to 10000. Slow scrolls, fast flings").option("--from <x,y>", "swipe: the point it starts at").option("--keys <keys...>", "keypress: modifiers and the key, e.g. Control a").option("--path <json>", "drag: JSON array of points to drag through").option("--runner <id>", runnerFlagDescription).option("--screenshot <path>", "Also save a JPEG of the screen, taken after the action, to this file, in place of a separate screenshot. An action that did not take effect answers with one too. - writes it to stdout and moves the confirmation, JSON included, to stderr").option("--scroll-x <delta>", "scroll: horizontal wheel delta").option("--scroll-y <delta>", "scroll: vertical wheel delta").option("--selector <selector>", "tap or fill: the element to act on, as a screen object would find it. tap takes it in place of --x and --y").option("--strategy <strategy>", "how --selector is resolved: xpath (default), ios-predicate or shadow").option("--text <text>", "type: the text to type into what has focus. fill: the field's new value").option("--to <x,y>", "swipe: the point it ends at").option("--url <url>", "navigate: the http or https URL to go to").option("--x <pixels>", "click, tap and the like: x, in screenshot pixels").option("--y <pixels>", "click, tap and the like: y, in screenshot pixels").addHelpText("after", actExamples).addHelpText("after", runnerGuides.act).action((action, opts, command) => withAuthContext(signals, (ctx) => handleRunnerAct(ctx, {
37948
38782
  flags: {
37949
38783
  button: opts.button,
37950
38784
  durationMs: opts.durationMs,
@@ -38157,11 +38991,11 @@ Examples:
38157
38991
  $ qawolf runner keepalive
38158
38992
  $ qawolf runner keepalive --runner ci`;
38159
38993
  function registerRunnerLifecycleCommands(runner, signals) {
38160
- declareCommandKind(runner.command("launch"), "write").description("Launch an interactive runner and make it this directory's default").option("--id <id>", "Id to launch under. Relaunching an id attaches to that runner").option("--name <family>", "Runner family to run, e.g. playwright").addHelpText("after", launchExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerLaunch(ctx, { id: opts.id, name: opts.name }, runnerDeps(ctx)))(opts, command));
38161
- declareCommandKind(runner.command("list"), "read").description("List the runners running on your team").option("--here", "Only the runners this directory launched").addHelpText("after", listExamples2).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerList(ctx, { here: opts.here === true }, runnerDeps(ctx)))(opts, command));
38162
- declareCommandKind(runner.command("terminate"), "write").description("End an interactive runner, and the pod it runs on with it").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerTerminate(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
38163
- declareCommandKind(runner.command("stop-run"), "write").description("Stop what a runner is currently executing, leaving the runner up").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerStopRun(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
38164
- declareCommandKind(runner.command("keepalive"), "read").description("Reset a runner's inactivity clock, for a caller that pauses between actions").option("--runner <id>", runnerFlagDescription).addHelpText("after", keepaliveExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerKeepalive(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
38994
+ declareCommandKind(runner.command("launch"), "write").description("Launch an interactive runner and make it this directory's default").option("--id <id>", "Id to launch under. Relaunching an id attaches to that runner").option("--name <family>", "Runner family to run, e.g. playwright").addHelpText("after", launchExamples).addHelpText("after", runnerGuides.launch).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerLaunch(ctx, { id: opts.id, name: opts.name }, runnerDeps(ctx)))(opts, command));
38995
+ declareCommandKind(runner.command("list"), "read").description("List the runners running on your team").option("--here", "Only the runners this directory launched").addHelpText("after", listExamples2).addHelpText("after", runnerGuides.list).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerList(ctx, { here: opts.here === true }, runnerDeps(ctx)))(opts, command));
38996
+ declareCommandKind(runner.command("terminate"), "write").description("End an interactive runner, and the pod it runs on with it").option("--runner <id>", runnerFlagDescription).addHelpText("after", runnerGuides.stopOrTerminate).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerTerminate(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
38997
+ declareCommandKind(runner.command("stop-run"), "write").description("Stop what a runner is currently executing, leaving the runner up").option("--runner <id>", runnerFlagDescription).addHelpText("after", runnerGuides.stopOrTerminate).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerStopRun(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
38998
+ declareCommandKind(runner.command("keepalive"), "read").description("Reset a runner's inactivity clock, for a caller that pauses between actions").option("--runner <id>", runnerFlagDescription).addHelpText("after", keepaliveExamples).addHelpText("after", runnerGuides.keepalive).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerKeepalive(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
38165
38999
  }
38166
39000
 
38167
39001
  // src/domains/interactiveRunner/promoteSnapshot.ts
@@ -38218,7 +39052,7 @@ Examples:
38218
39052
  $ qawolf runner promote-snapshot --screenshot checkout-1-actual.png --baseline checkout-1.png
38219
39053
  $ qawolf runner events run-events --tail 20 | jq 'select(.type == "imageDiffArtifact")'`;
38220
39054
  function registerRunnerPromoteSnapshotCommand(runner, signals) {
38221
- declareCommandKind(runner.command("promote-snapshot"), "write").description("Accept a run's screenshot as the new baseline for an image diff, on the runner that produced it").requiredOption("--screenshot <path>", "The screenshot to promote, as the image diff named it").requiredOption("--baseline <path>", "The baseline to replace, as the image diff named it").option("--runner <id>", runnerFlagDescription).addHelpText("after", promoteSnapshotExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerPromoteSnapshot(ctx, {
39055
+ declareCommandKind(runner.command("promote-snapshot"), "write").description("Accept a run's screenshot as the new baseline for an image diff, on the runner that produced it").requiredOption("--screenshot <path>", "The screenshot to promote, as the image diff named it").requiredOption("--baseline <path>", "The baseline to replace, as the image diff named it").option("--runner <id>", runnerFlagDescription).addHelpText("after", promoteSnapshotExamples).addHelpText("after", runnerGuides.promoteSnapshot).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerPromoteSnapshot(ctx, {
38222
39056
  baselinePath: opts.baseline,
38223
39057
  runner: opts.runner,
38224
39058
  screenshotPath: opts.screenshot
@@ -38412,7 +39246,7 @@ Examples:
38412
39246
  $ qawolf runner events run-logs --run <runId> --follow
38413
39247
  $ qawolf runner events console --since 120 --json`;
38414
39248
  function registerRunnerEventsCommand(runner, signals) {
38415
- declareCommandKind(runner.command("events <stream>"), "read").description(`Print a runner's journal, one entry per line. QA Wolf writes ${knownJournalStreams.join(", ")}`).option("--follow", "Keep reading as new entries arrive. Reading counts as activity, so a follow left open keeps the runner alive and billing", false).option("--run <id>", "Restrict run-scoped streams to one run").option("--runner <id>", runnerFlagDescription).option("--since <sequence>", "Read entries after this sequence").option("--tail <count>", "Read only the newest <count> entries").option("--timeout <seconds>", "Give up following after this long. Reading keeps the runner alive, so a follow left open would otherwise bill until the terminal closed", String(defaultFollowTimeoutSeconds)).addHelpText("after", eventsExamples).action((stream, opts, command) => withAuthContext(signals, (ctx) => handleRunnerEvents(ctx, {
39249
+ declareCommandKind(runner.command("events <stream>"), "read").description(`Print a runner's journal, one entry per line. QA Wolf writes ${knownJournalStreams.join(", ")}`).option("--follow", "Keep reading as new entries arrive. Reading counts as activity, so a follow left open keeps the runner alive and billing", false).option("--run <id>", "Restrict run-scoped streams to one run").option("--runner <id>", runnerFlagDescription).option("--since <sequence>", "Read entries after this sequence").option("--tail <count>", "Read only the newest <count> entries").option("--timeout <seconds>", "Give up following after this long. Reading keeps the runner alive, so a follow left open would otherwise bill until the terminal closed", String(defaultFollowTimeoutSeconds)).addHelpText("after", eventsExamples).addHelpText("after", runnerGuides.events).action((stream, opts, command) => withAuthContext(signals, (ctx) => handleRunnerEvents(ctx, {
38416
39250
  envelope: Boolean(command.optsWithGlobals().json),
38417
39251
  follow: opts.follow,
38418
39252
  run: opts.run,
@@ -38882,7 +39716,7 @@ Examples:
38882
39716
  $ qawolf runner run flows/checkout.flow.ts --lines 4-9 --lines-file pages/login.ts
38883
39717
  $ qawolf runner run flows/checkout.flow.ts --env-id staging`;
38884
39718
  function registerRunCommand(runner, signals) {
38885
- declareCommandKind(runner.command("run <flowFile>"), "write").description("Run a flow on an interactive runner, shipping the flow and what it imports").option("--follow", "Report the run's status until it settles: in progress, then passed or failed", false).option("--logs", "Stream every log line the run produces while following. Implies --follow", false).option("--run-events", "Stream the run's progress events as JSON lines while following. Implies --follow", false).option("--recorder-events", "Stream the browser actions the runner records as JSON lines while following, from an anchor taken just before submission: the recorder is runner-wide, not run-scoped. Implies --follow", false).option("--env-id <env>", "QA Wolf environment whose variables the run is given, by id or alias. Defaults to QAWOLF_ENVIRONMENT. QA Wolf reads and decrypts them itself, so they never leave the server and no size limit applies to them").option("--env-file <path>", "Dotenv file on this machine whose variables the run is given. Pass this or --env-id, not both").option("--lines <start-end>", "Run only these 1-indexed lines against the browser as it stands, instead of the whole flow from a fresh one").option("--lines-file <path>", "File the --lines range lives in. Defaults to <flowFile>; pass it only when the lines are in another file, such as a page object").option("--runner <id>", runnerFlagDescription).option("--timeout <seconds>", "Give up following after this long. Following keeps the runner alive, so a run that never settles would otherwise bill until the terminal closed", String(defaultFollowTimeoutSeconds)).addHelpText("after", runExamples2).action((flowFile, opts, command) => withAuthContext(signals, (ctx) => handleRunnerRun(ctx, {
39719
+ declareCommandKind(runner.command("run <flowFile>"), "write").description("Run a flow on an interactive runner, shipping the flow and what it imports").option("--follow", "Report the run's status until it settles: in progress, then passed or failed", false).option("--logs", "Stream every log line the run produces while following. Implies --follow", false).option("--run-events", "Stream the run's progress events as JSON lines while following. Implies --follow", false).option("--recorder-events", "Stream the browser actions the runner records as JSON lines while following, from an anchor taken just before submission: the recorder is runner-wide, not run-scoped. Implies --follow", false).option("--env-id <env>", "QA Wolf environment whose variables the run is given, by id or alias. Defaults to QAWOLF_ENVIRONMENT. QA Wolf reads and decrypts them itself, so they never leave the server and no size limit applies to them").option("--env-file <path>", "Dotenv file on this machine whose variables the run is given. Pass this or --env-id, not both").option("--lines <start-end>", "Run only these 1-indexed lines against the browser as it stands, instead of the whole flow from a fresh one").option("--lines-file <path>", "File the --lines range lives in. Defaults to <flowFile>; pass it only when the lines are in another file, such as a page object").option("--runner <id>", runnerFlagDescription).option("--timeout <seconds>", "Give up following after this long. Following keeps the runner alive, so a run that never settles would otherwise bill until the terminal closed", String(defaultFollowTimeoutSeconds)).addHelpText("after", runExamples2).addHelpText("after", runnerGuides.run).action((flowFile, opts, command) => withAuthContext(signals, (ctx) => handleRunnerRun(ctx, {
38886
39720
  entryPoint: flowFile,
38887
39721
  envFile: opts.envFile,
38888
39722
  envId: opts.envId,
@@ -39071,7 +39905,7 @@ async function handleRunnerRecordings(ctx, options, deps) {
39071
39905
 
39072
39906
  // src/commands/runner/recording.register.ts
39073
39907
  function registerRunnerRecordingCommands(runner, signals) {
39074
- const record = runner.command("record").description("Control video recording on a Playwright runner");
39908
+ const record = runner.command("record").description("Control video recording on a Playwright runner").addHelpText("after", runnerGuides.record);
39075
39909
  declareCommandKind(record.command("start"), "write").description("Start manual video capture across runs. Requires a ready screen and suppresses automatic capture until stopped").option("--recording-id <uuid>", "Recording UUID. Generated when omitted; reuse it when retrying this start").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerRecord(ctx, {
39076
39910
  command: { action: "start", recordingId: opts.recordingId },
39077
39911
  runner: opts.runner
@@ -39091,7 +39925,7 @@ function registerRunnerRecordingCommands(runner, signals) {
39091
39925
  command: { action: "auto", enabled: setting === "on" },
39092
39926
  runner: opts.runner
39093
39927
  }, runnerDeps(ctx)))(opts, command));
39094
- declareCommandKind(runner.command("list-recordings"), "read").description("Read a page of published video recordings, including after the runner terminates. Platform URLs persist; video URLs expire").option("--runner <id>", runnerFlagDescription).option("--recording-id <uuid>", "Look up one recording; an empty result means it is not published or does not exist").option("--page-token <token>", "Continue from nextPageToken returned by the previous page").action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerRecordings(ctx, {
39928
+ declareCommandKind(runner.command("list-recordings"), "read").description("Read a page of published video recordings, including after the runner terminates. Platform URLs persist; video URLs expire").option("--runner <id>", runnerFlagDescription).option("--recording-id <uuid>", "Look up one recording; an empty result means it is not published or does not exist").option("--page-token <token>", "Continue from nextPageToken returned by the previous page").addHelpText("after", runnerGuides.listRecordings).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerRecordings(ctx, {
39095
39929
  runner: opts.runner,
39096
39930
  ...opts.recordingId === undefined ? {} : { recordingId: opts.recordingId },
39097
39931
  ...opts.pageToken === undefined ? {} : { pageToken: opts.pageToken }
@@ -39100,7 +39934,7 @@ function registerRunnerRecordingCommands(runner, signals) {
39100
39934
 
39101
39935
  // src/commands/runner/index.ts
39102
39936
  function registerRunnerCommand(program, signals) {
39103
- const runner = program.command("runner").description("Drive an interactive runner on the QA Wolf platform");
39937
+ const runner = declareReferenceGuide(program.command("runner"), runnerWorkflowGuide).description("Drive an interactive runner on the QA Wolf platform");
39104
39938
  registerRunnerLifecycleCommands(runner, signals);
39105
39939
  registerRunCommand(runner, signals);
39106
39940
  registerRunnerEventsCommand(runner, signals);
@@ -39129,6 +39963,7 @@ function createProgram({
39129
39963
  registerInstallCommand(program, signals);
39130
39964
  registerRunnerCommand(program, signals);
39131
39965
  registerPublicApiCommands(program, signals);
39966
+ registerHelpCommand(program);
39132
39967
  return program;
39133
39968
  }
39134
39969
 
@@ -39142,4 +39977,4 @@ createProgram({ signals }).parseAsync().catch(() => {
39142
39977
  process.exitCode = 1;
39143
39978
  }).finally(() => exitWhenIdle(typeof process.exitCode === "number" ? process.exitCode : 0));
39144
39979
 
39145
- //# debugId=4535AB8728B9D01A64756E2164756E21
39980
+ //# debugId=A9F5B8A99DA7D0CB64756E2164756E21