@qawolf/cli 1.17.1 → 1.18.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
@@ -326161,6 +326161,13 @@ var lifecycleMessages = {
326161
326161
  runnerUnreachable: "The runner could not be reached. It may still be starting, or it may have terminated after inactivity. Retry, or launch it again."
326162
326162
  };
326163
326163
 
326164
+ // src/core/messages/interactiveRunner/list.ts
326165
+ var listMessages = {
326166
+ noRunners: "This directory has no runner running. Launch one with qawolf runner launch, or pass --runner to address a runner started elsewhere.",
326167
+ runnerCount: (count) => count === 1 ? "1 runner" : `${String(count)} runners`,
326168
+ title: "Runners"
326169
+ };
326170
+
326164
326171
  // src/core/messages/interactiveRunner/run.ts
326165
326172
  var runMessages = {
326166
326173
  envFileUnparseable: (reason) => `The --env-file could not be read. ${reason}`,
@@ -326198,6 +326205,7 @@ var runMessages = {
326198
326205
  // src/core/messages/interactiveRunner/index.ts
326199
326206
  var interactiveRunnerMessages = {
326200
326207
  ...lifecycleMessages,
326208
+ ...listMessages,
326201
326209
  ...runMessages,
326202
326210
  ...interactMessages
326203
326211
  };
@@ -343242,6 +343250,25 @@ var makeEvaluateSnippetOnRunnerContract = () => {
343242
343250
  };
343243
343251
  };
343244
343252
 
343253
+ // node_modules/@qawolf/api-contracts/dist/v1/runner/get.js
343254
+ var makeGetRunnerContract = () => {
343255
+ const input = exports_external.object({
343256
+ id: runnerIdSchema.describe("Id of the runner to look up.")
343257
+ });
343258
+ const output = exports_external.object({
343259
+ id: runnerIdSchema.describe("Id of the runner the call addressed."),
343260
+ outcome: exports_external.literal("success"),
343261
+ running: exports_external.boolean().describe("False when no runner is running under this id: it was never launched, it was terminated, or it terminated on its own after inactivity. These are not distinguished, because the run system keeps no record of a runner once it is gone.")
343262
+ });
343263
+ return {
343264
+ description: "Report whether a runner is running under this id on the caller's team. This is a lookup: it never starts a runner, and it does not reset a runner's inactivity clock the way reading its journal does. An id that is not running is a success with `running: false` rather than an error, so this is how to check a runner is still there before addressing it.",
343265
+ input,
343266
+ kind: "read",
343267
+ name: "runner.get",
343268
+ output
343269
+ };
343270
+ };
343271
+
343245
343272
  // node_modules/@qawolf/api-contracts/dist/v1/runner/highlightSelector.js
343246
343273
  var maxHighlightSelectorLength = 2000;
343247
343274
  var makeHighlightSelectorOnRunnerContract = () => {
@@ -343563,6 +343590,7 @@ var makeContractsV1 = (ids) => {
343563
343590
  },
343564
343591
  runner: {
343565
343592
  evaluateSnippet: makeEvaluateSnippetOnRunnerContract(),
343593
+ get: makeGetRunnerContract(),
343566
343594
  highlightSelector: makeHighlightSelectorOnRunnerContract(),
343567
343595
  importPackage: makeImportPackageOnRunnerContract(),
343568
343596
  inspect: makeInspectOnRunnerContract(),
@@ -346098,7 +346126,7 @@ function startUpdateCheck(deps) {
346098
346126
  // package.json
346099
346127
  var package_default = {
346100
346128
  name: "@qawolf/cli",
346101
- version: "1.17.1",
346129
+ version: "1.18.0",
346102
346130
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
346103
346131
  keywords: [
346104
346132
  "automation",
@@ -346158,7 +346186,7 @@ var package_default = {
346158
346186
  "@clack/prompts": "1.5.1",
346159
346187
  "@napi-rs/keyring": "1.3.0",
346160
346188
  "@oxc-node/core": "0.1.0",
346161
- "@qawolf/api-contracts": "0.34.0",
346189
+ "@qawolf/api-contracts": "0.35.0",
346162
346190
  "@qawolf/emails": "1.1.1",
346163
346191
  "@qawolf/flow-targets": "1.0.0",
346164
346192
  "@qawolf/flows": "0.1.4",
@@ -348590,28 +348618,35 @@ Examples:
348590
348618
  // src/domains/flows/list.ts
348591
348619
  import path2 from "node:path";
348592
348620
 
348593
- // src/domains/flows/renderListTable.ts
348594
- var columns = ["name", "target", "file"];
348621
+ // src/core/renderTable.ts
348595
348622
  var ansiBold = "\x1B[1m";
348596
348623
  var ansiReset = "\x1B[0m";
348597
- function renderListTable(rows, boldHeader) {
348598
- const widths = {
348599
- name: Math.max("name".length, ...rows.map((r2) => r2.name.length)),
348600
- target: Math.max("target".length, ...rows.map((r2) => r2.target.length)),
348601
- file: Math.max("file".length, ...rows.map((r2) => r2.file.length))
348602
- };
348603
- const header = {
348604
- name: "name",
348605
- target: "target",
348606
- file: "file"
348607
- };
348608
- const renderRow = (cells) => columns.map((c3) => cells[c3].padEnd(widths[c3])).join(" ").trimEnd();
348609
- const headerLine = boldHeader ? `${ansiBold}${renderRow(header)}${ansiReset}` : renderRow(header);
348610
- return [headerLine, ...rows.map(renderRow)].join(`
348624
+ function renderTable(options) {
348625
+ const { boldHeader, columns, rows } = options;
348626
+ const measured = columns.map((column) => ({
348627
+ column,
348628
+ width: Math.max(column.header.length, ...rows.map((row) => column.value(row).length))
348629
+ }));
348630
+ const renderRow = (cell) => measured.map(({ column, width }) => cell(column).padEnd(width)).join(" ").trimEnd();
348631
+ const header = renderRow((column) => column.header);
348632
+ return [
348633
+ boldHeader ? `${ansiBold}${header}${ansiReset}` : header,
348634
+ ...rows.map((row) => renderRow((column) => column.value(row)))
348635
+ ].join(`
348611
348636
  `) + `
348612
348637
  `;
348613
348638
  }
348614
348639
 
348640
+ // src/domains/flows/renderListTable.ts
348641
+ var columns = [
348642
+ { header: "name", value: (row) => row.name },
348643
+ { header: "target", value: (row) => row.target },
348644
+ { header: "file", value: (row) => row.file }
348645
+ ];
348646
+ function renderListTable(rows, boldHeader) {
348647
+ return renderTable({ boldHeader, columns, rows });
348648
+ }
348649
+
348615
348650
  // src/domains/flows/list.ts
348616
348651
  async function flowsList(ctx, pattern, deps) {
348617
348652
  const patterns = pattern ? [pattern] : [];
@@ -348672,7 +348707,8 @@ async function flowsListRemote(ctx, pattern, options) {
348672
348707
  file: f2.path,
348673
348708
  name: f2.name,
348674
348709
  tags: f2.tags,
348675
- target: typeof f2.executionTarget === "string" ? f2.executionTarget : JSON.stringify(f2.executionTarget)
348710
+ target: typeof f2.executionTarget === "string" ? f2.executionTarget : JSON.stringify(f2.executionTarget),
348711
+ url: f2.url
348676
348712
  }));
348677
348713
  if (ctx.ui.mode === "json") {
348678
348714
  ctx.ui.json(items);
@@ -356592,6 +356628,7 @@ async function handlePublicApiCommand(ctx, spec, options) {
356592
356628
  var skippedContractNames = new Set([
356593
356629
  "flow.list",
356594
356630
  "runner.evaluateSnippet",
356631
+ "runner.get",
356595
356632
  "runner.highlightSelector",
356596
356633
  "runner.importPackage",
356597
356634
  "runner.inspect",
@@ -356660,21 +356697,7 @@ function registerPublicApiCommands(program2, signals, options = {}) {
356660
356697
  // src/domains/interactiveRunner/runnerCallOptions.ts
356661
356698
  var runnerCallOptions = { timeoutMs: 90000 };
356662
356699
 
356663
- // src/domains/interactiveRunner/runnerIds.ts
356664
- function parseRunnerId(id) {
356665
- const parsed = runnerIdSchema.safeParse(id);
356666
- if (!parsed.success)
356667
- return { error: exports_external.prettifyError(parsed.error), ok: false };
356668
- return { id: parsed.data, ok: true };
356669
- }
356670
- function parseRunnerName(runnerName) {
356671
- const parsed = runnerNameSchema.safeParse(runnerName);
356672
- if (!parsed.success)
356673
- return { error: exports_external.prettifyError(parsed.error), ok: false };
356674
- return { ok: true, runnerName: parsed.data };
356675
- }
356676
-
356677
- // src/domains/interactiveRunner/launch.ts
356700
+ // src/domains/interactiveRunner/launchAndRemember.ts
356678
356701
  async function launchRunner(ctx, options) {
356679
356702
  const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.launch, {
356680
356703
  id: options.id,
@@ -356694,38 +356717,50 @@ async function launchAndRemember(ctx, options, deps) {
356694
356717
  const priorDefault = await deps.store.readDefaultRunnerId().catch(() => {
356695
356718
  return;
356696
356719
  });
356697
- const remembered = await deps.store.writeDefaultRunnerId(options.id).then(() => true).catch(() => {
356720
+ const remembered = await deps.store.rememberLaunch({ id: options.id, runnerName: options.runnerName }).then(() => true).catch(() => {
356698
356721
  ctx.ui.warn(interactiveRunnerMessages.defaultNotRemembered(options.id));
356699
356722
  return false;
356700
356723
  });
356701
356724
  const launched = await launchRunner(ctx, options);
356702
- if (launched.ok)
356725
+ if (launched.ok) {
356726
+ if (remembered) {
356727
+ await deps.store.rememberLaunch({
356728
+ id: options.id,
356729
+ runnerName: launched.value.runnerName
356730
+ }).catch(() => {
356731
+ return;
356732
+ });
356733
+ }
356703
356734
  return launched;
356735
+ }
356704
356736
  if (remembered && !launched.mayHaveArrived) {
356705
- await (priorDefault === undefined ? deps.store.clearDefaultRunnerId() : deps.store.writeDefaultRunnerId(priorDefault)).catch(() => {
356737
+ await deps.store.forgetRunner(options.id).catch(() => {
356706
356738
  return;
356707
356739
  });
356740
+ if (priorDefault !== undefined) {
356741
+ await deps.store.writeDefaultRunnerId(priorDefault).catch(() => {
356742
+ return;
356743
+ });
356744
+ }
356708
356745
  }
356709
356746
  return {
356710
356747
  ...launched,
356711
356748
  error: launched.mayHaveArrived ? interactiveRunnerMessages.launchLost(options.id, launched.error) : interactiveRunnerMessages.launchFailed(options.id, launched.error)
356712
356749
  };
356713
356750
  }
356714
- async function handleRunnerLaunch(ctx, options, deps) {
356715
- const chosenId = options.id ?? deps.makeRunnerId();
356716
- const id = parseRunnerId(chosenId);
356717
- if (!id.ok)
356718
- return { error: id.error, exitCode: exitCodes.invalidArgs };
356719
- const runnerName = options.name === undefined ? undefined : parseRunnerName(options.name);
356720
- if (runnerName !== undefined && !runnerName.ok) {
356721
- return { error: runnerName.error, exitCode: exitCodes.invalidArgs };
356722
- }
356723
- const launched = await launchAndRemember(ctx, { id: id.id, runnerName: runnerName?.runnerName }, deps);
356724
- if (!launched.ok) {
356725
- return { ...failureFields(launched), exitCode: launched.exitCode };
356726
- }
356727
- ctx.ui.output(launched.value, launched.value.alreadyRunning ? interactiveRunnerMessages.alreadyRunning(launched.value.id) : interactiveRunnerMessages.launched(launched.value.id));
356728
- return;
356751
+
356752
+ // src/domains/interactiveRunner/runnerIds.ts
356753
+ function parseRunnerId(id) {
356754
+ const parsed = runnerIdSchema.safeParse(id);
356755
+ if (!parsed.success)
356756
+ return { error: exports_external.prettifyError(parsed.error), ok: false };
356757
+ return { id: parsed.data, ok: true };
356758
+ }
356759
+ function parseRunnerName(runnerName) {
356760
+ const parsed = runnerNameSchema.safeParse(runnerName);
356761
+ if (!parsed.success)
356762
+ return { error: exports_external.prettifyError(parsed.error), ok: false };
356763
+ return { ok: true, runnerName: parsed.data };
356729
356764
  }
356730
356765
 
356731
356766
  // src/domains/interactiveRunner/resolveRunner.ts
@@ -357077,7 +357112,14 @@ function parseJson(text3) {
357077
357112
  // src/shell/interactiveRunner/runnerStore.ts
357078
357113
  import { join as join47 } from "node:path";
357079
357114
  var storeFileName = "runner.json";
357080
- var storeSchema = exports_external.object({ defaultRunnerId: exports_external.string().optional() });
357115
+ var runnersDirName = "runners";
357116
+ var storedRunnerSchema = exports_external.object({
357117
+ id: exports_external.string(),
357118
+ runnerName: exports_external.string().optional()
357119
+ });
357120
+ var storeSchema = exports_external.object({
357121
+ defaultRunnerId: exports_external.string().optional()
357122
+ });
357081
357123
  function parseJson2(text3) {
357082
357124
  try {
357083
357125
  return JSON.parse(text3);
@@ -357085,30 +357127,81 @@ function parseJson2(text3) {
357085
357127
  return;
357086
357128
  }
357087
357129
  }
357130
+ var pendingWrites = 0;
357088
357131
  function makeRunnerStore(options) {
357089
357132
  const directory = join47(options.cwd, qawolfDir);
357090
357133
  const path9 = join47(directory, storeFileName);
357091
- let pendingWrites = 0;
357092
- const nextPendingPath = () => `${path9}.${process.pid}.${++pendingWrites}.tmp`;
357134
+ const runnersDir = join47(directory, runnersDirName);
357135
+ const nextPendingPath = (target) => `${target}.${process.pid}.${++pendingWrites}.tmp`;
357136
+ const runnerPath = (runnerId) => join47(runnersDir, `${encodeURIComponent(runnerId)}.json`);
357137
+ const writeAtomically = async (target, contents2) => {
357138
+ const pendingPath = nextPendingPath(target);
357139
+ await options.fs.writeFile(pendingPath, `${JSON.stringify(contents2, undefined, 2)}
357140
+ `);
357141
+ await options.fs.rename(pendingPath, target);
357142
+ };
357143
+ const readDefaultRunnerId = async () => {
357144
+ const contents2 = await options.fs.readFile(path9).catch(() => {
357145
+ return;
357146
+ });
357147
+ if (contents2 === undefined)
357148
+ return;
357149
+ const parsed = storeSchema.safeParse(parseJson2(contents2));
357150
+ return parsed.success ? parsed.data.defaultRunnerId : undefined;
357151
+ };
357152
+ const writeDefaultRunnerId = async (runnerId) => {
357153
+ await options.fs.mkdir(directory, { recursive: true });
357154
+ await writeAtomically(path9, { defaultRunnerId: runnerId });
357155
+ };
357156
+ const readRunnerFileNames = async () => {
357157
+ try {
357158
+ const names = await options.fs.readdir(runnersDir);
357159
+ return names.filter((name) => name.endsWith(".json"));
357160
+ } catch {
357161
+ return [];
357162
+ }
357163
+ };
357093
357164
  return {
357094
- async clearDefaultRunnerId() {
357095
- await options.fs.rm(path9, { force: true });
357165
+ async forgetRunner(runnerId) {
357166
+ await options.fs.rm(runnerPath(runnerId), { force: true });
357167
+ if (await readDefaultRunnerId() === runnerId)
357168
+ await writeDefaultRunnerId(undefined);
357096
357169
  },
357097
- async readDefaultRunnerId() {
357098
- const contents2 = await options.fs.readFile(path9).catch(() => {
357099
- return;
357100
- });
357101
- if (contents2 === undefined)
357102
- return;
357103
- const parsed = storeSchema.safeParse(parseJson2(contents2));
357104
- return parsed.success ? parsed.data.defaultRunnerId : undefined;
357170
+ readDefaultRunnerId,
357171
+ async readRunners() {
357172
+ const runners = await Promise.all((await readRunnerFileNames()).map(async (name) => {
357173
+ const contents2 = await options.fs.readFile(join47(runnersDir, name)).catch(() => {
357174
+ return;
357175
+ });
357176
+ if (contents2 === undefined)
357177
+ return;
357178
+ const parsed = storedRunnerSchema.safeParse(parseJson2(contents2));
357179
+ return parsed.success ? parsed.data : undefined;
357180
+ }));
357181
+ return runners.filter((runner) => !!runner).sort((left, right) => left.id.localeCompare(right.id));
357182
+ },
357183
+ async rememberLaunch(runner) {
357184
+ await options.fs.mkdir(runnersDir, { recursive: true });
357185
+ await writeAtomically(runnerPath(runner.id), runner);
357186
+ await writeDefaultRunnerId(runner.id);
357187
+ },
357188
+ async dropRunners(runnerIds) {
357189
+ await Promise.all(runnerIds.map((runnerId) => options.fs.rm(runnerPath(runnerId), { force: true })));
357190
+ const names = await readRunnerFileNames();
357191
+ await Promise.all(names.map(async (name) => {
357192
+ const runnerFile = join47(runnersDir, name);
357193
+ const contents2 = await options.fs.readFile(runnerFile).catch(() => {
357194
+ return;
357195
+ });
357196
+ if (contents2 === undefined)
357197
+ return;
357198
+ if (storedRunnerSchema.safeParse(parseJson2(contents2)).success)
357199
+ return;
357200
+ await options.fs.rm(runnerFile, { force: true });
357201
+ }));
357105
357202
  },
357106
357203
  async writeDefaultRunnerId(runnerId) {
357107
- const pendingPath = nextPendingPath();
357108
- await options.fs.mkdir(directory, { recursive: true });
357109
- await options.fs.writeFile(pendingPath, `${JSON.stringify({ defaultRunnerId: runnerId }, undefined, 2)}
357110
- `);
357111
- await options.fs.rename(pendingPath, path9);
357204
+ await writeDefaultRunnerId(runnerId);
357112
357205
  }
357113
357206
  };
357114
357207
  }
@@ -357795,6 +357888,92 @@ async function handleRunnerKeepalive(ctx, options, deps) {
357795
357888
  return;
357796
357889
  }
357797
357890
 
357891
+ // src/domains/interactiveRunner/launch.ts
357892
+ async function handleRunnerLaunch(ctx, options, deps) {
357893
+ const chosenId = options.id ?? deps.makeRunnerId();
357894
+ const id = parseRunnerId(chosenId);
357895
+ if (!id.ok)
357896
+ return { error: id.error, exitCode: exitCodes.invalidArgs };
357897
+ const runnerName = options.name === undefined ? undefined : parseRunnerName(options.name);
357898
+ if (runnerName !== undefined && !runnerName.ok) {
357899
+ return { error: runnerName.error, exitCode: exitCodes.invalidArgs };
357900
+ }
357901
+ const launched = await launchAndRemember(ctx, { id: id.id, runnerName: runnerName?.runnerName }, deps);
357902
+ if (!launched.ok) {
357903
+ return { ...failureFields(launched), exitCode: launched.exitCode };
357904
+ }
357905
+ ctx.ui.output(launched.value, launched.value.alreadyRunning ? interactiveRunnerMessages.alreadyRunning(launched.value.id) : interactiveRunnerMessages.launched(launched.value.id));
357906
+ return;
357907
+ }
357908
+
357909
+ // src/domains/interactiveRunner/list.ts
357910
+ var columns2 = [
357911
+ { header: "id", value: (row) => row.id },
357912
+ { header: "family", value: (row) => row.runnerName ?? "" },
357913
+ { header: "default", value: (row) => row.isDefault ? "yes" : "" }
357914
+ ];
357915
+ var probeBatchSize = 8;
357916
+ async function readCandidates(deps) {
357917
+ const held = await deps.store.readRunners();
357918
+ const storedDefault = await deps.store.readDefaultRunnerId();
357919
+ const environmentValue = deps.env[runnerIdEnvironmentVariable]?.trim();
357920
+ const fromEnvironment = environmentValue ? environmentValue : undefined;
357921
+ const candidates = [...held];
357922
+ for (const id of [storedDefault, fromEnvironment]) {
357923
+ if (id !== undefined && !candidates.some((runner) => runner.id === id)) {
357924
+ candidates.push({ id });
357925
+ }
357926
+ }
357927
+ return { candidates, defaultRunnerId: fromEnvironment ?? storedDefault };
357928
+ }
357929
+ async function probeRunner(ctx, runner) {
357930
+ const result = await ctx.platformClient.callPublicApi(publicContractsV1.runner.get, { id: runner.id }, runnerCallOptions);
357931
+ if (!result.ok)
357932
+ return { ...failureFields(result), ok: false };
357933
+ return { ok: true, runner, running: result.value.running };
357934
+ }
357935
+ async function handleRunnerList(ctx, deps) {
357936
+ const { candidates, defaultRunnerId } = await readCandidates(deps);
357937
+ const running = [];
357938
+ const gone = [];
357939
+ for await (const probe of batchMap(candidates, (runner) => probeRunner(ctx, runner), probeBatchSize)) {
357940
+ if (!probe.ok) {
357941
+ return { ...failureFields(probe), exitCode: exitCodes.network };
357942
+ }
357943
+ if (probe.running)
357944
+ running.push(probe.runner);
357945
+ else
357946
+ gone.push(probe.runner.id);
357947
+ }
357948
+ await deps.store.dropRunners(gone).catch(() => {
357949
+ return;
357950
+ });
357951
+ const items = [
357952
+ ...running.filter((runner) => runner.id === defaultRunnerId),
357953
+ ...running.filter((runner) => runner.id !== defaultRunnerId)
357954
+ ].map((runner) => ({
357955
+ id: runner.id,
357956
+ isDefault: runner.id === defaultRunnerId,
357957
+ runnerName: runner.runnerName
357958
+ }));
357959
+ if (ctx.ui.mode === "json") {
357960
+ ctx.ui.json(items);
357961
+ return;
357962
+ }
357963
+ if (items.length === 0) {
357964
+ ctx.ui.info(interactiveRunnerMessages.noRunners);
357965
+ return;
357966
+ }
357967
+ if (ctx.ui.mode === "agent") {
357968
+ ctx.ui.write(renderTable({ boldHeader: false, columns: columns2, rows: items }));
357969
+ return;
357970
+ }
357971
+ ctx.ui.gap();
357972
+ ctx.ui.intro(interactiveRunnerMessages.title);
357973
+ ctx.ui.write(renderTable({ boldHeader: true, columns: columns2, rows: items }));
357974
+ ctx.ui.outro(interactiveRunnerMessages.runnerCount(items.length));
357975
+ }
357976
+
357798
357977
  // src/domains/interactiveRunner/stopRun.ts
357799
357978
  async function handleRunnerStopRun(ctx, options, deps) {
357800
357979
  const resolved = await resolveRunner(ctx, { autoLaunch: false, runner: options.runner }, deps);
@@ -357828,11 +358007,7 @@ async function handleRunnerTerminate(ctx, options, deps) {
357828
358007
  if (!result.ok) {
357829
358008
  return { ...failureFields(result), exitCode: exitCodes.network };
357830
358009
  }
357831
- await deps.store.readDefaultRunnerId().then(async (storedDefault) => {
357832
- if (storedDefault === resolved.runnerId) {
357833
- await deps.store.clearDefaultRunnerId();
357834
- }
357835
- }).catch(() => {
358010
+ await deps.store.forgetRunner(resolved.runnerId).catch(() => {
357836
358011
  ctx.ui.warn(interactiveRunnerMessages.defaultNotForgotten(resolved.runnerId));
357837
358012
  });
357838
358013
  ctx.ui.output(result.value, result.value.wasRunning ? interactiveRunnerMessages.terminated(result.value.id) : interactiveRunnerMessages.wasNotRunning(result.value.id));
@@ -357845,12 +358020,17 @@ Examples:
357845
358020
  $ qawolf runner launch
357846
358021
  $ qawolf runner launch --name android
357847
358022
  $ qawolf runner launch --id ci`;
358023
+ var listExamples2 = `
358024
+ Examples:
358025
+ $ qawolf runner list
358026
+ $ qawolf runner list --json`;
357848
358027
  var keepaliveExamples = `
357849
358028
  Examples:
357850
358029
  $ qawolf runner keepalive
357851
358030
  $ qawolf runner keepalive --runner ci`;
357852
358031
  function registerRunnerLifecycleCommands(runner, signals) {
357853
358032
  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));
358033
+ declareCommandKind(runner.command("list"), "read").description("List the runners this directory holds that are still running").addHelpText("after", listExamples2).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerList(ctx, runnerDeps(ctx)))(opts, command));
357854
358034
  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));
357855
358035
  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));
357856
358036
  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));
