@qawolf/cli 1.20.1 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -93,6 +93,28 @@ Omit `--global` to install the skill only for the current project. The skill als
93
93
 
94
94
  The skill is generated from its [source template](src/commands/qawolfCliSkill.template.md) and the command tree (`bun run generate`), and kept in sync by the test suite.
95
95
 
96
+ ## Runner SDK
97
+
98
+ Drive interactive runners from TypeScript instead of spawning commands. Every verb names the runner it addresses, so nothing is launched or billed implicitly.
99
+
100
+ ```ts
101
+ import { createRunnerSdk } from "@qawolf/cli/runner-sdk";
102
+
103
+ const runner = createRunnerSdk({ apiKey: process.env.QAWOLF_API_KEY });
104
+
105
+ await runner.launch({ id: "agent-1", runnerFamily: "default" });
106
+
107
+ const submitted = await runner.run({
108
+ entryPointPath: "src/flows/smoke.flow.ts",
109
+ environment: "ambient",
110
+ runnerId: "agent-1",
111
+ selection: "whole-flow",
112
+ });
113
+ if (submitted.ok) console.log(submitted.value.runId, submitted.value.fileSync);
114
+ ```
115
+
116
+ `ok: false` is a transport or auth failure. A runner that refused the request comes back as `ok: true` with `value.outcome === "failure"` and a `failureReason` from the published contract, so a caller can switch on it exhaustively.
117
+
96
118
  ## Reference
97
119
 
