@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 +256 -76
- package/package.json +2 -2
- package/skills/qawolf-cli/SKILL.md +12 -0
- package/skills/qawolf-cli/references/runner.md +26 -0
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.
|
|
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.
|
|
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/
|
|
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
|
|
348598
|
-
const
|
|
348599
|
-
|
|
348600
|
-
|
|
348601
|
-
|
|
348602
|
-
};
|
|
348603
|
-
const
|
|
348604
|
-
|
|
348605
|
-
|
|
348606
|
-
|
|
348607
|
-
|
|
348608
|
-
|
|
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/
|
|
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.
|
|
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
|
|
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
|
-
|
|
356715
|
-
|
|
356716
|
-
|
|
356717
|
-
|
|
356718
|
-
|
|
356719
|
-
|
|
356720
|
-
|
|
356721
|
-
|
|
356722
|
-
|
|
356723
|
-
const
|
|
356724
|
-
if (!
|
|
356725
|
-
return {
|
|
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
|
|
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
|
-
|
|
357092
|
-
const nextPendingPath = () => `${
|
|
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
|
|
357095
|
-
await options.fs.rm(
|
|
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
|
-
|
|
357098
|
-
|
|
357099
|
-
|
|
357100
|
-
|
|
357101
|
-
|
|
357102
|
-
|
|
357103
|
-
|
|
357104
|
-
|
|
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
|
-
|
|
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.
|
|
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=
|
|
358831
|
+
//# debugId=B6D9FB052BE12D6D64756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qawolf/cli",
|
|
3
|
-
"version": "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.
|
|
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
|