@@ -358648,4 +358828,4 @@ createProgram({ signals }).parseAsync().catch(() => {
358648
358828
  process.exitCode = 1;
358649
358829
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
358650
358830
 
358651
- //# debugId=C71E3AC48D2D260064756E2164756E21
358831
+ //# debugId=B6D9FB052BE12D6D64756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -60,7 +60,7 @@
60
60
  "@clack/prompts": "1.5.1",
61
61
  "@napi-rs/keyring": "1.3.0",
62
62
  "@oxc-node/core": "0.1.0",
63
- "@qawolf/api-contracts": "0.34.0",
63
+ "@qawolf/api-contracts": "0.35.0",
64
64
  "@qawolf/emails": "1.1.1",
65
65
  "@qawolf/flow-targets": "1.0.0",
66
66
  "@qawolf/flows": "0.1.4",
@@ -105,6 +105,17 @@ Inspect `git status` before publishing. Stage and commit only files changed for
105
105
  the current task, preserve unrelated worktree changes, then push the task's
106
106
  current branch.
107
107
 
108
+ The platform reads flows from the environment's flow code branch, not from your
109
+ task branch. `qawolf --json environment get` reports it as `flowCodeBranch`
110
+ (`name`, `syncStatus`, `lastSyncedCommitHash`). A flow has a platform page, an
111
+ id, and a place in `qawolf flows list --remote` only once it is on that branch
112
+ and the environment has reconciled the commit. To link or run a new flow on the
113
+ platform: integrate the flow code branch into your work, push the flow to that
114
+ branch, poll `environment get` until `lastSyncedCommitHash` includes your
115
+ commit, then read the flow's `flowId` and `url` from
116
+ `qawolf --json flows list --remote --env <environment> --include-drafts`. Send
117
+ that `url`; never guess a route and never send a repository link in its place.
118
+
108
119
  ## Commands
109
120
 
110
121
  <!-- commands-table:start — generated by `bun run generate`, do not edit -->
@@ -154,6 +165,7 @@ current branch.
154
165
  | `qawolf runner inspect variable` | read | Print a top-level variable's value from the running workflow |
155
166
  | `qawolf runner keepalive` | read | Reset a runner's inactivity clock, for a caller that pauses between actions |
156
167
  | `qawolf runner launch` | write | Launch an interactive runner and make it this directory's default |
168
+ | `qawolf runner list` | read | List the runners this directory holds that are still running |
157
169
  | `qawolf runner promote-snapshot` | write | Accept a run's screenshot as the new baseline for an image diff, on the runner that produced it |
158
170
  | `qawolf runner run` | write | Run a flow on an interactive runner, shipping the flow and what it imports |
159
171
  | `qawolf runner screenshot` | read | Save a JPEG of an interactive runner's screen to a file |
@@ -41,6 +41,32 @@ No `read` command ever launches a runner. `screenshot`, `events` and `keepalive`
41
41
  tell you there is no runner rather than quietly billing one, and so does
42
42
  `terminate`, since starting a pod in order to end it would be absurd.
43
43
 
44
+ ## Knowing what you are holding
45
+
46
+ `qawolf runner list` names the runners this directory has launched that are
47
+ still running, and marks the one a command with no `--runner` would reach:
48
+
49
+ ```text
50
+ id family default
51
+ tester-abc-main playwright yes
52
+ tester-abc-checkout playwright
53
+ ```
54
+
55
+ Every runner is looked up before it is listed, so a runner that idled out is
56
+ absent rather than reported. The lookup neither starts a runner nor resets an
57
+ inactivity clock, which is what separates `list` from `keepalive`: listing tells
58
+ you what is there and changes nothing, and holding a runner open is still
59
+ `keepalive`'s job. A lookup that cannot be answered fails the command rather
60
+ than returning a shorter list, because a short list reads as the whole truth.
61
+
62
+ Nothing is billed by listing, but everything in the list is billing. Terminate
63
+ what you are done with.
64
+
65
+ The list includes the runner named by `QAWOLF_RUNNER_ID` even though this
66
+ directory did not launch it, so a harness handed a runner sees it alongside the
67
+ ones it started itself. Use the `id` column with `--runner` to address any of
68
+ them; addressing one does not make it the default.
69
+
44
70
  ## The order that matters
45
71
 
46
72
  A freshly launched runner has no screen. The virtual desktop starts with the