98
120
  - [Commands](https://docs.qawolf.com/qawolf/libraries/cli/api-reference/commands) — full command and flag reference
package/dist/cli.js CHANGED
@@ -325968,6 +325968,7 @@ var flowsMessages = {
325968
325968
  flowCount: (count) => pluralize(count, "flow"),
325969
325969
  list: {
325970
325970
  remoteRequiresEnv: "--remote requires an environment. Pass --env <env> or set QAWOLF_ENVIRONMENT.",
325971
+ aiTaskIdRequiresRemote: "--ai-task-id requires --remote",
325971
325972
  draftsRequireRemote: "--include-drafts requires --remote"
325972
325973
  },
325973
325974
  selectors: {
@@ -326122,7 +326123,6 @@ var interactMessages = {
326122
326123
  actionAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
326123
326124
  screenshotAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
326124
326125
  runnerHasNoScreen: "This runner does not run a browser on a virtual desktop, so there is nothing about it to see or drive. Retrying will never help: launch a playwright runner instead.",
326125
- runnerHasNoScreenToEvaluate: "The runner could not evaluate the snippet. This covers a runner that is still starting or busy, and also one with no live page to evaluate against: a freshly launched runner has no page until a run opens one, so run a flow on it with qawolf runner run first. If it is not a runner that runs a browser at all, this will never clear. It is not proof the snippet did not run, so do not resubmit one that mutates the page without reading qawolf runner events console first.",
326126
326126
  inspectNeedsABrowserRunner: "This runner is a mobile device, and element and page HTML are a browser's shapes. Retrying will never help. Launch a playwright runner to inspect one.",
326127
326127
  nothingToInspect: (errorMessage2) => `The runner had nothing to inspect${errorMessage2 === undefined ? "" : `: ${errorMessage2}`}. There is no live page, nothing matched the selector, or no variable has that name; a runner cannot tell those apart. Run a flow on it first, or check the selector or name.`,
326128
326128
  inspectAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
@@ -326137,7 +326137,10 @@ var interactMessages = {
326137
326137
  snippetErrored: (errorMessage2) => errorMessage2 === undefined ? "The snippet threw and reported no message." : `The snippet threw: ${errorMessage2}`,
326138
326138
  snippetFileUnreadable: (path) => `Could not read "${path}". Name a readable file, or "-" to read the snippet from stdin.`,
326139
326139
  snippetRan: "The snippet ran. Its value is not returned: read anything it printed with qawolf runner events console.",
326140
+ snippetRunnerUnreachable: "The runner could not be reached. It may still be starting, or it may have terminated after inactivity. It is not proof the snippet did not run: a snippet that outlives the answer window is still executing when you read this, so do not blindly resubmit one that mutates the page without reading qawolf runner events console first.",
326140
326141
  snippetStopped: "The snippet was interrupted before it finished. Anything it printed first is in qawolf runner events console.",
326142
+ runnerCannotEvaluateSnippets: "This runner has no snippet evaluator attached, so there is nothing to run code against. Retrying will never help: launch a playwright, android, ios, or windows runner instead.",
326143
+ snippetAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
326141
326144
  stdinEmptyAction: 'Nothing arrived on stdin. Pipe the action in, or name the action type instead of "-".',
326142
326145
  stdinEmptySnippet: 'Nothing arrived on stdin. Pipe the snippet in, or name a file instead of "-".',
326143
326146
  missingPackageJsonForImport: "No package.json in the current directory. The install resolves against the run's own dependencies, which are read from one.",
@@ -326153,7 +326156,7 @@ var interactMessages = {
326153
326156
  runnerCannotHighlightSelectors: "This runner has no browser to draw on, so there is nothing to highlight. Retrying will never help: launch a playwright runner instead.",
326154
326157
  highlightAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`,
326155
326158
  snapshotPromoted: (screenshotPath, baselinePath) => `Promoted ${screenshotPath} to the baseline at ${baselinePath}.`,
326156
- snapshotNotFound: (screenshotPath) => `The run wrote no screenshot at "${screenshotPath}", so nothing was changed. The two paths have to be the ones an image diff reported: read them from the image-diff-artifact entry on qawolf runner events run-events.`,
326159
+ snapshotNotFound: (screenshotPath) => `The run wrote no screenshot at "${screenshotPath}", so nothing was changed. The two paths have to be the ones an image diff reported: read them from the imageDiffArtifact entry on qawolf runner events run-events.`,
326157
326160
  runnerCannotPromoteSnapshots: "This runner stores no screenshots, so it holds no baseline to replace. Retrying will never help.",
326158
326161
  promoteSnapshotAnsweredUnknown: (failureReason) => `The runner answered "${failureReason}", which this version of the CLI does not know how to report. Upgrade with npm install -g @qawolf/cli.`
326159
326162
  };
@@ -342209,6 +342212,7 @@ var identityResponse = exports_external.union([
342209
342212
  var defaultIdSchemas = {
342210
342213
  aiTask: exports_external.string().min(1).describe("The id of the AI task."),
342211
342214
  automation: exports_external.string().min(1).describe("The id of the automation."),
342215
+ chatSession: exports_external.string().min(1).describe("The id of the chat session."),
342212
342216
  environment: exports_external.string().min(1).describe("The id of the environment."),
342213
342217
  environmentRef: exports_external.string().min(1).describe("The id or alias of the environment. Aliases require a team API key."),
342214
342218
  flow: exports_external.string().min(1).describe("The id of the flow."),
@@ -342237,19 +342241,24 @@ var makeFlowSelectionSchema = (ids) => exports_external.object({
342237
342241
  var makeCreateRunContract = (ids) => {
342238
342242
  const input = exports_external.object({
342239
342243
  aiTaskId: ids.aiTask.describe("The AI task whose conversation should receive run status updates.").optional(),
342244
+ chatSessionId: ids.chatSession.describe("The chat session whose transcript should receive run status updates.").optional(),
342240
342245
  environmentId: ids.environmentRef,
342241
342246
  environmentVariables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
342242
342247
  ignoreRules: exports_external.boolean().describe("Ignore this team's sequencing rules for this run. By default they are honoured: the flows your selection depends on are added to the run, and flows run in the order the rules require. Set this to run exactly what you selected, in any order.").default(false),
342243
342248
  pullRequestNumber: exports_external.number().int().positive().describe("The number of the pull request to associate with the run.").optional(),
342244
342249
  repository: exports_external.string().min(1).describe('The owner/name of the repository, e.g. "acme/web".').optional()
342245
- }).and(makeFlowSelectionSchema(ids));
342250
+ }).and(makeFlowSelectionSchema(ids)).refine((value) => !(value.aiTaskId && value.chatSessionId), {
342251
+ message: "Provide at most one of aiTaskId and chatSessionId: a task already implies its own chat session.",
342252
+ path: ["chatSessionId"]
342253
+ });
342246
342254
  const output = resource({
342247
342255
  excludedFlows: exports_external.array(exports_external.object({
342248
342256
  flowId: ids.flow,
342249
342257
  name: exports_external.string().describe("The name of the flow."),
342250
342258
  reason: exports_external.enum(["deleted", "not-on-branch"]).describe('Why the flow cannot run: "deleted" means the flow was deleted, "not-on-branch" means the flow has no file at the commit the run was created from.')
342251
342259
  })).describe("Explicitly requested flows that were left out of the run, each with the reason. Flows selected through tags are not reported here."),
342252
- runId: ids.run
342260
+ runId: ids.run,
342261
+ tracking: exports_external.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.')
342253
342262
  }, { urlFieldDescription: "Absolute URL of the run page." });
342254
342263
  return {
342255
342264
  description: "Create a run for the selected flows and/or tags in an environment.",
@@ -342694,8 +342703,9 @@ var runSelectionSchema = exports_external.object({
342694
342703
  // node_modules/@qawolf/api-contracts/dist/v1/automate/index.js
342695
342704
  var makeAutomateContract = (ids) => {
342696
342705
  const input = exports_external.object({
342706
+ aiTaskId: ids.aiTask.describe("The AI task to build the automation on: drafts are read from and the automation branches off the task's branch instead of the environment's.").optional(),
342697
342707
  environmentId: ids.environmentRef,
342698
- flowIds: exports_external.array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("IDs of every requested draft flow the automation builds. Reuse an existing draft only when its name matches the requested journey; a generic starter or placeholder is not a match. After publishing, resolve the IDs with `qawolf --json flows list --remote --env <environment-id-or-alias> --include-drafts`."),
342708
+ flowIds: exports_external.array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("IDs of every requested draft flow the automation builds. Reuse an existing draft only when its name matches the requested journey; a generic starter or placeholder is not a match. After publishing, resolve the IDs with `qawolf --json flows list --remote --env <environment-id-or-alias> --include-drafts`, adding `--ai-task-id` when the drafts live on an AI task's branch."),
342699
342709
  patch: exports_external.string().min(1).max(1e6).optional().describe("Unified diff applied to the automation's working branch before it runs. It cannot create or rename a selected flow; publish selected flows with Git first.")
342700
342710
  });
342701
342711
  const output = resource({ automationId: ids.automation }, { urlFieldDescription: "Absolute URL of the automation review page." });
@@ -342926,6 +342936,7 @@ var flowReadinessSchema = exports_external.enum(["draft", "active"]);
342926
342936
  // node_modules/@qawolf/api-contracts/dist/v1/flow/index.js
342927
342937
  var makeListFlowsContract = (ids) => {
342928
342938
  const input = exports_external.object({
342939
+ aiTaskId: ids.aiTask.describe("List the flows on this AI task's branch, including drafts, instead of the ones in the environment. The task must belong to the environment.").optional(),
342929
342940
  environmentId: ids.environmentRef,
342930
342941
  includeDrafts: exports_external.boolean().default(false).describe("Include draft flows in the listing.")
342931
342942
  });
@@ -342943,7 +342954,7 @@ var makeListFlowsContract = (ids) => {
342943
342954
  }))
342944
342955
  });
342945
342956
  return {
342946
- description: "List the flows of an environment at its latest reconciled commit.",
342957
+ description: "List the flows of an environment at its latest reconciled commit, or, when an AI task is given, the flows on that task's branch.",
342947
342958
  input,
342948
342959
  kind: "read",
342949
342960
  name: "flow.list",
@@ -343269,10 +343280,13 @@ var makeEvaluateSnippetOnRunnerContract = () => {
343269
343280
  outcome: exports_external.literal("success"),
343270
343281
  result: exports_external.enum(["success", "stopped", "error"]).describe("`success` if the snippet ran to completion, `stopped` if it was interrupted, `error` if it threw. Whether it ran, not what it evaluated to.")
343271
343282
  }),
343272
- makeRunnerFailureSchema([runnerUnreachableFailureReason])
343283
+ makeRunnerFailureSchema([
343284
+ "runner-cannot-evaluate-snippets",
343285
+ runnerUnreachableFailureReason
343286
+ ])
343273
343287
  ]);
343274
343288
  return {
343275
- description: "Evaluate a snippet against whatever the runner's browser is showing right now. Answers whether the snippet ran, and its error if it threw — not the value it evaluated to, so read anything you want back out of the runner's journal (a snippet's `console.log` lands in the `console` stream). A snippet starts no run and leaves no run-scoped journal entries of its own. `runner-unreachable` if the runner could not be reached or could not evaluate: as well as a runner that is still starting, has terminated after inactivity, or is busy, this covers a runner with no live page to evaluate against — which will never clear, so check that the runner you launched is one that runs a browser before retrying. It is NOT proof the snippet did not run: a snippet that outlives the answer window is still executing when you read this, so do not blindly resubmit one that mutates what the page is looking at.",
343289
+ description: "Evaluate a snippet against whatever the runner's browser is showing right now. Answers whether the snippet ran, and its error if it threw — not the value it evaluated to, so read anything you want back out of the runner's journal (a snippet's `console.log` lands in the `console` stream). A snippet starts no run and leaves no run-scoped journal entries of its own. `runner-cannot-evaluate-snippets` on a runner with nothing attached to serve one — which will never clear, so check that the runner you launched is one that runs a browser or app before retrying. `runner-unreachable` if the runner could not be reached: it may still be starting, it may have terminated after inactivity, or it may be too busy to answer. Neither is proof the snippet did not run: a snippet that outlives the answer window is still executing when you read this, so do not blindly resubmit one that mutates what the page is looking at.",
343276
343290
  input,
343277
343291
  kind: "write",
343278
343292
  name: "runner.evaluateSnippet",
@@ -346156,7 +346170,7 @@ function startUpdateCheck(deps) {
346156
346170
  // package.json
346157
346171
  var package_default = {
346158
346172
  name: "@qawolf/cli",
346159
- version: "1.20.1",
346173
+ version: "1.22.0",
346160
346174
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
346161
346175
  keywords: [
346162
346176
  "automation",
@@ -346184,16 +346198,27 @@ var package_default = {
346184
346198
  "LICENSE",
346185
346199
  "README.md",
346186
346200
  "dist/cli.js",
346201
+ "dist/runner-sdk.js",
346202
+ "dist/types/runnerSdk/index.d.ts",
346203
+ "dist/types/runnerSdk/types.d.ts",
346187
346204
  "skills"
346188
346205
  ],
346189
346206
  type: "module",
346207
+ exports: {
346208
+ "./runner-sdk": {
346209
+ types: "./dist/types/runnerSdk/index.d.ts",
346210
+ import: "./dist/runner-sdk.js"
346211
+ },
346212
+ "./package.json": "./package.json"
346213
+ },
346190
346214
  publishConfig: {
346191
346215
  access: "public"
346192
346216
  },
346193
346217
  scripts: {
346194
346218
  build: "bun run build:bundle",
346195
- "build:bundle": "bun run generate && bun scripts/build.ts",
346196
346219
  "build:binary": "bun run build && bun scripts/buildBinary.ts",
346220
+ "build:bundle": "bun run generate && bun scripts/build.ts",
346221
+ "build:types-gate": "bun scripts/checkSdkTypes.ts",
346197
346222
  dev: "bun run src/main.ts",
346198
346223
  format: "oxfmt .",
346199
346224
  "format:check": "oxfmt --check .",
@@ -346216,7 +346241,7 @@ var package_default = {
346216
346241
  "@clack/prompts": "1.5.1",
346217
346242
  "@napi-rs/keyring": "1.3.0",
346218
346243
  "@oxc-node/core": "0.1.0",
346219
- "@qawolf/api-contracts": "0.35.0",
346244
+ "@qawolf/api-contracts": "0.38.0",
346220
346245
  "@qawolf/emails": "1.1.1",
346221
346246
  "@qawolf/flow-targets": "1.0.0",
346222
346247
  "@qawolf/flows": "0.1.4",
@@ -349119,6 +349144,7 @@ async function fetchKnownTags(ctx) {
349119
349144
  // src/domains/flows/listRemote.ts
349120
349145
  async function flowsListRemote(ctx, pattern, options) {
349121
349146
  const result = await ctx.platformClient.callPublicApi(publicContractsV1.flow.list, {
349147
+ aiTaskId: options.aiTaskId,
349122
349148
  environmentId: options.env,
349123
349149
  includeDrafts: options.includeDrafts
349124
349150
  });
@@ -356431,25 +356457,32 @@ Examples:
356431
356457
  $ qawolf flows list --tag auth
356432
356458
  $ qawolf flows list --env staging --tag auth
356433
356459
  $ qawolf flows list --remote --env staging --tag auth --tag smoke
356434
- $ qawolf flows list "**/checkout/**" --remote --env staging --include-drafts`;
356435
- function registerFlowsCommand(program2, signals) {
356460
+ $ qawolf flows list "**/checkout/**" --remote --env staging --include-drafts
356461
+ $ qawolf flows list --remote --env staging --ai-task-id ait_123`;
356462
+ function registerFlowsCommand(program2, signals, deps = { withResolvedEnv }) {
356436
356463
  const flows = program2.command("flows").description("Manage and run QA Wolf flows");
356437
356464
  registerFlowsRunCommand(flows, signals);
356438
356465
  registerRunWorkerCommand(flows, signals);
356439
356466
  declareCommandKind(flows.command("list [pattern]"), "local", {
356440
356467
  kindNote: "read with --remote"
356441
- }).description("List flows matching [pattern] from the local project, or from a QA Wolf environment with --remote").option("--remote", "List flows from the QA Wolf platform instead of the local project", false).option("--env <env>", "Environment to list flows from: a QA Wolf environment with --remote, otherwise a pulled one by slug or id").option("--include-drafts", "Include draft flows in the listing (requires --remote)", false).option("--tag <name>", "Only list flows carrying this tag; repeat for several. Without --remote, matches against tags cached by the last pull", collectValue, []).addHelpText("after", listExamples).action((pattern, opts, command) => {
356468
+ }).description("List flows matching [pattern] from the local project, or from a QA Wolf environment with --remote").option("--remote", "List flows from the QA Wolf platform instead of the local project", false).option("--env <env>", "Environment to list flows from: a QA Wolf environment with --remote, otherwise a pulled one by slug or id").option("--include-drafts", "Include draft flows in the listing (requires --remote)", false).option("--tag <name>", "Only list flows carrying this tag; repeat for several. Without --remote, matches against tags cached by the last pull", collectValue, []).addOption(new Option("--ai-task-id <aiTaskId>", "List the flows on this AI task's branch, including drafts, instead of the ones in the environment (requires --remote)").env("QAWOLF_AI_TASK_ID")).addHelpText("after", listExamples).action((pattern, opts, command) => {
356442
356469
  const tags = opts.tag;
356443
356470
  if (opts.remote) {
356444
- return withResolvedEnv(signals, {
356471
+ return deps.withResolvedEnv(signals, {
356445
356472
  explicit: opts.env,
356446
356473
  requiredMessage: flowsMessages.list.remoteRequiresEnv
356447
356474
  }, (ctx, env3) => flowsListRemote(ctx, pattern, {
356448
356475
  env: env3,
356449
356476
  includeDrafts: opts.includeDrafts,
356477
+ aiTaskId: opts.aiTaskId,
356450
356478
  tags
356451
356479
  }))(opts, command);
356452
356480
  }
356481
+ if (command.getOptionValueSource("aiTaskId") === "cli") {
356482
+ return withContext(signals, async () => ({
356483
+ error: flowsMessages.list.aiTaskIdRequiresRemote
356484
+ }))(opts, command);
356485
+ }
356453
356486
  if (opts.includeDrafts) {
356454
356487
  return withContext(signals, async () => ({
356455
356488
  error: flowsMessages.list.draftsRequireRemote
@@ -357314,18 +357347,38 @@ var skippedContractNames = new Set([
357314
357347
  "runner.terminate"
357315
357348
  ]);
357316
357349
 
357317
- // src/commands/publicApi/index.ts
357318
- var groupDescriptions = {
357319
- run: "Trigger and manage QA Wolf runs on the platform"
357320
- };
357350
+ // src/commands/publicApi/environmentOptions.ts
357321
357351
  var optionEnvironmentVariables = new Map([
357322
357352
  ["environmentId", "QAWOLF_ENVIRONMENT"],
357323
- ["public.run.create.aiTaskId", "QAWOLF_AI_TASK_ID"]
357353
+ ["public.run.create.aiTaskId", "QAWOLF_AI_TASK_ID"],
357354
+ ["public.run.create.chatSessionId", "QAWOLF_CHAT_SESSION_ID"]
357324
357355
  ]);
357325
357356
  function optionEnvironmentVariable(spec, path9) {
357326
357357
  const dotted = path9.join(".");
357327
357358
  return optionEnvironmentVariables.get(`${spec.trpcPath}.${dotted}`) ?? optionEnvironmentVariables.get(dotted);
357328
357359
  }
357360
+ var exclusiveOptions = new Map([
357361
+ ["public.run.create", { keep: "aiTaskId", drop: "chatSessionId" }]
357362
+ ]);
357363
+ function withoutExclusiveConflict(spec, command, options) {
357364
+ const pair = exclusiveOptions.get(spec.trpcPath);
357365
+ if (!pair)
357366
+ return options;
357367
+ if (options[pair.keep] === undefined || options[pair.drop] === undefined) {
357368
+ return options;
357369
+ }
357370
+ const fromEnvironment = (key) => command.getOptionValueSource(key) === "env";
357371
+ if (!fromEnvironment(pair.keep) && !fromEnvironment(pair.drop)) {
357372
+ return options;
357373
+ }
357374
+ const dropped = fromEnvironment(pair.drop) ? pair.drop : pair.keep;
357375
+ return Object.fromEntries(Object.entries(options).filter(([key]) => key !== dropped));
357376
+ }
357377
+
357378
+ // src/commands/publicApi/index.ts
357379
+ var groupDescriptions = {
357380
+ run: "Trigger and manage QA Wolf runs on the platform"
357381
+ };
357329
357382
  function resolveGroup(parent2, segment) {
357330
357383
  const existing = parent2.commands.find((command) => command.name() === segment);
357331
357384
  return existing ?? parent2.command(segment).description(groupDescriptions[segment] ?? `QA Wolf public API ${segment} commands`);
@@ -357354,7 +357407,7 @@ function registerSpec(program2, signals, spec, authDeps) {
357354
357407
  command.option(flag.flag, flag.description);
357355
357408
  }
357356
357409
  }
357357
- command.action((options, leaf) => withAuthContext(signals, (ctx) => handlePublicApiCommand(ctx, spec, options), authDeps ?? {})(options, leaf));
357410
+ command.action((options, leaf) => withAuthContext(signals, (ctx) => handlePublicApiCommand(ctx, spec, withoutExclusiveConflict(spec, leaf, options)), authDeps ?? {})(options, leaf));
357358
357411
  }
357359
357412
  function registerPublicApiCommands(program2, signals, options = {}) {
357360
357413
  const specs = buildCommandSpecs(options.contracts ?? publicContractsV1, {
@@ -358322,6 +358375,28 @@ function registerRunnerInspectCommands(runner, signals) {
358322
358375
  registerRunnerInspectMobileCommands(inspect, signals);
358323
358376
  }
358324
358377
 
358378
+ // src/domains/interactiveRunner/evaluateSnippetFailure.ts
358379
+ function describeEvaluateSnippetFailure(failure) {
358380
+ const { failureReason } = failure;
358381
+ switch (failureReason) {
358382
+ case "runner-cannot-evaluate-snippets":
358383
+ return {
358384
+ error: interactiveRunnerMessages.runnerCannotEvaluateSnippets,
358385
+ exitCode: exitCodes.invalidArgs
358386
+ };
358387
+ case "runner-unreachable":
358388
+ return {
358389
+ error: interactiveRunnerMessages.snippetRunnerUnreachable,
358390
+ exitCode: exitCodes.network
358391
+ };
358392
+ default:
358393
+ return {
358394
+ error: interactiveRunnerMessages.snippetAnsweredUnknown(failureReason),
358395
+ exitCode: exitCodes.network
358396
+ };
358397
+ }
358398
+ }
358399
+
358325
358400
  // src/core/interactiveRunner/runFiles.ts
358326
358401
  import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep5 } from "node:path";
358327
358402
  function toCollectedPath(cwd, path9) {
@@ -358383,9 +358458,8 @@ function describeRunFilesCheck(check2) {
358383
358458
  }
358384
358459
  }
358385
358460
 
358386
- // src/domains/interactiveRunner/evaluateSnippet.ts
358387
- var stdinArgument = "-";
358388
- async function resolveScope(contextFile, deps) {
358461
+ // src/domains/interactiveRunner/snippetScope.ts
358462
+ async function resolveSnippetScope(contextFile, deps) {
358389
358463
  if (contextFile === undefined) {
358390
358464
  return { filePath: undefined, files: undefined, ok: true };
358391
358465
  }
@@ -358397,6 +358471,9 @@ async function resolveScope(contextFile, deps) {
358397
358471
  }
358398
358472
  return { filePath, files, ok: true };
358399
358473
  }
358474
+
358475
+ // src/domains/interactiveRunner/evaluateSnippet.ts
358476
+ var stdinArgument = "-";
358400
358477
  async function readCode(source, deps) {
358401
358478
  const code = source === stdinArgument ? await deps.readStdin() : await deps.readFile(source).catch(() => {
358402
358479
  return;
@@ -358416,7 +358493,7 @@ async function handleRunnerExec(ctx, options, deps) {
358416
358493
  const code = await readCode(options.source, deps);
358417
358494
  if (!code.ok)
358418
358495
  return { error: code.error, exitCode: exitCodes.invalidArgs };
358419
- const scope = await resolveScope(options.contextFile, deps);
358496
+ const scope = await resolveSnippetScope(options.contextFile, deps);
358420
358497
  if (!scope.ok)
358421
358498
  return { error: scope.error, exitCode: exitCodes.invalidArgs };
358422
358499
  const resolved = await resolveRunner(ctx, { autoLaunch: true, runner: options.runner }, deps);
@@ -358437,11 +358514,7 @@ async function handleRunnerExec(ctx, options, deps) {
358437
358514
  };
358438
358515
  }
358439
358516
  if (result.value.outcome === "failure") {
358440
- result.value.failureReason;
358441
- return {
358442
- error: interactiveRunnerMessages.runnerHasNoScreenToEvaluate,
358443
- exitCode: exitCodes.network
358444
- };
358517
+ return describeEvaluateSnippetFailure(result.value);
358445
358518
  }
358446
358519
  switch (result.value.result) {
358447
358520
  case "success":
@@ -358792,14 +358865,13 @@ async function probeRunner(ctx, runner) {
358792
358865
  return { ...failureFields(result), ok: false };
358793
358866
  return { ok: true, runner, running: result.value.running };
358794
358867
  }
358795
- async function handleRunnerList(ctx, deps) {
358868
+ async function listRunners(ctx, deps) {
358796
358869
  const { candidates, defaultRunnerId } = await readCandidates(deps);
358797
358870
  const running = [];
358798
358871
  const gone = [];
358799
358872
  for await (const probe of batchMap(candidates, (runner) => probeRunner(ctx, runner), probeBatchSize)) {
358800
- if (!probe.ok) {
358801
- return { ...failureFields(probe), exitCode: exitCodes.network };
358802
- }
358873
+ if (!probe.ok)
358874
+ return { ...failureFields(probe), ok: false };
358803
358875
  if (probe.running)
358804
358876
  running.push(probe.runner);
358805
358877
  else
@@ -358808,14 +358880,24 @@ async function handleRunnerList(ctx, deps) {
358808
358880
  await deps.store.dropRunners(gone).catch(() => {
358809
358881
  return;
358810
358882
  });
358811
- const items = [
358812
- ...running.filter((runner) => runner.id === defaultRunnerId),
358813
- ...running.filter((runner) => runner.id !== defaultRunnerId)
358814
- ].map((runner) => ({
358815
- id: runner.id,
358816
- isDefault: runner.id === defaultRunnerId,
358817
- runnerName: runner.runnerName
358818
- }));
358883
+ return {
358884
+ items: [
358885
+ ...running.filter((runner) => runner.id === defaultRunnerId),
358886
+ ...running.filter((runner) => runner.id !== defaultRunnerId)
358887
+ ].map((runner) => ({
358888
+ id: runner.id,
358889
+ isDefault: runner.id === defaultRunnerId,
358890
+ runnerName: runner.runnerName
358891
+ })),
358892
+ ok: true
358893
+ };
358894
+ }
358895
+ async function handleRunnerList(ctx, deps) {
358896
+ const listed = await listRunners(ctx, deps);
358897
+ if (!listed.ok) {
358898
+ return { ...failureFields(listed), exitCode: exitCodes.network };
358899
+ }
358900
+ const items = listed.items;
358819
358901
  if (ctx.ui.mode === "json") {
358820
358902
  ctx.ui.json(items);
358821
358903
  return;
@@ -358948,7 +359030,7 @@ async function handleRunnerPromoteSnapshot(ctx, options, deps) {
358948
359030
  var promoteSnapshotExamples = `
358949
359031
  Examples:
358950
359032
  $ qawolf runner promote-snapshot --screenshot checkout-1-actual.png --baseline checkout-1.png
358951
- $ qawolf runner events run-events --tail 20 | jq 'select(.type == "image-diff-artifact")'`;
359033
+ $ qawolf runner events run-events --tail 20 | jq 'select(.type == "imageDiffArtifact")'`;
358952
359034
  function registerRunnerPromoteSnapshotCommand(runner, signals) {
358953
359035
  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, {
358954
359036
  baselinePath: opts.baseline,
@@ -359691,4 +359773,4 @@ createProgram({ signals }).parseAsync().catch(() => {
359691
359773
  process.exitCode = 1;
359692
359774
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
359693
359775
 
359694
- //# debugId=50C554904C1768A664756E2164756E21
359776
+ //# debugId=B89704129DAB2F9C64756E2164756E21