@qawolf/cli 1.20.1 → 1.21.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
  };
@@ -342694,8 +342697,9 @@ var runSelectionSchema = exports_external.object({
342694
342697
  // node_modules/@qawolf/api-contracts/dist/v1/automate/index.js
342695
342698
  var makeAutomateContract = (ids) => {
342696
342699
  const input = exports_external.object({
342700
+ 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
342701
  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`."),
342702
+ 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
342703
  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
342704
  });
342701
342705
  const output = resource({ automationId: ids.automation }, { urlFieldDescription: "Absolute URL of the automation review page." });
@@ -342926,6 +342930,7 @@ var flowReadinessSchema = exports_external.enum(["draft", "active"]);
342926
342930
  // node_modules/@qawolf/api-contracts/dist/v1/flow/index.js
342927
342931
  var makeListFlowsContract = (ids) => {
342928
342932
  const input = exports_external.object({
342933
+ 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
342934
  environmentId: ids.environmentRef,
342930
342935
  includeDrafts: exports_external.boolean().default(false).describe("Include draft flows in the listing.")
342931
342936
  });
@@ -342943,7 +342948,7 @@ var makeListFlowsContract = (ids) => {
342943
342948
  }))
342944
342949
  });
342945
342950
  return {
342946
- description: "List the flows of an environment at its latest reconciled commit.",
342951
+ 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
342952
  input,
342948
342953
  kind: "read",
342949
342954
  name: "flow.list",
@@ -343269,10 +343274,13 @@ var makeEvaluateSnippetOnRunnerContract = () => {
343269
343274
  outcome: exports_external.literal("success"),
343270
343275
  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
343276
  }),
343272
- makeRunnerFailureSchema([runnerUnreachableFailureReason])
343277
+ makeRunnerFailureSchema([
343278
+ "runner-cannot-evaluate-snippets",
343279
+ runnerUnreachableFailureReason
343280
+ ])
343273
343281
  ]);
343274
343282
  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.",
343283
+ 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
343284
  input,
343277
343285
  kind: "write",
343278
343286
  name: "runner.evaluateSnippet",
@@ -346156,7 +346164,7 @@ function startUpdateCheck(deps) {
346156
346164
  // package.json
346157
346165
  var package_default = {
346158
346166
  name: "@qawolf/cli",
346159
- version: "1.20.1",
346167
+ version: "1.21.0",
346160
346168
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
346161
346169
  keywords: [
346162
346170
  "automation",
@@ -346184,16 +346192,27 @@ var package_default = {
346184
346192
  "LICENSE",
346185
346193
  "README.md",
346186
346194
  "dist/cli.js",
346195
+ "dist/runner-sdk.js",
346196
+ "dist/types/runnerSdk/index.d.ts",
346197
+ "dist/types/runnerSdk/types.d.ts",
346187
346198
  "skills"
346188
346199
  ],
346189
346200
  type: "module",
346201
+ exports: {
346202
+ "./runner-sdk": {
346203
+ types: "./dist/types/runnerSdk/index.d.ts",
346204
+ import: "./dist/runner-sdk.js"
346205
+ },
346206
+ "./package.json": "./package.json"
346207
+ },
346190
346208
  publishConfig: {
346191
346209
  access: "public"
346192
346210
  },
346193
346211
  scripts: {
346194
346212
  build: "bun run build:bundle",
346195
- "build:bundle": "bun run generate && bun scripts/build.ts",
346196
346213
  "build:binary": "bun run build && bun scripts/buildBinary.ts",
346214
+ "build:bundle": "bun run generate && bun scripts/build.ts",
346215
+ "build:types-gate": "bun scripts/checkSdkTypes.ts",
346197
346216
  dev: "bun run src/main.ts",
346198
346217
  format: "oxfmt .",
346199
346218
  "format:check": "oxfmt --check .",
@@ -346216,7 +346235,7 @@ var package_default = {
346216
346235
  "@clack/prompts": "1.5.1",
346217
346236
  "@napi-rs/keyring": "1.3.0",
346218
346237
  "@oxc-node/core": "0.1.0",
346219
- "@qawolf/api-contracts": "0.35.0",
346238
+ "@qawolf/api-contracts": "0.37.0",
346220
346239
  "@qawolf/emails": "1.1.1",
346221
346240
  "@qawolf/flow-targets": "1.0.0",
346222
346241
  "@qawolf/flows": "0.1.4",
@@ -349119,6 +349138,7 @@ async function fetchKnownTags(ctx) {
349119
349138
  // src/domains/flows/listRemote.ts
349120
349139
  async function flowsListRemote(ctx, pattern, options) {
349121
349140
  const result = await ctx.platformClient.callPublicApi(publicContractsV1.flow.list, {
349141
+ aiTaskId: options.aiTaskId,
349122
349142
  environmentId: options.env,
349123
349143
  includeDrafts: options.includeDrafts
349124
349144
  });
@@ -356431,25 +356451,32 @@ Examples:
356431
356451
  $ qawolf flows list --tag auth
356432
356452
  $ qawolf flows list --env staging --tag auth
356433
356453
  $ 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) {
356454
+ $ qawolf flows list "**/checkout/**" --remote --env staging --include-drafts
356455
+ $ qawolf flows list --remote --env staging --ai-task-id ait_123`;
356456
+ function registerFlowsCommand(program2, signals, deps = { withResolvedEnv }) {
356436
356457
  const flows = program2.command("flows").description("Manage and run QA Wolf flows");
356437
356458
  registerFlowsRunCommand(flows, signals);
356438
356459
  registerRunWorkerCommand(flows, signals);
356439
356460
  declareCommandKind(flows.command("list [pattern]"), "local", {
356440
356461
  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) => {
356462
+ }).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
356463
  const tags = opts.tag;
356443
356464
  if (opts.remote) {
356444
- return withResolvedEnv(signals, {
356465
+ return deps.withResolvedEnv(signals, {
356445
356466
  explicit: opts.env,
356446
356467
  requiredMessage: flowsMessages.list.remoteRequiresEnv
356447
356468
  }, (ctx, env3) => flowsListRemote(ctx, pattern, {
356448
356469
  env: env3,
356449
356470
  includeDrafts: opts.includeDrafts,
356471
+ aiTaskId: opts.aiTaskId,
356450
356472
  tags
356451
356473
  }))(opts, command);
356452
356474
  }
356475
+ if (command.getOptionValueSource("aiTaskId") === "cli") {
356476
+ return withContext(signals, async () => ({
356477
+ error: flowsMessages.list.aiTaskIdRequiresRemote
356478
+ }))(opts, command);
356479
+ }
356453
356480
  if (opts.includeDrafts) {
356454
356481
  return withContext(signals, async () => ({
356455
356482
  error: flowsMessages.list.draftsRequireRemote
@@ -358322,6 +358349,28 @@ function registerRunnerInspectCommands(runner, signals) {
358322
358349
  registerRunnerInspectMobileCommands(inspect, signals);
358323
358350
  }
358324
358351
 
358352
+ // src/domains/interactiveRunner/evaluateSnippetFailure.ts
358353
+ function describeEvaluateSnippetFailure(failure) {
358354
+ const { failureReason } = failure;
358355
+ switch (failureReason) {
358356
+ case "runner-cannot-evaluate-snippets":
358357
+ return {
358358
+ error: interactiveRunnerMessages.runnerCannotEvaluateSnippets,
358359
+ exitCode: exitCodes.invalidArgs
358360
+ };
358361
+ case "runner-unreachable":
358362
+ return {
358363
+ error: interactiveRunnerMessages.snippetRunnerUnreachable,
358364
+ exitCode: exitCodes.network
358365
+ };
358366
+ default:
358367
+ return {
358368
+ error: interactiveRunnerMessages.snippetAnsweredUnknown(failureReason),
358369
+ exitCode: exitCodes.network
358370
+ };
358371
+ }
358372
+ }
358373
+
358325
358374
  // src/core/interactiveRunner/runFiles.ts
358326
358375
  import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep5 } from "node:path";
358327
358376
  function toCollectedPath(cwd, path9) {
@@ -358383,9 +358432,8 @@ function describeRunFilesCheck(check2) {
358383
358432
  }
358384
358433
  }
358385
358434
 
358386
- // src/domains/interactiveRunner/evaluateSnippet.ts
358387
- var stdinArgument = "-";
358388
- async function resolveScope(contextFile, deps) {
358435
+ // src/domains/interactiveRunner/snippetScope.ts
358436
+ async function resolveSnippetScope(contextFile, deps) {
358389
358437
  if (contextFile === undefined) {
358390
358438
  return { filePath: undefined, files: undefined, ok: true };
358391
358439
  }
@@ -358397,6 +358445,9 @@ async function resolveScope(contextFile, deps) {
358397
358445
  }
358398
358446
  return { filePath, files, ok: true };
358399
358447
  }
358448
+
358449
+ // src/domains/interactiveRunner/evaluateSnippet.ts
358450
+ var stdinArgument = "-";
358400
358451
  async function readCode(source, deps) {
358401
358452
  const code = source === stdinArgument ? await deps.readStdin() : await deps.readFile(source).catch(() => {
358402
358453
  return;
@@ -358416,7 +358467,7 @@ async function handleRunnerExec(ctx, options, deps) {
358416
358467
  const code = await readCode(options.source, deps);
358417
358468
  if (!code.ok)
358418
358469
  return { error: code.error, exitCode: exitCodes.invalidArgs };
358419
- const scope = await resolveScope(options.contextFile, deps);
358470
+ const scope = await resolveSnippetScope(options.contextFile, deps);
358420
358471
  if (!scope.ok)
358421
358472
  return { error: scope.error, exitCode: exitCodes.invalidArgs };
358422
358473
  const resolved = await resolveRunner(ctx, { autoLaunch: true, runner: options.runner }, deps);
@@ -358437,11 +358488,7 @@ async function handleRunnerExec(ctx, options, deps) {
358437
358488
  };
358438
358489
  }
358439
358490
  if (result.value.outcome === "failure") {
358440
- result.value.failureReason;
358441
- return {
358442
- error: interactiveRunnerMessages.runnerHasNoScreenToEvaluate,
358443
- exitCode: exitCodes.network
358444
- };
358491
+ return describeEvaluateSnippetFailure(result.value);
358445
358492
  }
358446
358493
  switch (result.value.result) {
358447
358494
  case "success":
@@ -358792,14 +358839,13 @@ async function probeRunner(ctx, runner) {
358792
358839
  return { ...failureFields(result), ok: false };
358793
358840
  return { ok: true, runner, running: result.value.running };
358794
358841
  }
358795
- async function handleRunnerList(ctx, deps) {
358842
+ async function listRunners(ctx, deps) {
358796
358843
  const { candidates, defaultRunnerId } = await readCandidates(deps);
358797
358844
  const running = [];
358798
358845
  const gone = [];
358799
358846
  for await (const probe of batchMap(candidates, (runner) => probeRunner(ctx, runner), probeBatchSize)) {
358800
- if (!probe.ok) {
358801
- return { ...failureFields(probe), exitCode: exitCodes.network };
358802
- }
358847
+ if (!probe.ok)
358848
+ return { ...failureFields(probe), ok: false };
358803
358849
  if (probe.running)
358804
358850
  running.push(probe.runner);
358805
358851
  else
@@ -358808,14 +358854,24 @@ async function handleRunnerList(ctx, deps) {
358808
358854
  await deps.store.dropRunners(gone).catch(() => {
358809
358855
  return;
358810
358856
  });
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
- }));
358857
+ return {
358858
+ items: [
358859
+ ...running.filter((runner) => runner.id === defaultRunnerId),
358860
+ ...running.filter((runner) => runner.id !== defaultRunnerId)
358861
+ ].map((runner) => ({
358862
+ id: runner.id,
358863
+ isDefault: runner.id === defaultRunnerId,
358864
+ runnerName: runner.runnerName
358865
+ })),
358866
+ ok: true
358867
+ };
358868
+ }
358869
+ async function handleRunnerList(ctx, deps) {
358870
+ const listed = await listRunners(ctx, deps);
358871
+ if (!listed.ok) {
358872
+ return { ...failureFields(listed), exitCode: exitCodes.network };
358873
+ }
358874
+ const items = listed.items;
358819
358875
  if (ctx.ui.mode === "json") {
358820
358876
  ctx.ui.json(items);
358821
358877
  return;
@@ -358948,7 +359004,7 @@ async function handleRunnerPromoteSnapshot(ctx, options, deps) {
358948
359004
  var promoteSnapshotExamples = `
358949
359005
  Examples:
358950
359006
  $ 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")'`;
359007
+ $ qawolf runner events run-events --tail 20 | jq 'select(.type == "imageDiffArtifact")'`;
358952
359008
  function registerRunnerPromoteSnapshotCommand(runner, signals) {
358953
359009
  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
359010
  baselinePath: opts.baseline,
@@ -359691,4 +359747,4 @@ createProgram({ signals }).parseAsync().catch(() => {
359691
359747
  process.exitCode = 1;
359692
359748
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
359693
359749
 
359694
- //# debugId=50C554904C1768A664756E2164756E21
359750
+ //# debugId=65DBB9A4D5E4AFCD64756E2164756E21