@swmansion/argent 0.32.1-next.1 → 0.32.1-next.11
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-cmds.mjs +513 -438
- package/dist/client.d.ts +5 -0
- package/dist/client.js +38 -9
- package/dist/installer.mjs +15 -1
- package/dist/mcp-server.mjs +33 -8
- package/dist/tool-args.generated.d.ts +9 -4
- package/dist/tool-server.cjs +22595 -21575
- package/package.json +1 -1
- package/skills/argent-create-e2e/references/e2e-yaml.md +4 -0
- package/skills/argent-create-e2e/references/live-authoring.md +1 -0
- package/skills/argent-tv-interact/SKILL.md +4 -4
package/dist/cli-cmds.mjs
CHANGED
|
@@ -8917,7 +8917,7 @@ var require_src = __commonJS({
|
|
|
8917
8917
|
|
|
8918
8918
|
// ../argent-cli/src/run.ts
|
|
8919
8919
|
import * as fs12 from "node:fs";
|
|
8920
|
-
import * as
|
|
8920
|
+
import * as path18 from "node:path";
|
|
8921
8921
|
|
|
8922
8922
|
// ../argent-tools-client/src/launcher.ts
|
|
8923
8923
|
import * as net from "node:net";
|
|
@@ -10452,6 +10452,8 @@ var FAILURE_CODES = {
|
|
|
10452
10452
|
CLI_RUN_ARGS_JSON_INVALID: "CLI_RUN_ARGS_JSON_INVALID",
|
|
10453
10453
|
CLI_RUN_INPUT_VALIDATION_FAILED: "CLI_RUN_INPUT_VALIDATION_FAILED",
|
|
10454
10454
|
CLI_RUN_TOOL_CALL_FAILED: "CLI_RUN_TOOL_CALL_FAILED",
|
|
10455
|
+
// The result carried the tool-server's `failure` field.
|
|
10456
|
+
CLI_RUN_TOOL_RESULT_FAILED: "CLI_RUN_TOOL_RESULT_FAILED",
|
|
10455
10457
|
CLI_RUN_SAVE_IMAGE_FAILED: "CLI_RUN_SAVE_IMAGE_FAILED",
|
|
10456
10458
|
TOOL_CAPABILITY_UNSUPPORTED_OPERATION: "TOOL_CAPABILITY_UNSUPPORTED_OPERATION",
|
|
10457
10459
|
TOOL_PLATFORM_NOT_IMPLEMENTED: "TOOL_PLATFORM_NOT_IMPLEMENTED",
|
|
@@ -10550,15 +10552,21 @@ var FAILURE_CODES = {
|
|
|
10550
10552
|
ANDROID_LAUNCH_AM_START_FAILED: "ANDROID_LAUNCH_AM_START_FAILED",
|
|
10551
10553
|
ANDROID_OPEN_URL_FAILED: "ANDROID_OPEN_URL_FAILED",
|
|
10552
10554
|
ANDROID_REINSTALL_INSTALL_FAILED: "ANDROID_REINSTALL_INSTALL_FAILED",
|
|
10555
|
+
ANDROID_REINSTALL_UNINSTALL_FAILED: "ANDROID_REINSTALL_UNINSTALL_FAILED",
|
|
10553
10556
|
ANDROID_RESTART_FAILED: "ANDROID_RESTART_FAILED",
|
|
10554
10557
|
ANDROID_SETTINGS_PERMISSION_FAILED: "ANDROID_SETTINGS_PERMISSION_FAILED",
|
|
10555
10558
|
IOS_LAUNCH_SIMCTL_FAILED: "IOS_LAUNCH_SIMCTL_FAILED",
|
|
10556
10559
|
IOS_OPEN_URL_FAILED: "IOS_OPEN_URL_FAILED",
|
|
10557
10560
|
IOS_REINSTALL_INSTALL_FAILED: "IOS_REINSTALL_INSTALL_FAILED",
|
|
10561
|
+
IOS_REINSTALL_UNINSTALL_FAILED: "IOS_REINSTALL_UNINSTALL_FAILED",
|
|
10558
10562
|
IOS_RESTART_LAUNCH_FAILED: "IOS_RESTART_LAUNCH_FAILED",
|
|
10563
|
+
IOS_RESTART_TERMINATE_FAILED: "IOS_RESTART_TERMINATE_FAILED",
|
|
10564
|
+
IOS_TERMINATE_APP_FAILED: "IOS_TERMINATE_APP_FAILED",
|
|
10559
10565
|
IOS_SETTINGS_PERMISSION_FAILED: "IOS_SETTINGS_PERMISSION_FAILED",
|
|
10560
10566
|
IOS_SHAKE_FAILED: "IOS_SHAKE_FAILED",
|
|
10561
10567
|
ANDROID_SHAKE_FAILED: "ANDROID_SHAKE_FAILED",
|
|
10568
|
+
STOP_METRO_PORT_PROBE_FAILED: "STOP_METRO_PORT_PROBE_FAILED",
|
|
10569
|
+
STOP_METRO_SIGNAL_FAILED: "STOP_METRO_SIGNAL_FAILED",
|
|
10562
10570
|
// The hinge of a foldable iOS simulator: the server refused the request on a
|
|
10563
10571
|
// foldable (a bad angle, a hinge that would not move) vs. nothing to fold —
|
|
10564
10572
|
// a device that is not foldable, no hinge route, a remote simulator.
|
|
@@ -10704,10 +10712,13 @@ var FAILURE_CODES = {
|
|
|
10704
10712
|
FLOW_NAME_INVALID: "FLOW_NAME_INVALID",
|
|
10705
10713
|
FLOW_NO_ACTIVE_RECORDING: "FLOW_NO_ACTIVE_RECORDING",
|
|
10706
10714
|
FLOW_FILE_INVALID: "FLOW_FILE_INVALID",
|
|
10715
|
+
FLOW_FILE_NEWER_VERSION: "FLOW_FILE_NEWER_VERSION",
|
|
10707
10716
|
FLOW_FILE_WRITE_FAILED: "FLOW_FILE_WRITE_FAILED",
|
|
10708
10717
|
FLOW_ENTRY_UNRECOGNIZED: "FLOW_ENTRY_UNRECOGNIZED",
|
|
10709
10718
|
FLOW_E2E_HAS_PREREQUISITE: "FLOW_E2E_HAS_PREREQUISITE",
|
|
10710
10719
|
FLOW_DEVICE_RESOLUTION: "FLOW_DEVICE_RESOLUTION",
|
|
10720
|
+
// The target does not satisfy the test's `requires:` block; a directory run skips it.
|
|
10721
|
+
FLOW_REQUIREMENTS_UNMET: "FLOW_REQUIREMENTS_UNMET",
|
|
10711
10722
|
PROFILER_QUERY_MODE_INVALID: "PROFILER_QUERY_MODE_INVALID",
|
|
10712
10723
|
PROFILER_QUERY_REQUIRED_PARAM_MISSING: "PROFILER_QUERY_REQUIRED_PARAM_MISSING",
|
|
10713
10724
|
PROFILER_DATA_NOT_LOADED: "PROFILER_DATA_NOT_LOADED",
|
|
@@ -10726,6 +10737,7 @@ var FAILURE_CODES = {
|
|
|
10726
10737
|
CHROMIUM_STORAGE_EVAL_FAILED: "CHROMIUM_STORAGE_EVAL_FAILED",
|
|
10727
10738
|
CHROMIUM_TAB_NOT_FOUND: "CHROMIUM_TAB_NOT_FOUND",
|
|
10728
10739
|
CHROMIUM_TAB_OPEN_FAILED: "CHROMIUM_TAB_OPEN_FAILED",
|
|
10740
|
+
CHROMIUM_NAVIGATE_FAILED: "CHROMIUM_NAVIGATE_FAILED",
|
|
10729
10741
|
CHROMIUM_DESCRIBE_FAILED: "CHROMIUM_DESCRIBE_FAILED",
|
|
10730
10742
|
CHROMIUM_ELECTRON_APP_PATH_INVALID: "CHROMIUM_ELECTRON_APP_PATH_INVALID",
|
|
10731
10743
|
CHROMIUM_ELECTRON_SPAWN_FAILED: "CHROMIUM_ELECTRON_SPAWN_FAILED",
|
|
@@ -10742,6 +10754,8 @@ var FAILURE_CODES = {
|
|
|
10742
10754
|
SCREENSHOT_DIFF_INPUT_INVALID: "SCREENSHOT_DIFF_INPUT_INVALID",
|
|
10743
10755
|
BOOT_DEVICE_TARGET_SELECTION_INVALID: "BOOT_DEVICE_TARGET_SELECTION_INVALID",
|
|
10744
10756
|
BOOT_IOS_UNSUPPORTED_HOST: "BOOT_IOS_UNSUPPORTED_HOST",
|
|
10757
|
+
BOOT_IOS_SIMCTL_LIST_TIMEOUT: "BOOT_IOS_SIMCTL_LIST_TIMEOUT",
|
|
10758
|
+
BOOT_IOS_SIMCTL_TIMEOUT: "BOOT_IOS_SIMCTL_TIMEOUT",
|
|
10745
10759
|
BOOT_ANDROID_NO_AVDS: "BOOT_ANDROID_NO_AVDS",
|
|
10746
10760
|
BOOT_ANDROID_AVD_NOT_FOUND: "BOOT_ANDROID_AVD_NOT_FOUND",
|
|
10747
10761
|
BOOT_ANDROID_ADB_UNAVAILABLE: "BOOT_ANDROID_ADB_UNAVAILABLE",
|
|
@@ -31639,7 +31653,8 @@ async function consumeToolStream(name, body, onProgress, signal) {
|
|
|
31639
31653
|
final = {
|
|
31640
31654
|
data: msg.data,
|
|
31641
31655
|
note: msg.note,
|
|
31642
|
-
...msg.profiling === true ? { profiling: true } : {}
|
|
31656
|
+
...msg.profiling === true ? { profiling: true } : {},
|
|
31657
|
+
...typeof msg.failure === "string" ? { failure: msg.failure } : {}
|
|
31643
31658
|
};
|
|
31644
31659
|
} else if (msg.event === "error") {
|
|
31645
31660
|
throw new ToolInvocationError(msg.error ?? "tool invocation failed", {
|
|
@@ -31691,7 +31706,8 @@ async function consumeToolStream(name, body, onProgress, signal) {
|
|
|
31691
31706
|
return {
|
|
31692
31707
|
data: final.data,
|
|
31693
31708
|
note: final.note,
|
|
31694
|
-
...final.profiling ? { profiling: true } : {}
|
|
31709
|
+
...final.profiling ? { profiling: true } : {},
|
|
31710
|
+
...final.failure !== void 0 ? { failure: final.failure } : {}
|
|
31695
31711
|
};
|
|
31696
31712
|
}
|
|
31697
31713
|
function errorBodyMessage(body) {
|
|
@@ -31856,6 +31872,7 @@ function createToolsClient(options = {}) {
|
|
|
31856
31872
|
}
|
|
31857
31873
|
const shadowWarning = await e2eShadowWarningFor(name, args);
|
|
31858
31874
|
const baselineDirs = [];
|
|
31875
|
+
let unwrittenBaselines2 = [];
|
|
31859
31876
|
if (meta3?.fileInputs?.length) {
|
|
31860
31877
|
if (remote) assertRequiredPresent(meta3, finalArgs);
|
|
31861
31878
|
finalArgs = await prepareFileInputs(meta3.fileInputs, finalArgs ?? {}, {
|
|
@@ -31869,8 +31886,9 @@ function createToolsClient(options = {}) {
|
|
|
31869
31886
|
signal: opts?.signal
|
|
31870
31887
|
});
|
|
31871
31888
|
}
|
|
31872
|
-
const settle2 = async (
|
|
31873
|
-
const applied = await applyClientFileDirectives(
|
|
31889
|
+
const settle2 = async (data2) => {
|
|
31890
|
+
const applied = await applyClientFileDirectives(data2, { allowedDirs: baselineDirs });
|
|
31891
|
+
unwrittenBaselines2 = applied.failed.map((f) => f.path);
|
|
31874
31892
|
for (const { path: file2, error: error63 } of applied.failed) {
|
|
31875
31893
|
diagnose(`The baseline ${file2} was not written on this client: ${error63}`);
|
|
31876
31894
|
}
|
|
@@ -31887,6 +31905,7 @@ function createToolsClient(options = {}) {
|
|
|
31887
31905
|
}
|
|
31888
31906
|
return { ...result, warning: shadowWarning };
|
|
31889
31907
|
};
|
|
31908
|
+
const unwrittenFailure = () => unwrittenBaselines2.length > 0 ? `Baselines not written on this client: ${unwrittenBaselines2.join(", ")}` : void 0;
|
|
31890
31909
|
const stream = opts?.onProgress !== void 0 || carriesMembers(finalArgs);
|
|
31891
31910
|
const sentOnce = carriesUpload(finalArgs);
|
|
31892
31911
|
const forwarded = remote ? await forwardedSecrets(finalArgs) : void 0;
|
|
@@ -31950,11 +31969,14 @@ function createToolsClient(options = {}) {
|
|
|
31950
31969
|
}),
|
|
31951
31970
|
opts?.signal
|
|
31952
31971
|
);
|
|
31972
|
+
const data2 = await settle2(streamed.data);
|
|
31973
|
+
const failure3 = streamed.failure ?? unwrittenFailure();
|
|
31953
31974
|
return {
|
|
31954
|
-
data:
|
|
31975
|
+
data: data2,
|
|
31955
31976
|
note: streamed.note,
|
|
31956
31977
|
outputHint: meta3?.outputHint,
|
|
31957
|
-
...streamed.profiling ? { profiling: true } : {}
|
|
31978
|
+
...streamed.profiling ? { profiling: true } : {},
|
|
31979
|
+
...failure3 !== void 0 ? { failure: failure3 } : {}
|
|
31958
31980
|
};
|
|
31959
31981
|
}
|
|
31960
31982
|
let json2;
|
|
@@ -31990,11 +32012,14 @@ function createToolsClient(options = {}) {
|
|
|
31990
32012
|
});
|
|
31991
32013
|
}
|
|
31992
32014
|
opts?.signal?.throwIfAborted();
|
|
32015
|
+
const data = await settle2(json2.data);
|
|
32016
|
+
const failure2 = (typeof json2.failure === "string" ? json2.failure : void 0) ?? unwrittenFailure();
|
|
31993
32017
|
return {
|
|
31994
|
-
data
|
|
32018
|
+
data,
|
|
31995
32019
|
note: json2.note,
|
|
31996
32020
|
outputHint: meta3?.outputHint,
|
|
31997
|
-
...json2.profiling === true ? { profiling: true } : {}
|
|
32021
|
+
...json2.profiling === true ? { profiling: true } : {},
|
|
32022
|
+
...failure2 !== void 0 ? { failure: failure2 } : {}
|
|
31998
32023
|
};
|
|
31999
32024
|
}
|
|
32000
32025
|
return { fetchTools, fetchTool, callTool, baseUrl };
|
|
@@ -35809,7 +35834,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
35809
35834
|
var SESSION_ID2 = randomUUID5();
|
|
35810
35835
|
function readCliVersion() {
|
|
35811
35836
|
if (true) {
|
|
35812
|
-
return "0.32.1-next.
|
|
35837
|
+
return "0.32.1-next.10";
|
|
35813
35838
|
}
|
|
35814
35839
|
return "0.0.0";
|
|
35815
35840
|
}
|
|
@@ -36748,391 +36773,106 @@ function isValidationIssue(value) {
|
|
|
36748
36773
|
return typeof issue2.code === "string" && Array.isArray(issue2.path) && typeof issue2.message === "string";
|
|
36749
36774
|
}
|
|
36750
36775
|
|
|
36751
|
-
// ../argent-cli/src/
|
|
36752
|
-
|
|
36753
|
-
|
|
36754
|
-
|
|
36755
|
-
|
|
36776
|
+
// ../argent-cli/src/e2e.ts
|
|
36777
|
+
import * as fsp from "node:fs/promises";
|
|
36778
|
+
import { constants as fsConstants2 } from "node:fs";
|
|
36779
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
36780
|
+
import * as path17 from "node:path";
|
|
36781
|
+
|
|
36782
|
+
// ../argent-cli/src/command-args.ts
|
|
36783
|
+
var UsageError = class extends Error {
|
|
36784
|
+
constructor(message) {
|
|
36785
|
+
super(message);
|
|
36786
|
+
this.name = "UsageError";
|
|
36787
|
+
}
|
|
36788
|
+
};
|
|
36789
|
+
function listChoices(choices) {
|
|
36790
|
+
const quoted = choices.map((c2) => `"${c2}"`);
|
|
36791
|
+
if (quoted.length <= 1) return quoted.join("");
|
|
36792
|
+
return `${quoted.slice(0, -1).join(", ")} or ${quoted[quoted.length - 1]}`;
|
|
36793
|
+
}
|
|
36794
|
+
function resolveName(tok, specs) {
|
|
36795
|
+
if (tok.startsWith("--")) return tok.slice(2) in specs ? tok.slice(2) : null;
|
|
36796
|
+
const short = tok.slice(1);
|
|
36797
|
+
for (const [name, spec] of Object.entries(specs)) {
|
|
36798
|
+
if (spec.alias === short) return name;
|
|
36799
|
+
}
|
|
36800
|
+
return null;
|
|
36801
|
+
}
|
|
36802
|
+
function parseCommandArgs(argv, specs) {
|
|
36803
|
+
const positionals = [];
|
|
36804
|
+
const options = {};
|
|
36756
36805
|
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
36757
36806
|
const tok = argv[i2];
|
|
36758
|
-
if (tok === "--
|
|
36759
|
-
|
|
36760
|
-
|
|
36807
|
+
if (tok === "--") {
|
|
36808
|
+
positionals.push(...argv.slice(i2 + 1));
|
|
36809
|
+
break;
|
|
36761
36810
|
}
|
|
36762
|
-
if (tok === "
|
|
36763
|
-
|
|
36764
|
-
if (!v) throw new FlagParseException("--out requires a path");
|
|
36765
|
-
outPath = v;
|
|
36766
|
-
i2 += 1;
|
|
36811
|
+
if (!tok.startsWith("-") || tok === "-") {
|
|
36812
|
+
positionals.push(tok);
|
|
36767
36813
|
continue;
|
|
36768
36814
|
}
|
|
36769
|
-
|
|
36770
|
-
|
|
36815
|
+
const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
|
|
36816
|
+
const flag = eq === -1 ? tok : tok.slice(0, eq);
|
|
36817
|
+
const inlineValue = eq === -1 ? void 0 : tok.slice(eq + 1);
|
|
36818
|
+
const name = resolveName(flag, specs);
|
|
36819
|
+
if (name === null) throw new UsageError(`Unknown flag: ${tok}`);
|
|
36820
|
+
const spec = specs[name];
|
|
36821
|
+
const display = `--${name}`;
|
|
36822
|
+
if (spec.kind === "boolean") {
|
|
36823
|
+
if (inlineValue !== void 0) throw new UsageError(`${display} does not take a value`);
|
|
36824
|
+
const next = argv[i2 + 1]?.trim().toLowerCase();
|
|
36825
|
+
if (next === "true" || next === "false") {
|
|
36826
|
+
throw new UsageError(
|
|
36827
|
+
`${display} does not take a value \u2014 it is a switch; omit it to leave the option off`
|
|
36828
|
+
);
|
|
36829
|
+
}
|
|
36830
|
+
options[name] = true;
|
|
36771
36831
|
continue;
|
|
36772
36832
|
}
|
|
36773
|
-
|
|
36774
|
-
|
|
36775
|
-
|
|
36776
|
-
|
|
36777
|
-
|
|
36778
|
-
|
|
36779
|
-
|
|
36780
|
-
|
|
36781
|
-
|
|
36782
|
-
|
|
36783
|
-
});
|
|
36784
|
-
|
|
36785
|
-
|
|
36786
|
-
});
|
|
36787
|
-
}
|
|
36788
|
-
function printToolHelp(meta3) {
|
|
36789
|
-
const description = meta3.description?.trim() ?? "";
|
|
36790
|
-
const schema = meta3.inputSchema;
|
|
36791
|
-
const hasOwnArgsField = schema?.properties?.args !== void 0;
|
|
36792
|
-
console.log(`argent run ${meta3.name} [flags]`);
|
|
36793
|
-
if (description) console.log(`
|
|
36794
|
-
${description}
|
|
36795
|
-
`);
|
|
36796
|
-
console.log("Flags:");
|
|
36797
|
-
console.log(formatSchemaUsage(schema));
|
|
36798
|
-
console.log("\nGlobal flags:");
|
|
36799
|
-
if (!hasOwnArgsField) {
|
|
36800
|
-
console.log(" --args <json> Pass the entire payload as JSON (overrides flags)");
|
|
36801
|
-
console.log(" --args - Read the entire payload as JSON from stdin");
|
|
36802
|
-
}
|
|
36803
|
-
console.log(" --<field>-json <json> Pass a single field as JSON (objects/nested arrays)");
|
|
36804
|
-
console.log(" --json Print the raw JSON result");
|
|
36805
|
-
console.log(" --out <path> For image results, save to <path> instead of fetching URL");
|
|
36806
|
-
console.log(" --help, -h Show this help");
|
|
36807
|
-
}
|
|
36808
|
-
async function fetchImageToFile(result, outPath) {
|
|
36809
|
-
const url2 = result.url;
|
|
36810
|
-
if (!url2) {
|
|
36811
|
-
throw new Error("Tool result did not include a `url`; cannot save image");
|
|
36812
|
-
}
|
|
36813
|
-
const res = await fetch(url2);
|
|
36814
|
-
if (!res.ok) throw new Error(`Failed to download image: ${res.status} ${res.statusText}`);
|
|
36815
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
36816
|
-
fs12.mkdirSync(path17.dirname(path17.resolve(outPath)), { recursive: true });
|
|
36817
|
-
fs12.writeFileSync(outPath, buf);
|
|
36818
|
-
}
|
|
36819
|
-
function renderResult(result, outputHint, images, json2) {
|
|
36820
|
-
if (json2) return JSON.stringify(result, null, 2);
|
|
36821
|
-
if (outputHint === "image") {
|
|
36822
|
-
if (images.length > 0) return `Saved screenshot: ${images[0].localPath}`;
|
|
36823
|
-
if (result && typeof result === "object" && "path" in result && typeof result.path === "string") {
|
|
36824
|
-
return `Saved screenshot: ${result.path}`;
|
|
36833
|
+
let value = inlineValue;
|
|
36834
|
+
if (value === void 0) {
|
|
36835
|
+
const next = argv[i2 + 1];
|
|
36836
|
+
if (next !== void 0 && (!next.startsWith("-") || next === "-")) {
|
|
36837
|
+
value = next;
|
|
36838
|
+
i2 += 1;
|
|
36839
|
+
}
|
|
36840
|
+
} else if (value === "") {
|
|
36841
|
+
value = void 0;
|
|
36842
|
+
}
|
|
36843
|
+
if (value === void 0) throw new UsageError(`${display} requires a value`);
|
|
36844
|
+
if (spec.choices && !spec.choices.includes(value)) {
|
|
36845
|
+
throw new UsageError(`${display} must be ${listChoices(spec.choices)}, got "${value}"`);
|
|
36825
36846
|
}
|
|
36847
|
+
options[name] = value;
|
|
36826
36848
|
}
|
|
36827
|
-
|
|
36828
|
-
return JSON.stringify(result, null, 2);
|
|
36849
|
+
return { positionals, options };
|
|
36829
36850
|
}
|
|
36830
|
-
|
|
36831
|
-
|
|
36832
|
-
|
|
36851
|
+
|
|
36852
|
+
// ../argent-cli/src/e2e.ts
|
|
36853
|
+
var STATUS_GLYPH = {
|
|
36854
|
+
pass: "\u2713",
|
|
36855
|
+
fail: "\u2717",
|
|
36856
|
+
error: "\u2717",
|
|
36857
|
+
skip: "\xB7"
|
|
36858
|
+
};
|
|
36859
|
+
var MAX_RENDER_DEPTH = 20;
|
|
36860
|
+
function stepIndent(depth) {
|
|
36861
|
+
if (typeof depth !== "number" || !Number.isInteger(depth) || depth <= 0) return "";
|
|
36862
|
+
return " ".repeat(Math.min(depth, MAX_RENDER_DEPTH));
|
|
36833
36863
|
}
|
|
36834
|
-
|
|
36835
|
-
|
|
36836
|
-
|
|
36837
|
-
|
|
36838
|
-
|
|
36839
|
-
|
|
36840
|
-
...signal
|
|
36841
|
-
});
|
|
36842
|
-
await shutdown();
|
|
36864
|
+
function durationSuffix(ms) {
|
|
36865
|
+
if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) return "";
|
|
36866
|
+
const tenths = Math.round(ms / 100);
|
|
36867
|
+
if (tenths < 600) return ` (${(tenths / 10).toFixed(1)}s)`;
|
|
36868
|
+
const seconds = Math.round(ms / 1e3);
|
|
36869
|
+
return ` (${Math.floor(seconds / 60)}m ${seconds % 60}s)`;
|
|
36843
36870
|
}
|
|
36844
|
-
|
|
36845
|
-
|
|
36846
|
-
|
|
36847
|
-
const { fetchTool, callTool, baseUrl } = createToolsClient({ paths: options.paths });
|
|
36848
|
-
const [toolName, ...rest] = argv;
|
|
36849
|
-
if (!toolName || toolName === "--help" || toolName === "-h") {
|
|
36850
|
-
console.log(`Usage: argent run <tool> [flags]
|
|
36871
|
+
function printHelp(toStderr = false) {
|
|
36872
|
+
const print = toStderr ? console.error : console.log;
|
|
36873
|
+
print(`Usage: argent e2e <subcommand> [options]
|
|
36851
36874
|
|
|
36852
|
-
|
|
36853
|
-
available tools, or \`argent tools describe <name>\` to see one tool's flags.
|
|
36854
|
-
|
|
36855
|
-
Examples:
|
|
36856
|
-
argent run list-devices
|
|
36857
|
-
argent run gesture-tap --udid <UDID> --x 0.5 --y 0.5
|
|
36858
|
-
argent run screenshot --udid <UDID> --out ./screen.png
|
|
36859
|
-
argent run run-sequence --udid <UDID> --steps-json '[{"tool":"button","args":{"button":"home"}}]'
|
|
36860
|
-
argent run gesture-tap --args '{"udid":"<UDID>","x":0.5,"y":0.5}'
|
|
36861
|
-
`);
|
|
36862
|
-
return;
|
|
36863
|
-
}
|
|
36864
|
-
let cliOptions;
|
|
36865
|
-
try {
|
|
36866
|
-
cliOptions = splitOptions(rest);
|
|
36867
|
-
} catch (err) {
|
|
36868
|
-
if (err instanceof FlagParseException) {
|
|
36869
|
-
console.error(`Error: ${err.message}
|
|
36870
|
-
`);
|
|
36871
|
-
console.error(`Run \`argent run ${toolName} --help\` to see this tool's flags.`);
|
|
36872
|
-
await trackRunFailure(toolName, startedAt, {
|
|
36873
|
-
error_code: FAILURE_CODES.CLI_RUN_FLAG_PARSE_FAILED,
|
|
36874
|
-
failure_stage: "cli_run_split_options",
|
|
36875
|
-
failure_area: "cli",
|
|
36876
|
-
error_kind: "validation"
|
|
36877
|
-
});
|
|
36878
|
-
process.exit(2);
|
|
36879
|
-
}
|
|
36880
|
-
throw err;
|
|
36881
|
-
}
|
|
36882
|
-
const { json: json2, outPath, argvForFlags } = cliOptions;
|
|
36883
|
-
const meta3 = await fetchTool(toolName);
|
|
36884
|
-
if (!meta3) {
|
|
36885
|
-
console.error(`Tool "${toolName}" not found. Run \`argent tools\` to list available tools.`);
|
|
36886
|
-
await trackRunFailure(toolName, startedAt, {
|
|
36887
|
-
error_code: FAILURE_CODES.CLI_RUN_TOOL_NOT_FOUND,
|
|
36888
|
-
failure_stage: "cli_run_fetch_tool",
|
|
36889
|
-
failure_area: "cli",
|
|
36890
|
-
error_kind: "not_found"
|
|
36891
|
-
});
|
|
36892
|
-
process.exit(1);
|
|
36893
|
-
}
|
|
36894
|
-
const schema = meta3.inputSchema;
|
|
36895
|
-
const failInvocation = async (summary, report, failure2) => {
|
|
36896
|
-
if (json2) {
|
|
36897
|
-
console.error(
|
|
36898
|
-
JSON.stringify(
|
|
36899
|
-
{
|
|
36900
|
-
error: summary,
|
|
36901
|
-
missing: report ? missingFlagNames(report, schema) : [],
|
|
36902
|
-
issues: report?.rawIssues ?? []
|
|
36903
|
-
},
|
|
36904
|
-
null,
|
|
36905
|
-
2
|
|
36906
|
-
)
|
|
36907
|
-
);
|
|
36908
|
-
} else {
|
|
36909
|
-
console.error(`Error: ${summary}
|
|
36910
|
-
`);
|
|
36911
|
-
printToolHelp(meta3);
|
|
36912
|
-
}
|
|
36913
|
-
await trackRunFailure(toolName, startedAt, {
|
|
36914
|
-
...failure2,
|
|
36915
|
-
failure_area: "cli",
|
|
36916
|
-
error_kind: "validation"
|
|
36917
|
-
});
|
|
36918
|
-
process.exit(2);
|
|
36919
|
-
};
|
|
36920
|
-
const failValidation = (report, stage) => failInvocation(formatValidationError(report, schema), report, {
|
|
36921
|
-
error_code: FAILURE_CODES.CLI_RUN_INPUT_VALIDATION_FAILED,
|
|
36922
|
-
failure_stage: stage
|
|
36923
|
-
});
|
|
36924
|
-
let parsed;
|
|
36925
|
-
try {
|
|
36926
|
-
parsed = parseFlags(argvForFlags, schema);
|
|
36927
|
-
} catch (err) {
|
|
36928
|
-
if (err instanceof FlagParseException) {
|
|
36929
|
-
await failInvocation(err.message, null, {
|
|
36930
|
-
error_code: FAILURE_CODES.CLI_RUN_FLAG_PARSE_FAILED,
|
|
36931
|
-
failure_stage: "cli_run_parse_flags"
|
|
36932
|
-
});
|
|
36933
|
-
}
|
|
36934
|
-
throw err;
|
|
36935
|
-
}
|
|
36936
|
-
if (parsed.helpRequested) {
|
|
36937
|
-
printToolHelp(meta3);
|
|
36938
|
-
return;
|
|
36939
|
-
}
|
|
36940
|
-
if (parsed.positional.length > 0) {
|
|
36941
|
-
console.error(
|
|
36942
|
-
`Note: ignoring unused argument(s): ${parsed.positional.join(", ")}. Pass values as --flag <value> or --flag=<value>.`
|
|
36943
|
-
);
|
|
36944
|
-
}
|
|
36945
|
-
let payload = {};
|
|
36946
|
-
if (parsed.rawArgs !== null) {
|
|
36947
|
-
let rawJson = parsed.rawArgs;
|
|
36948
|
-
if (rawJson === "-") {
|
|
36949
|
-
rawJson = await readStdin();
|
|
36950
|
-
}
|
|
36951
|
-
try {
|
|
36952
|
-
const parsedRaw = JSON.parse(rawJson);
|
|
36953
|
-
if (parsedRaw === null || typeof parsedRaw !== "object" || Array.isArray(parsedRaw)) {
|
|
36954
|
-
console.error("--args must be a JSON object");
|
|
36955
|
-
await trackRunFailure(toolName, startedAt, {
|
|
36956
|
-
error_code: FAILURE_CODES.CLI_RUN_ARGS_NOT_OBJECT,
|
|
36957
|
-
failure_stage: "cli_run_parse_raw_args",
|
|
36958
|
-
failure_area: "cli",
|
|
36959
|
-
error_kind: "validation"
|
|
36960
|
-
});
|
|
36961
|
-
process.exit(2);
|
|
36962
|
-
}
|
|
36963
|
-
payload = parsedRaw;
|
|
36964
|
-
} catch (err) {
|
|
36965
|
-
console.error(`--args is not valid JSON: ${err instanceof Error ? err.message : err}`);
|
|
36966
|
-
await trackRunFailure(toolName, startedAt, {
|
|
36967
|
-
error_code: FAILURE_CODES.CLI_RUN_ARGS_JSON_INVALID,
|
|
36968
|
-
failure_stage: "cli_run_parse_raw_args",
|
|
36969
|
-
failure_area: "cli",
|
|
36970
|
-
error_kind: "validation"
|
|
36971
|
-
});
|
|
36972
|
-
process.exit(2);
|
|
36973
|
-
}
|
|
36974
|
-
}
|
|
36975
|
-
for (const [k, v] of Object.entries(parsed.args)) {
|
|
36976
|
-
payload[k] = v;
|
|
36977
|
-
}
|
|
36978
|
-
const missing = findMissingRequired(payload, schema);
|
|
36979
|
-
if (missing.length > 0) {
|
|
36980
|
-
await failValidation({ missing, invalid: [], rawIssues: null }, "cli_run_required_flags");
|
|
36981
|
-
}
|
|
36982
|
-
let result;
|
|
36983
|
-
let note;
|
|
36984
|
-
let images = [];
|
|
36985
|
-
try {
|
|
36986
|
-
const resp = await callTool(toolName, payload);
|
|
36987
|
-
const { url: url2, token } = await baseUrl();
|
|
36988
|
-
const materialized = await materializeArtifacts(resp.data, {
|
|
36989
|
-
toolsUrl: url2,
|
|
36990
|
-
authToken: token,
|
|
36991
|
-
deviceId: getDeviceIdFromArgs(payload)
|
|
36992
|
-
});
|
|
36993
|
-
result = materialized.result;
|
|
36994
|
-
images = materialized.images;
|
|
36995
|
-
note = resp.note;
|
|
36996
|
-
} catch (err) {
|
|
36997
|
-
const report = describeServerValidationFailure(err, payload, schema);
|
|
36998
|
-
if (report) {
|
|
36999
|
-
await failValidation(report, "cli_run_server_validation");
|
|
37000
|
-
}
|
|
37001
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
37002
|
-
await trackRunFailure(toolName, startedAt, {
|
|
37003
|
-
error_code: FAILURE_CODES.CLI_RUN_TOOL_CALL_FAILED,
|
|
37004
|
-
failure_stage: "cli_run_call_tool",
|
|
37005
|
-
failure_area: "cli",
|
|
37006
|
-
error_kind: "unknown"
|
|
37007
|
-
});
|
|
37008
|
-
process.exit(1);
|
|
37009
|
-
}
|
|
37010
|
-
if (outPath && meta3.outputHint === "image") {
|
|
37011
|
-
try {
|
|
37012
|
-
if (images.length > 0) {
|
|
37013
|
-
fs12.mkdirSync(path17.dirname(path17.resolve(outPath)), { recursive: true });
|
|
37014
|
-
fs12.writeFileSync(outPath, images[0].data);
|
|
37015
|
-
} else if (result && typeof result === "object") {
|
|
37016
|
-
await fetchImageToFile(result, outPath);
|
|
37017
|
-
}
|
|
37018
|
-
} catch (err) {
|
|
37019
|
-
console.error(`Failed to save image: ${err instanceof Error ? err.message : err}`);
|
|
37020
|
-
await trackRunFailure(toolName, startedAt, {
|
|
37021
|
-
error_code: FAILURE_CODES.CLI_RUN_SAVE_IMAGE_FAILED,
|
|
37022
|
-
failure_stage: "cli_run_save_image",
|
|
37023
|
-
failure_area: "cli",
|
|
37024
|
-
error_kind: "unknown"
|
|
37025
|
-
});
|
|
37026
|
-
process.exit(1);
|
|
37027
|
-
}
|
|
37028
|
-
}
|
|
37029
|
-
if (note) console.error(note);
|
|
37030
|
-
console.log(renderResult(result, meta3.outputHint, images, json2));
|
|
37031
|
-
if (outPath && meta3.outputHint === "image" && !json2) {
|
|
37032
|
-
console.log(`Wrote: ${outPath}`);
|
|
37033
|
-
}
|
|
37034
|
-
}
|
|
37035
|
-
|
|
37036
|
-
// ../argent-cli/src/e2e.ts
|
|
37037
|
-
import * as fsp from "node:fs/promises";
|
|
37038
|
-
import { constants as fsConstants2 } from "node:fs";
|
|
37039
|
-
import { createHash as createHash5 } from "node:crypto";
|
|
37040
|
-
import * as path18 from "node:path";
|
|
37041
|
-
|
|
37042
|
-
// ../argent-cli/src/command-args.ts
|
|
37043
|
-
var UsageError = class extends Error {
|
|
37044
|
-
constructor(message) {
|
|
37045
|
-
super(message);
|
|
37046
|
-
this.name = "UsageError";
|
|
37047
|
-
}
|
|
37048
|
-
};
|
|
37049
|
-
function listChoices(choices) {
|
|
37050
|
-
const quoted = choices.map((c2) => `"${c2}"`);
|
|
37051
|
-
if (quoted.length <= 1) return quoted.join("");
|
|
37052
|
-
return `${quoted.slice(0, -1).join(", ")} or ${quoted[quoted.length - 1]}`;
|
|
37053
|
-
}
|
|
37054
|
-
function resolveName(tok, specs) {
|
|
37055
|
-
if (tok.startsWith("--")) return tok.slice(2) in specs ? tok.slice(2) : null;
|
|
37056
|
-
const short = tok.slice(1);
|
|
37057
|
-
for (const [name, spec] of Object.entries(specs)) {
|
|
37058
|
-
if (spec.alias === short) return name;
|
|
37059
|
-
}
|
|
37060
|
-
return null;
|
|
37061
|
-
}
|
|
37062
|
-
function parseCommandArgs(argv, specs) {
|
|
37063
|
-
const positionals = [];
|
|
37064
|
-
const options = {};
|
|
37065
|
-
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
37066
|
-
const tok = argv[i2];
|
|
37067
|
-
if (tok === "--") {
|
|
37068
|
-
positionals.push(...argv.slice(i2 + 1));
|
|
37069
|
-
break;
|
|
37070
|
-
}
|
|
37071
|
-
if (!tok.startsWith("-") || tok === "-") {
|
|
37072
|
-
positionals.push(tok);
|
|
37073
|
-
continue;
|
|
37074
|
-
}
|
|
37075
|
-
const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
|
|
37076
|
-
const flag = eq === -1 ? tok : tok.slice(0, eq);
|
|
37077
|
-
const inlineValue = eq === -1 ? void 0 : tok.slice(eq + 1);
|
|
37078
|
-
const name = resolveName(flag, specs);
|
|
37079
|
-
if (name === null) throw new UsageError(`Unknown flag: ${tok}`);
|
|
37080
|
-
const spec = specs[name];
|
|
37081
|
-
const display = `--${name}`;
|
|
37082
|
-
if (spec.kind === "boolean") {
|
|
37083
|
-
if (inlineValue !== void 0) throw new UsageError(`${display} does not take a value`);
|
|
37084
|
-
const next = argv[i2 + 1]?.trim().toLowerCase();
|
|
37085
|
-
if (next === "true" || next === "false") {
|
|
37086
|
-
throw new UsageError(
|
|
37087
|
-
`${display} does not take a value \u2014 it is a switch; omit it to leave the option off`
|
|
37088
|
-
);
|
|
37089
|
-
}
|
|
37090
|
-
options[name] = true;
|
|
37091
|
-
continue;
|
|
37092
|
-
}
|
|
37093
|
-
let value = inlineValue;
|
|
37094
|
-
if (value === void 0) {
|
|
37095
|
-
const next = argv[i2 + 1];
|
|
37096
|
-
if (next !== void 0 && (!next.startsWith("-") || next === "-")) {
|
|
37097
|
-
value = next;
|
|
37098
|
-
i2 += 1;
|
|
37099
|
-
}
|
|
37100
|
-
} else if (value === "") {
|
|
37101
|
-
value = void 0;
|
|
37102
|
-
}
|
|
37103
|
-
if (value === void 0) throw new UsageError(`${display} requires a value`);
|
|
37104
|
-
if (spec.choices && !spec.choices.includes(value)) {
|
|
37105
|
-
throw new UsageError(`${display} must be ${listChoices(spec.choices)}, got "${value}"`);
|
|
37106
|
-
}
|
|
37107
|
-
options[name] = value;
|
|
37108
|
-
}
|
|
37109
|
-
return { positionals, options };
|
|
37110
|
-
}
|
|
37111
|
-
|
|
37112
|
-
// ../argent-cli/src/e2e.ts
|
|
37113
|
-
var STATUS_GLYPH = {
|
|
37114
|
-
pass: "\u2713",
|
|
37115
|
-
fail: "\u2717",
|
|
37116
|
-
error: "\u2717",
|
|
37117
|
-
skip: "\xB7"
|
|
37118
|
-
};
|
|
37119
|
-
var MAX_RENDER_DEPTH = 20;
|
|
37120
|
-
function stepIndent(depth) {
|
|
37121
|
-
if (typeof depth !== "number" || !Number.isInteger(depth) || depth <= 0) return "";
|
|
37122
|
-
return " ".repeat(Math.min(depth, MAX_RENDER_DEPTH));
|
|
37123
|
-
}
|
|
37124
|
-
function durationSuffix(ms) {
|
|
37125
|
-
if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) return "";
|
|
37126
|
-
const tenths = Math.round(ms / 100);
|
|
37127
|
-
if (tenths < 600) return ` (${(tenths / 10).toFixed(1)}s)`;
|
|
37128
|
-
const seconds = Math.round(ms / 1e3);
|
|
37129
|
-
return ` (${Math.floor(seconds / 60)}m ${seconds % 60}s)`;
|
|
37130
|
-
}
|
|
37131
|
-
function printHelp(toStderr = false) {
|
|
37132
|
-
const print = toStderr ? console.error : console.log;
|
|
37133
|
-
print(`Usage: argent e2e <subcommand> [options]
|
|
37134
|
-
|
|
37135
|
-
Run a YAML e2e test without an LLM in the loop. \`run\` takes any of these forms:
|
|
36875
|
+
Run a YAML e2e test without an LLM in the loop. \`run\` takes any of these forms:
|
|
37136
36876
|
|
|
37137
36877
|
a name An e2e test saved under .argent/e2e or .argent/flows, resolved
|
|
37138
36878
|
from the current directory. "checkout" runs
|
|
@@ -37150,8 +36890,9 @@ contain only letters, numbers, "_", or "-" \u2014 the same charset a name must
|
|
|
37150
36890
|
match. A test is standalone when its first non-\`echo\`/\`script\` step is \`launch\`.
|
|
37151
36891
|
Other tests are fragments and use the device's current state. Exception: a fragment whose first step \`run:\`s a chromium standalone
|
|
37152
36892
|
test boots that test's app before step 1 \u2014 when that launch is unambiguously
|
|
37153
|
-
chromium (a lone \`{ chromium: ... }\` target,
|
|
37154
|
-
|
|
36893
|
+
chromium (a lone \`{ chromium: ... }\` target, --platform chromium, or a
|
|
36894
|
+
\`requires:\` block that allows only chromium); a multi-platform launch
|
|
36895
|
+
auto-detects a device instead. Pass --device to attach to
|
|
37155
36896
|
a running instance.
|
|
37156
36897
|
|
|
37157
36898
|
A directory run prints only the steps that need attention (each failure, each
|
|
@@ -37164,6 +36905,12 @@ file, or a device it cannot resolve. A transport failure, a rejection the server
|
|
|
37164
36905
|
does not mark as validation, or a reply that is not a report stops the batch and
|
|
37165
36906
|
counts the remaining tests skipped.
|
|
37166
36907
|
|
|
36908
|
+
A test can declare a \`requires:\` block (platform, runtimeKind) that names the
|
|
36909
|
+
targets it supports. A directory run skips each test whose requirements the
|
|
36910
|
+
target does not meet, so the same directory runs on each platform without a
|
|
36911
|
+
separate list of tests. A single-test run fails instead. A directory run where
|
|
36912
|
+
no test ran exits 2.
|
|
36913
|
+
|
|
37167
36914
|
The CLI sends a run to its tool-server: the local one that starts
|
|
37168
36915
|
automatically, or the one that \`argent link\` or ARGENT_TOOLS_URL names. Over
|
|
37169
36916
|
a link, the CLI uploads these files and writes the new baselines that the run
|
|
@@ -37346,7 +37093,8 @@ function renderFailedSteps(report) {
|
|
|
37346
37093
|
return lines;
|
|
37347
37094
|
}
|
|
37348
37095
|
function renderBatchSummary(counts, durationMs) {
|
|
37349
|
-
|
|
37096
|
+
const verdict = counts.failed > 0 ? "FAIL" : counts.passed === 0 ? "NONE RAN" : "PASS";
|
|
37097
|
+
return `${verdict} \u2014 ${counts.total} e2e test${counts.total === 1 ? "" : "s"}: ${counts.passed} passed, ${counts.failed} failed, ${counts.skipped} skipped${durationSuffix(durationMs)}`;
|
|
37350
37098
|
}
|
|
37351
37099
|
function summarizeFailure(report) {
|
|
37352
37100
|
let n3 = 0;
|
|
@@ -37385,14 +37133,14 @@ function shellQuoteArg(arg) {
|
|
|
37385
37133
|
return `'${arg.replaceAll("'", "'\\''")}'`;
|
|
37386
37134
|
}
|
|
37387
37135
|
function rerunCommand(flowPath, rel, projectRoot, args) {
|
|
37388
|
-
const pathArg = (p) => shellQuoteArg(p.startsWith("-") ? `.${
|
|
37389
|
-
const fromCwd =
|
|
37390
|
-
const shown =
|
|
37136
|
+
const pathArg = (p) => shellQuoteArg(p.startsWith("-") ? `.${path17.sep}${p}` : p);
|
|
37137
|
+
const fromCwd = path17.relative(projectRoot, flowPath);
|
|
37138
|
+
const shown = path17.isAbsolute(fromCwd) || fromCwd.split(/[\\/]+/).includes("..") ? flowPath : fromCwd;
|
|
37391
37139
|
const parts = ["argent e2e run", pathArg(shown)];
|
|
37392
37140
|
if (args.device) parts.push("--device", shellQuoteArg(args.device));
|
|
37393
37141
|
if (args.platform) parts.push("--platform", shellQuoteArg(args.platform));
|
|
37394
37142
|
if (args.updateBaselines) parts.push("--update-baselines");
|
|
37395
|
-
if (args.output) parts.push("--output", pathArg(
|
|
37143
|
+
if (args.output) parts.push("--output", pathArg(path17.join(args.output, path17.dirname(rel))));
|
|
37396
37144
|
return parts.join(" ");
|
|
37397
37145
|
}
|
|
37398
37146
|
var EXPORT_SOURCE_MARKER = ".argent-e2e-source";
|
|
@@ -37400,8 +37148,8 @@ var LEGACY_EXPORT_SOURCE_MARKER = ".argent-flow-source";
|
|
|
37400
37148
|
var MARKER_READ_RETRIES = 4;
|
|
37401
37149
|
var MARKER_READ_DELAY_MS = 5;
|
|
37402
37150
|
async function readExportMarker(dir) {
|
|
37403
|
-
const owner = await readMarkerFile(
|
|
37404
|
-
return owner !== void 0 ? owner : await readMarkerFile(
|
|
37151
|
+
const owner = await readMarkerFile(path17.join(dir, EXPORT_SOURCE_MARKER));
|
|
37152
|
+
return owner !== void 0 ? owner : await readMarkerFile(path17.join(dir, LEGACY_EXPORT_SOURCE_MARKER));
|
|
37405
37153
|
}
|
|
37406
37154
|
async function readMarkerFile(marker) {
|
|
37407
37155
|
for (let attempt = 0; ; attempt++) {
|
|
@@ -37438,7 +37186,7 @@ function occupiedBy(owner) {
|
|
|
37438
37186
|
async function takeExportDir(dir, flowPath) {
|
|
37439
37187
|
try {
|
|
37440
37188
|
await fsp.mkdir(dir, { recursive: true });
|
|
37441
|
-
await fsp.writeFile(
|
|
37189
|
+
await fsp.writeFile(path17.join(dir, EXPORT_SOURCE_MARKER), `${flowPath}
|
|
37442
37190
|
`, { flag: "wx" });
|
|
37443
37191
|
return null;
|
|
37444
37192
|
} catch (err) {
|
|
@@ -37457,7 +37205,7 @@ async function claimExportDirName(outputDir, flowPath, stem) {
|
|
|
37457
37205
|
}
|
|
37458
37206
|
const avoided = [];
|
|
37459
37207
|
for (const name of candidates) {
|
|
37460
|
-
const dir =
|
|
37208
|
+
const dir = path17.join(outputDir, name);
|
|
37461
37209
|
const claim2 = await classifyExportDir(dir, flowPath);
|
|
37462
37210
|
const reason = claim2.state === "foreign" ? occupiedBy(claim2.owner) : await takeExportDir(dir, flowPath);
|
|
37463
37211
|
if (reason !== null) {
|
|
@@ -37477,7 +37225,7 @@ async function claimExportDirName(outputDir, flowPath, stem) {
|
|
|
37477
37225
|
return null;
|
|
37478
37226
|
}
|
|
37479
37227
|
async function exportFailureArtifacts(report, outputDir, flowPath, ctx) {
|
|
37480
|
-
const stem =
|
|
37228
|
+
const stem = path17.basename(flowPath, ".yaml");
|
|
37481
37229
|
if (!SAFE_ARTIFACT_NAME.test(stem)) {
|
|
37482
37230
|
console.error(
|
|
37483
37231
|
`warning: skipping artifact export for unsafe e2e test filename ${JSON.stringify(stem)}`
|
|
@@ -37510,11 +37258,11 @@ async function exportFailureArtifacts(report, outputDir, flowPath, ctx) {
|
|
|
37510
37258
|
if (dir === null) {
|
|
37511
37259
|
const dirName = await claimExportDirName(outputDir, flowPath, stem);
|
|
37512
37260
|
if (dirName === null) return;
|
|
37513
|
-
dir =
|
|
37261
|
+
dir = path17.join(outputDir, dirName);
|
|
37514
37262
|
}
|
|
37515
|
-
const dest =
|
|
37516
|
-
const rel =
|
|
37517
|
-
if (rel.startsWith("..") ||
|
|
37263
|
+
const dest = path17.join(dir, fileName(role));
|
|
37264
|
+
const rel = path17.relative(outputDir, dest);
|
|
37265
|
+
if (rel.startsWith("..") || path17.isAbsolute(rel)) continue;
|
|
37518
37266
|
try {
|
|
37519
37267
|
await fsp.copyFile(value, dest);
|
|
37520
37268
|
s.artifacts[role] = dest;
|
|
@@ -37529,7 +37277,7 @@ async function exportFailureArtifacts(report, outputDir, flowPath, ctx) {
|
|
|
37529
37277
|
function keyFromBaselinePath(artifacts) {
|
|
37530
37278
|
const baseline = artifacts.baseline;
|
|
37531
37279
|
if (typeof baseline !== "string") return null;
|
|
37532
|
-
return
|
|
37280
|
+
return path17.basename(baseline).replace(/\.png$/, "");
|
|
37533
37281
|
}
|
|
37534
37282
|
function resolveArtifactDisplayPaths(report) {
|
|
37535
37283
|
for (const s of report.steps) {
|
|
@@ -37586,17 +37334,17 @@ async function collectRunnableFlowPaths(dir, relDir = "") {
|
|
|
37586
37334
|
const found = [];
|
|
37587
37335
|
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
37588
37336
|
for (const entry of entries) {
|
|
37589
|
-
const rel = relDir ?
|
|
37337
|
+
const rel = relDir ? path17.join(relDir, entry.name) : entry.name;
|
|
37590
37338
|
if (entry.isDirectory()) {
|
|
37591
37339
|
if (entry.name === "__baselines__") continue;
|
|
37592
37340
|
found.push(
|
|
37593
|
-
...await collectRunnableFlowPaths(
|
|
37341
|
+
...await collectRunnableFlowPaths(path17.join(dir, entry.name), rel).catch(() => [])
|
|
37594
37342
|
);
|
|
37595
37343
|
continue;
|
|
37596
37344
|
}
|
|
37597
37345
|
if (!entry.name.endsWith(".yaml")) continue;
|
|
37598
|
-
if (!SAFE_FLOW_NAME.test(
|
|
37599
|
-
if (await isRunnableFlowFile(
|
|
37346
|
+
if (!SAFE_FLOW_NAME.test(path17.basename(entry.name, ".yaml"))) continue;
|
|
37347
|
+
if (await isRunnableFlowFile(path17.join(dir, entry.name))) found.push(rel);
|
|
37600
37348
|
}
|
|
37601
37349
|
return found;
|
|
37602
37350
|
}
|
|
@@ -37604,26 +37352,26 @@ async function resolveFlowRef(ref, projectRoot) {
|
|
|
37604
37352
|
if (!SAFE_FLOW_NAME.test(ref)) return { suppliedPath: ref, fromName: false };
|
|
37605
37353
|
const picked = await resolveE2eFile(projectRoot, ref);
|
|
37606
37354
|
return {
|
|
37607
|
-
suppliedPath:
|
|
37355
|
+
suppliedPath: path17.relative(projectRoot, picked.path),
|
|
37608
37356
|
fromName: true,
|
|
37609
|
-
...picked.shadowed === void 0 ? {} : { shadowed:
|
|
37357
|
+
...picked.shadowed === void 0 ? {} : { shadowed: path17.relative(projectRoot, picked.shadowed) }
|
|
37610
37358
|
};
|
|
37611
37359
|
}
|
|
37612
37360
|
async function savedFlowHint(projectRoot, missingPath) {
|
|
37613
|
-
const dirs = E2E_DIRS.map((rel) => ({ rel, abs:
|
|
37361
|
+
const dirs = E2E_DIRS.map((rel) => ({ rel, abs: path17.resolve(projectRoot, rel) }));
|
|
37614
37362
|
for (const { abs } of dirs) {
|
|
37615
|
-
const within =
|
|
37616
|
-
if (!within.startsWith("..") && !
|
|
37363
|
+
const within = path17.relative(abs, missingPath);
|
|
37364
|
+
if (!within.startsWith("..") && !path17.isAbsolute(within)) return "";
|
|
37617
37365
|
}
|
|
37618
|
-
const wanted = `${
|
|
37366
|
+
const wanted = `${path17.basename(missingPath, ".yaml")}.yaml`;
|
|
37619
37367
|
for (const { rel, abs } of dirs) {
|
|
37620
37368
|
const entries = await fsp.readdir(abs).catch(() => null);
|
|
37621
37369
|
if (entries === null) continue;
|
|
37622
37370
|
const actual = entries.includes(wanted) ? wanted : entries.find((name2) => name2.toLowerCase() === wanted.toLowerCase());
|
|
37623
37371
|
if (actual === void 0) continue;
|
|
37624
|
-
const name =
|
|
37372
|
+
const name = path17.basename(actual, ".yaml");
|
|
37625
37373
|
if (!SAFE_FLOW_NAME.test(name)) return "";
|
|
37626
|
-
if (!await isRunnableFlowFile(
|
|
37374
|
+
if (!await isRunnableFlowFile(path17.join(abs, actual))) return "";
|
|
37627
37375
|
return `
|
|
37628
37376
|
An e2e test named "${name}" is saved under ${rel} \u2014 did you mean: argent e2e run ${name}`;
|
|
37629
37377
|
}
|
|
@@ -37633,17 +37381,17 @@ async function collectFlowFiles(dir, recursive2) {
|
|
|
37633
37381
|
const found = [];
|
|
37634
37382
|
const walk = async (current, rel) => {
|
|
37635
37383
|
for (const entry of await fsp.readdir(current, { withFileTypes: true })) {
|
|
37636
|
-
const entryRel = rel ?
|
|
37384
|
+
const entryRel = rel ? path17.join(rel, entry.name) : entry.name;
|
|
37637
37385
|
if (entry.isDirectory()) {
|
|
37638
37386
|
if (recursive2 && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
37639
|
-
await walk(
|
|
37387
|
+
await walk(path17.join(current, entry.name), entryRel).catch(() => {
|
|
37640
37388
|
});
|
|
37641
37389
|
}
|
|
37642
37390
|
continue;
|
|
37643
37391
|
}
|
|
37644
37392
|
if (!entry.name.endsWith(".yaml")) continue;
|
|
37645
|
-
if (!SAFE_FLOW_NAME.test(
|
|
37646
|
-
if (await isRunnableFlowFile(
|
|
37393
|
+
if (!SAFE_FLOW_NAME.test(path17.basename(entry.name, ".yaml"))) continue;
|
|
37394
|
+
if (await isRunnableFlowFile(path17.join(current, entry.name))) found.push(entryRel);
|
|
37647
37395
|
}
|
|
37648
37396
|
};
|
|
37649
37397
|
await walk(dir, "");
|
|
@@ -37732,8 +37480,12 @@ function rejectionVerdict(code) {
|
|
|
37732
37480
|
case FAILURE_CODES.FLOW_ENTRY_UNRECOGNIZED:
|
|
37733
37481
|
case FAILURE_CODES.FLOW_E2E_HAS_PREREQUISITE:
|
|
37734
37482
|
return "not run (invalid e2e test)";
|
|
37483
|
+
case FAILURE_CODES.FLOW_FILE_NEWER_VERSION:
|
|
37484
|
+
return "not run (needs a newer Argent)";
|
|
37735
37485
|
case FAILURE_CODES.FLOW_DEVICE_RESOLUTION:
|
|
37736
37486
|
return "not run (no device resolved)";
|
|
37487
|
+
case FAILURE_CODES.FLOW_REQUIREMENTS_UNMET:
|
|
37488
|
+
return "not run (requires not met)";
|
|
37737
37489
|
case FAILURE_CODES.FILE_INPUT_UPLOAD_FAILED:
|
|
37738
37490
|
return "not run (upload failed)";
|
|
37739
37491
|
default:
|
|
@@ -37786,7 +37538,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
37786
37538
|
);
|
|
37787
37539
|
}
|
|
37788
37540
|
const { callTool, baseUrl } = runToolsClient(args, options);
|
|
37789
|
-
const outputBase = args.output ?
|
|
37541
|
+
const outputBase = args.output ? path17.resolve(args.output) : void 0;
|
|
37790
37542
|
const results = [];
|
|
37791
37543
|
const failures = [];
|
|
37792
37544
|
const batchStartedAt = Date.now();
|
|
@@ -37799,7 +37551,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
37799
37551
|
if (!args.json) console.log(` ${STATUS_GLYPH.skip} not run (batch stopped)`);
|
|
37800
37552
|
continue;
|
|
37801
37553
|
}
|
|
37802
|
-
const flowPath =
|
|
37554
|
+
const flowPath = path17.join(dir, rel);
|
|
37803
37555
|
const rerun = rerunCommand(flowPath, rel, projectRoot, args);
|
|
37804
37556
|
let report;
|
|
37805
37557
|
try {
|
|
@@ -37810,6 +37562,14 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
37810
37562
|
const err = unreachableErr ?? thrown;
|
|
37811
37563
|
const message = err instanceof Error ? err.message : String(err);
|
|
37812
37564
|
const toolErr = err instanceof ToolInvocationError ? err : void 0;
|
|
37565
|
+
if (toolErr?.errorCode === FAILURE_CODES.FLOW_REQUIREMENTS_UNMET) {
|
|
37566
|
+
const reason = message.replace(/^\[Tool:[^\]]+\] /, "");
|
|
37567
|
+
results.push({ path: rel, status: "skip", reason });
|
|
37568
|
+
if (!args.json)
|
|
37569
|
+
console.log(` ${STATUS_GLYPH.skip} skipped (requires not met)
|
|
37570
|
+
${reason}`);
|
|
37571
|
+
continue;
|
|
37572
|
+
}
|
|
37813
37573
|
const rejectedThisFlowOnly = toolErr?.errorKind === "validation";
|
|
37814
37574
|
const verdict = unreachableErr ? UNREACHABLE_VERDICT : rejectedThisFlowOnly ? rejectionVerdict(toolErr?.errorCode) : "did not finish (run error)";
|
|
37815
37575
|
if (!args.json) console.log(` ${STATUS_GLYPH.error} ${verdict}`);
|
|
@@ -37833,7 +37593,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
37833
37593
|
printWarnings(nestedStepWarnings(report.steps), args.json);
|
|
37834
37594
|
await exportAndResolveArtifacts(
|
|
37835
37595
|
report,
|
|
37836
|
-
outputBase ?
|
|
37596
|
+
outputBase ? path17.join(outputBase, path17.dirname(rel)) : void 0,
|
|
37837
37597
|
flowPath,
|
|
37838
37598
|
baseUrl
|
|
37839
37599
|
);
|
|
@@ -37860,11 +37620,18 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
37860
37620
|
skipped: results.filter((r2) => r2.status === "skip").length
|
|
37861
37621
|
};
|
|
37862
37622
|
const durationMs = Date.now() - batchStartedAt;
|
|
37623
|
+
const noneRan = counts.passed === 0 && counts.failed === 0;
|
|
37863
37624
|
if (args.json) {
|
|
37864
37625
|
console.log(
|
|
37865
37626
|
JSON.stringify(
|
|
37866
37627
|
// `flows` is the deprecated spelling of `e2eTests`.
|
|
37867
|
-
{
|
|
37628
|
+
{
|
|
37629
|
+
ok: counts.failed === 0 && !noneRan,
|
|
37630
|
+
...counts,
|
|
37631
|
+
durationMs,
|
|
37632
|
+
e2eTests: results,
|
|
37633
|
+
flows: results
|
|
37634
|
+
},
|
|
37868
37635
|
null,
|
|
37869
37636
|
2
|
|
37870
37637
|
)
|
|
@@ -37873,8 +37640,9 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
|
|
|
37873
37640
|
for (const line of renderFailedFlows(failures)) console.log(line);
|
|
37874
37641
|
console.log(`
|
|
37875
37642
|
${renderBatchSummary(counts, durationMs)}`);
|
|
37643
|
+
if (noneRan) console.error(`No e2e test in ${dir} ran on the selected target.`);
|
|
37876
37644
|
}
|
|
37877
|
-
return exitAfterFlush(unreachable ? 2 : counts.failed === 0 ? 0 : 1);
|
|
37645
|
+
return exitAfterFlush(unreachable || noneRan ? 2 : counts.failed === 0 ? 0 : 1);
|
|
37878
37646
|
}
|
|
37879
37647
|
async function e2e(argv, options) {
|
|
37880
37648
|
const [sub, ...rest] = argv;
|
|
@@ -37891,14 +37659,14 @@ async function e2e(argv, options) {
|
|
|
37891
37659
|
for (const dir of E2E_DIRS) {
|
|
37892
37660
|
let rels;
|
|
37893
37661
|
try {
|
|
37894
|
-
rels = (await collectRunnableFlowPaths(
|
|
37662
|
+
rels = (await collectRunnableFlowPaths(path17.join(process.cwd(), dir))).sort();
|
|
37895
37663
|
} catch {
|
|
37896
37664
|
continue;
|
|
37897
37665
|
}
|
|
37898
37666
|
anyDir = true;
|
|
37899
37667
|
for (const rel of rels) {
|
|
37900
|
-
const shown =
|
|
37901
|
-
const name =
|
|
37668
|
+
const shown = path17.join(dir, rel);
|
|
37669
|
+
const name = path17.dirname(rel) === "." ? path17.basename(rel, ".yaml") : void 0;
|
|
37902
37670
|
const usedByName = name === void 0 ? void 0 : byName.get(name);
|
|
37903
37671
|
if (name !== void 0 && usedByName === void 0) byName.set(name, shown);
|
|
37904
37672
|
lines.push(shown);
|
|
@@ -37969,10 +37737,10 @@ async function e2e(argv, options) {
|
|
|
37969
37737
|
}
|
|
37970
37738
|
if (suppliedPath.split(/[\\/]+/).includes("..")) {
|
|
37971
37739
|
let recovery = "Pass the fully resolved path to the e2e test's YAML.";
|
|
37972
|
-
const suppliedBase2 =
|
|
37740
|
+
const suppliedBase2 = path17.basename(suppliedPath);
|
|
37973
37741
|
if (suppliedBase2 !== ".." && suppliedBase2 !== ".") {
|
|
37974
37742
|
try {
|
|
37975
|
-
const resolved =
|
|
37743
|
+
const resolved = path17.join(await fsp.realpath(path17.dirname(suppliedPath)), suppliedBase2);
|
|
37976
37744
|
await fsp.stat(resolved);
|
|
37977
37745
|
recovery = `Did you mean: argent e2e run ${shellQuoteArg(resolved)}`;
|
|
37978
37746
|
} catch {
|
|
@@ -37984,7 +37752,7 @@ async function e2e(argv, options) {
|
|
|
37984
37752
|
2
|
|
37985
37753
|
);
|
|
37986
37754
|
}
|
|
37987
|
-
const resolvedPath =
|
|
37755
|
+
const resolvedPath = path17.resolve(projectRoot, suppliedPath);
|
|
37988
37756
|
let isDirectory = false;
|
|
37989
37757
|
try {
|
|
37990
37758
|
isDirectory = (await fsp.stat(resolvedPath)).isDirectory();
|
|
@@ -38007,16 +37775,16 @@ async function e2e(argv, options) {
|
|
|
38007
37775
|
}
|
|
38008
37776
|
const separatorTrimmedPath = suppliedPath.replace(/[\\/]+$/, "");
|
|
38009
37777
|
if (separatorTrimmedPath !== suppliedPath && separatorTrimmedPath !== "") {
|
|
38010
|
-
const hint = SAFE_FLOW_NAME.test(separatorTrimmedPath) ? `.${
|
|
37778
|
+
const hint = SAFE_FLOW_NAME.test(separatorTrimmedPath) ? `.${path17.sep}${separatorTrimmedPath}` : separatorTrimmedPath;
|
|
38011
37779
|
return fail(
|
|
38012
37780
|
`E2E test path must not end in a path separator \u2014 the separator claims a directory, which the kernel would refuse to open as a file, so the CLI would run a file this string does not name: ${suppliedPath}
|
|
38013
37781
|
Did you mean: argent e2e run ${shellQuoteArg(hint)}`,
|
|
38014
37782
|
2
|
|
38015
37783
|
);
|
|
38016
37784
|
}
|
|
38017
|
-
if (
|
|
38018
|
-
const looksLikeName = !suppliedPath.includes("/") && !suppliedPath.includes("\\") &&
|
|
38019
|
-
if (
|
|
37785
|
+
if (path17.extname(suppliedPath) !== ".yaml") {
|
|
37786
|
+
const looksLikeName = !suppliedPath.includes("/") && !suppliedPath.includes("\\") && path17.extname(suppliedPath) === "";
|
|
37787
|
+
if (path17.basename(suppliedPath).toLowerCase() === ".yaml") {
|
|
38020
37788
|
return fail(
|
|
38021
37789
|
`E2E test filename must have a non-empty name containing only letters, numbers, "_", or "-": ${suppliedPath}`,
|
|
38022
37790
|
2
|
|
@@ -38029,15 +37797,15 @@ Names run \`${E2E_DIRS.map((dir) => `${dir}/<name>.yaml`).join("` or `")}\`; pas
|
|
|
38029
37797
|
2
|
|
38030
37798
|
);
|
|
38031
37799
|
}
|
|
38032
|
-
if (
|
|
37800
|
+
if (path17.extname(suppliedPath).toLowerCase() === ".yaml") {
|
|
38033
37801
|
return fail(
|
|
38034
|
-
`E2E test extension must be lowercase .yaml, not ${
|
|
37802
|
+
`E2E test extension must be lowercase .yaml, not ${path17.extname(suppliedPath)}: ${suppliedPath}`,
|
|
38035
37803
|
2
|
|
38036
37804
|
);
|
|
38037
37805
|
}
|
|
38038
37806
|
return fail(`E2E test path must end in .yaml: ${suppliedPath}`, 2);
|
|
38039
37807
|
}
|
|
38040
|
-
const flowName =
|
|
37808
|
+
const flowName = path17.basename(suppliedPath, ".yaml");
|
|
38041
37809
|
if (!SAFE_FLOW_NAME.test(flowName)) {
|
|
38042
37810
|
return fail(
|
|
38043
37811
|
`E2E test filename must have a non-empty name containing only letters, numbers, "_", or "-": ${suppliedPath}`,
|
|
@@ -38061,12 +37829,12 @@ No e2e test named "${args.flowRef}" is saved in ${E2E_DIRS.join(" or ")} \u2014
|
|
|
38061
37829
|
}
|
|
38062
37830
|
return fail(`${detail}: ${flowPath}${recovery}`, 2);
|
|
38063
37831
|
}
|
|
38064
|
-
const suppliedBase =
|
|
38065
|
-
const siblings = await fsp.readdir(
|
|
37832
|
+
const suppliedBase = path17.basename(flowPath);
|
|
37833
|
+
const siblings = await fsp.readdir(path17.dirname(flowPath)).catch(() => null);
|
|
38066
37834
|
if (siblings !== null && !siblings.includes(suppliedBase)) {
|
|
38067
37835
|
const actual = siblings.find((name) => name.toLowerCase() === suppliedBase.toLowerCase());
|
|
38068
|
-
const actualRunnable = actual !== void 0 &&
|
|
38069
|
-
const recovery = actualRunnable ? fromName ? `Did you mean: argent e2e run ${
|
|
37836
|
+
const actualRunnable = actual !== void 0 && path17.extname(actual) === ".yaml" && SAFE_FLOW_NAME.test(path17.basename(actual, ".yaml"));
|
|
37837
|
+
const recovery = actualRunnable ? fromName ? `Did you mean: argent e2e run ${path17.basename(actual, ".yaml")}` : `Did you mean: argent e2e run ${shellQuoteArg(path17.join(path17.dirname(suppliedPath), actual))}` : actual !== void 0 ? `Rename ${actual} to ${suppliedBase} to run it \u2014 e2e test files must be lowercase .yaml.` : "Pass the e2e test file's name exactly as it appears on disk.";
|
|
38070
37838
|
return fail(
|
|
38071
37839
|
`E2E test path must name the file as it appears on disk \u2014 this filesystem matched ${JSON.stringify(suppliedBase)} case-insensitively${actual !== void 0 ? ` to ${JSON.stringify(actual)}` : ""}, so the test name (which keys the report, __baselines__/, and --output) would be one no file carries: ${suppliedPath}
|
|
38072
37840
|
${recovery}`,
|
|
@@ -38125,7 +37893,7 @@ ${recovery}`,
|
|
|
38125
37893
|
try {
|
|
38126
37894
|
await exportAndResolveArtifacts(
|
|
38127
37895
|
report,
|
|
38128
|
-
args.output ?
|
|
37896
|
+
args.output ? path17.resolve(args.output) : void 0,
|
|
38129
37897
|
flowPath,
|
|
38130
37898
|
baseUrl
|
|
38131
37899
|
);
|
|
@@ -38154,6 +37922,313 @@ ${renderSummary(report, { withDevice: true })}`);
|
|
|
38154
37922
|
return exitAfterFlush(report.ok && unwritten.length === 0 ? 0 : 1);
|
|
38155
37923
|
}
|
|
38156
37924
|
|
|
37925
|
+
// ../argent-cli/src/run.ts
|
|
37926
|
+
function splitOptions(argv) {
|
|
37927
|
+
let json2 = false;
|
|
37928
|
+
let outPath = null;
|
|
37929
|
+
const rest = [];
|
|
37930
|
+
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
37931
|
+
const tok = argv[i2];
|
|
37932
|
+
if (tok === "--json") {
|
|
37933
|
+
json2 = true;
|
|
37934
|
+
continue;
|
|
37935
|
+
}
|
|
37936
|
+
if (tok === "--out") {
|
|
37937
|
+
const v = argv[i2 + 1];
|
|
37938
|
+
if (!v) throw new FlagParseException("--out requires a path");
|
|
37939
|
+
outPath = v;
|
|
37940
|
+
i2 += 1;
|
|
37941
|
+
continue;
|
|
37942
|
+
}
|
|
37943
|
+
if (tok.startsWith("--out=")) {
|
|
37944
|
+
outPath = tok.slice("--out=".length);
|
|
37945
|
+
continue;
|
|
37946
|
+
}
|
|
37947
|
+
rest.push(tok);
|
|
37948
|
+
}
|
|
37949
|
+
return { json: json2, outPath, argvForFlags: rest };
|
|
37950
|
+
}
|
|
37951
|
+
async function readStdin() {
|
|
37952
|
+
return new Promise((resolve13, reject) => {
|
|
37953
|
+
let data = "";
|
|
37954
|
+
process.stdin.setEncoding("utf8");
|
|
37955
|
+
process.stdin.on("data", (chunk) => {
|
|
37956
|
+
data += chunk;
|
|
37957
|
+
});
|
|
37958
|
+
process.stdin.on("end", () => resolve13(data));
|
|
37959
|
+
process.stdin.on("error", reject);
|
|
37960
|
+
});
|
|
37961
|
+
}
|
|
37962
|
+
function printToolHelp(meta3) {
|
|
37963
|
+
const description = meta3.description?.trim() ?? "";
|
|
37964
|
+
const schema = meta3.inputSchema;
|
|
37965
|
+
const hasOwnArgsField = schema?.properties?.args !== void 0;
|
|
37966
|
+
console.log(`argent run ${meta3.name} [flags]`);
|
|
37967
|
+
if (description) console.log(`
|
|
37968
|
+
${description}
|
|
37969
|
+
`);
|
|
37970
|
+
console.log("Flags:");
|
|
37971
|
+
console.log(formatSchemaUsage(schema));
|
|
37972
|
+
console.log("\nGlobal flags:");
|
|
37973
|
+
if (!hasOwnArgsField) {
|
|
37974
|
+
console.log(" --args <json> Pass the entire payload as JSON (overrides flags)");
|
|
37975
|
+
console.log(" --args - Read the entire payload as JSON from stdin");
|
|
37976
|
+
}
|
|
37977
|
+
console.log(" --<field>-json <json> Pass a single field as JSON (objects/nested arrays)");
|
|
37978
|
+
console.log(" --json Print the raw JSON result");
|
|
37979
|
+
console.log(" --out <path> For image results, save to <path> instead of fetching URL");
|
|
37980
|
+
console.log(" --help, -h Show this help");
|
|
37981
|
+
}
|
|
37982
|
+
async function fetchImageToFile(result, outPath) {
|
|
37983
|
+
const url2 = result.url;
|
|
37984
|
+
if (!url2) {
|
|
37985
|
+
throw new Error("Tool result did not include a `url`; cannot save image");
|
|
37986
|
+
}
|
|
37987
|
+
const res = await fetch(url2);
|
|
37988
|
+
if (!res.ok) throw new Error(`Failed to download image: ${res.status} ${res.statusText}`);
|
|
37989
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
37990
|
+
fs12.mkdirSync(path18.dirname(path18.resolve(outPath)), { recursive: true });
|
|
37991
|
+
fs12.writeFileSync(outPath, buf);
|
|
37992
|
+
}
|
|
37993
|
+
function renderResult(result, outputHint, images, json2) {
|
|
37994
|
+
if (json2) return JSON.stringify(result, null, 2);
|
|
37995
|
+
if (outputHint === "image") {
|
|
37996
|
+
if (images.length > 0) return `Saved screenshot: ${images[0].localPath}`;
|
|
37997
|
+
if (result && typeof result === "object" && "path" in result && typeof result.path === "string") {
|
|
37998
|
+
return `Saved screenshot: ${result.path}`;
|
|
37999
|
+
}
|
|
38000
|
+
}
|
|
38001
|
+
if (typeof result === "string") return result;
|
|
38002
|
+
return JSON.stringify(result, null, 2);
|
|
38003
|
+
}
|
|
38004
|
+
var SAFE_TOOL_RE = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
38005
|
+
function safeToolName(toolName) {
|
|
38006
|
+
return toolName && SAFE_TOOL_RE.test(toolName) ? toolName : "unknown";
|
|
38007
|
+
}
|
|
38008
|
+
async function trackRunFailure(toolName, startedAt, signal) {
|
|
38009
|
+
const tool = toolName ? canonicalToolId(toolName) : toolName;
|
|
38010
|
+
track("cli:run_fail", {
|
|
38011
|
+
tool: safeToolName(tool),
|
|
38012
|
+
...tool !== toolName ? { alias_used: true } : {},
|
|
38013
|
+
duration_ms: performance.now() - startedAt,
|
|
38014
|
+
...signal
|
|
38015
|
+
});
|
|
38016
|
+
await shutdown();
|
|
38017
|
+
}
|
|
38018
|
+
async function run(argv, options) {
|
|
38019
|
+
init("cli");
|
|
38020
|
+
const startedAt = performance.now();
|
|
38021
|
+
const { fetchTool, callTool, baseUrl } = createToolsClient({ paths: options.paths });
|
|
38022
|
+
const [toolName, ...rest] = argv;
|
|
38023
|
+
if (!toolName || toolName === "--help" || toolName === "-h") {
|
|
38024
|
+
console.log(`Usage: argent run <tool> [flags]
|
|
38025
|
+
|
|
38026
|
+
Invoke a tool exposed by the argent tool-server. Run \`argent tools\` to list
|
|
38027
|
+
available tools, or \`argent tools describe <name>\` to see one tool's flags.
|
|
38028
|
+
|
|
38029
|
+
Examples:
|
|
38030
|
+
argent run list-devices
|
|
38031
|
+
argent run gesture-tap --udid <UDID> --x 0.5 --y 0.5
|
|
38032
|
+
argent run screenshot --udid <UDID> --out ./screen.png
|
|
38033
|
+
argent run run-sequence --udid <UDID> --steps-json '[{"tool":"button","args":{"button":"home"}}]'
|
|
38034
|
+
argent run gesture-tap --args '{"udid":"<UDID>","x":0.5,"y":0.5}'
|
|
38035
|
+
|
|
38036
|
+
Exits 1 when the tool fails. Also exits 1 when the result reports that the
|
|
38037
|
+
call did not do its job: an e2e test that does not pass, an await-ui-element
|
|
38038
|
+
wait with success=false, a run-sequence that a step stopped, a debugger tool
|
|
38039
|
+
with no connection or an error, a native-devtools precheck that stopped the
|
|
38040
|
+
tool, a native-profiler-analyze with status analysis_failed, an e2e recording
|
|
38041
|
+
call that recorded nothing, or a baseline that was not written on this client.
|
|
38042
|
+
Other results exit 0, also a negative answer such as await-screen-idle's
|
|
38043
|
+
settled=false or debugger-status's not_connected.
|
|
38044
|
+
`);
|
|
38045
|
+
return;
|
|
38046
|
+
}
|
|
38047
|
+
let cliOptions;
|
|
38048
|
+
try {
|
|
38049
|
+
cliOptions = splitOptions(rest);
|
|
38050
|
+
} catch (err) {
|
|
38051
|
+
if (err instanceof FlagParseException) {
|
|
38052
|
+
console.error(`Error: ${err.message}
|
|
38053
|
+
`);
|
|
38054
|
+
console.error(`Run \`argent run ${toolName} --help\` to see this tool's flags.`);
|
|
38055
|
+
await trackRunFailure(toolName, startedAt, {
|
|
38056
|
+
error_code: FAILURE_CODES.CLI_RUN_FLAG_PARSE_FAILED,
|
|
38057
|
+
failure_stage: "cli_run_split_options",
|
|
38058
|
+
failure_area: "cli",
|
|
38059
|
+
error_kind: "validation"
|
|
38060
|
+
});
|
|
38061
|
+
process.exit(2);
|
|
38062
|
+
}
|
|
38063
|
+
throw err;
|
|
38064
|
+
}
|
|
38065
|
+
const { json: json2, outPath, argvForFlags } = cliOptions;
|
|
38066
|
+
const meta3 = await fetchTool(toolName);
|
|
38067
|
+
if (!meta3) {
|
|
38068
|
+
console.error(`Tool "${toolName}" not found. Run \`argent tools\` to list available tools.`);
|
|
38069
|
+
await trackRunFailure(toolName, startedAt, {
|
|
38070
|
+
error_code: FAILURE_CODES.CLI_RUN_TOOL_NOT_FOUND,
|
|
38071
|
+
failure_stage: "cli_run_fetch_tool",
|
|
38072
|
+
failure_area: "cli",
|
|
38073
|
+
error_kind: "not_found"
|
|
38074
|
+
});
|
|
38075
|
+
process.exit(1);
|
|
38076
|
+
}
|
|
38077
|
+
const schema = meta3.inputSchema;
|
|
38078
|
+
const failInvocation = async (summary, report, failure3) => {
|
|
38079
|
+
if (json2) {
|
|
38080
|
+
console.error(
|
|
38081
|
+
JSON.stringify(
|
|
38082
|
+
{
|
|
38083
|
+
error: summary,
|
|
38084
|
+
missing: report ? missingFlagNames(report, schema) : [],
|
|
38085
|
+
issues: report?.rawIssues ?? []
|
|
38086
|
+
},
|
|
38087
|
+
null,
|
|
38088
|
+
2
|
|
38089
|
+
)
|
|
38090
|
+
);
|
|
38091
|
+
} else {
|
|
38092
|
+
console.error(`Error: ${summary}
|
|
38093
|
+
`);
|
|
38094
|
+
printToolHelp(meta3);
|
|
38095
|
+
}
|
|
38096
|
+
await trackRunFailure(toolName, startedAt, {
|
|
38097
|
+
...failure3,
|
|
38098
|
+
failure_area: "cli",
|
|
38099
|
+
error_kind: "validation"
|
|
38100
|
+
});
|
|
38101
|
+
process.exit(2);
|
|
38102
|
+
};
|
|
38103
|
+
const failValidation = (report, stage) => failInvocation(formatValidationError(report, schema), report, {
|
|
38104
|
+
error_code: FAILURE_CODES.CLI_RUN_INPUT_VALIDATION_FAILED,
|
|
38105
|
+
failure_stage: stage
|
|
38106
|
+
});
|
|
38107
|
+
let parsed;
|
|
38108
|
+
try {
|
|
38109
|
+
parsed = parseFlags(argvForFlags, schema);
|
|
38110
|
+
} catch (err) {
|
|
38111
|
+
if (err instanceof FlagParseException) {
|
|
38112
|
+
await failInvocation(err.message, null, {
|
|
38113
|
+
error_code: FAILURE_CODES.CLI_RUN_FLAG_PARSE_FAILED,
|
|
38114
|
+
failure_stage: "cli_run_parse_flags"
|
|
38115
|
+
});
|
|
38116
|
+
}
|
|
38117
|
+
throw err;
|
|
38118
|
+
}
|
|
38119
|
+
if (parsed.helpRequested) {
|
|
38120
|
+
printToolHelp(meta3);
|
|
38121
|
+
return;
|
|
38122
|
+
}
|
|
38123
|
+
if (parsed.positional.length > 0) {
|
|
38124
|
+
console.error(
|
|
38125
|
+
`Note: ignoring unused argument(s): ${parsed.positional.join(", ")}. Pass values as --flag <value> or --flag=<value>.`
|
|
38126
|
+
);
|
|
38127
|
+
}
|
|
38128
|
+
let payload = {};
|
|
38129
|
+
if (parsed.rawArgs !== null) {
|
|
38130
|
+
let rawJson = parsed.rawArgs;
|
|
38131
|
+
if (rawJson === "-") {
|
|
38132
|
+
rawJson = await readStdin();
|
|
38133
|
+
}
|
|
38134
|
+
try {
|
|
38135
|
+
const parsedRaw = JSON.parse(rawJson);
|
|
38136
|
+
if (parsedRaw === null || typeof parsedRaw !== "object" || Array.isArray(parsedRaw)) {
|
|
38137
|
+
console.error("--args must be a JSON object");
|
|
38138
|
+
await trackRunFailure(toolName, startedAt, {
|
|
38139
|
+
error_code: FAILURE_CODES.CLI_RUN_ARGS_NOT_OBJECT,
|
|
38140
|
+
failure_stage: "cli_run_parse_raw_args",
|
|
38141
|
+
failure_area: "cli",
|
|
38142
|
+
error_kind: "validation"
|
|
38143
|
+
});
|
|
38144
|
+
process.exit(2);
|
|
38145
|
+
}
|
|
38146
|
+
payload = parsedRaw;
|
|
38147
|
+
} catch (err) {
|
|
38148
|
+
console.error(`--args is not valid JSON: ${err instanceof Error ? err.message : err}`);
|
|
38149
|
+
await trackRunFailure(toolName, startedAt, {
|
|
38150
|
+
error_code: FAILURE_CODES.CLI_RUN_ARGS_JSON_INVALID,
|
|
38151
|
+
failure_stage: "cli_run_parse_raw_args",
|
|
38152
|
+
failure_area: "cli",
|
|
38153
|
+
error_kind: "validation"
|
|
38154
|
+
});
|
|
38155
|
+
process.exit(2);
|
|
38156
|
+
}
|
|
38157
|
+
}
|
|
38158
|
+
for (const [k, v] of Object.entries(parsed.args)) {
|
|
38159
|
+
payload[k] = v;
|
|
38160
|
+
}
|
|
38161
|
+
const missing = findMissingRequired(payload, schema);
|
|
38162
|
+
if (missing.length > 0) {
|
|
38163
|
+
await failValidation({ missing, invalid: [], rawIssues: null }, "cli_run_required_flags");
|
|
38164
|
+
}
|
|
38165
|
+
let result;
|
|
38166
|
+
let note;
|
|
38167
|
+
let failure2;
|
|
38168
|
+
let images = [];
|
|
38169
|
+
try {
|
|
38170
|
+
const resp = await callTool(toolName, payload);
|
|
38171
|
+
const { url: url2, token } = await baseUrl();
|
|
38172
|
+
const materialized = await materializeArtifacts(resp.data, {
|
|
38173
|
+
toolsUrl: url2,
|
|
38174
|
+
authToken: token,
|
|
38175
|
+
deviceId: getDeviceIdFromArgs(payload)
|
|
38176
|
+
});
|
|
38177
|
+
result = materialized.result;
|
|
38178
|
+
images = materialized.images;
|
|
38179
|
+
note = resp.note;
|
|
38180
|
+
failure2 = resp.failure;
|
|
38181
|
+
} catch (err) {
|
|
38182
|
+
const report = describeServerValidationFailure(err, payload, schema);
|
|
38183
|
+
if (report) {
|
|
38184
|
+
await failValidation(report, "cli_run_server_validation");
|
|
38185
|
+
}
|
|
38186
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
38187
|
+
await trackRunFailure(toolName, startedAt, {
|
|
38188
|
+
error_code: FAILURE_CODES.CLI_RUN_TOOL_CALL_FAILED,
|
|
38189
|
+
failure_stage: "cli_run_call_tool",
|
|
38190
|
+
failure_area: "cli",
|
|
38191
|
+
error_kind: "unknown"
|
|
38192
|
+
});
|
|
38193
|
+
process.exit(1);
|
|
38194
|
+
}
|
|
38195
|
+
if (outPath && meta3.outputHint === "image") {
|
|
38196
|
+
try {
|
|
38197
|
+
if (images.length > 0) {
|
|
38198
|
+
fs12.mkdirSync(path18.dirname(path18.resolve(outPath)), { recursive: true });
|
|
38199
|
+
fs12.writeFileSync(outPath, images[0].data);
|
|
38200
|
+
} else if (result && typeof result === "object") {
|
|
38201
|
+
await fetchImageToFile(result, outPath);
|
|
38202
|
+
}
|
|
38203
|
+
} catch (err) {
|
|
38204
|
+
console.error(`Failed to save image: ${err instanceof Error ? err.message : err}`);
|
|
38205
|
+
await trackRunFailure(toolName, startedAt, {
|
|
38206
|
+
error_code: FAILURE_CODES.CLI_RUN_SAVE_IMAGE_FAILED,
|
|
38207
|
+
failure_stage: "cli_run_save_image",
|
|
38208
|
+
failure_area: "cli",
|
|
38209
|
+
error_kind: "unknown"
|
|
38210
|
+
});
|
|
38211
|
+
process.exit(1);
|
|
38212
|
+
}
|
|
38213
|
+
}
|
|
38214
|
+
if (note) console.error(note);
|
|
38215
|
+
console.log(renderResult(result, meta3.outputHint, images, json2));
|
|
38216
|
+
if (outPath && meta3.outputHint === "image" && !json2) {
|
|
38217
|
+
console.log(`Wrote: ${outPath}`);
|
|
38218
|
+
}
|
|
38219
|
+
if (failure2) {
|
|
38220
|
+
await new Promise((resolve13) => process.stdout.write("", () => resolve13()));
|
|
38221
|
+
console.error(failure2);
|
|
38222
|
+
await trackRunFailure(toolName, startedAt, {
|
|
38223
|
+
error_code: FAILURE_CODES.CLI_RUN_TOOL_RESULT_FAILED,
|
|
38224
|
+
failure_stage: "cli_run_tool_result",
|
|
38225
|
+
failure_area: "cli",
|
|
38226
|
+
error_kind: "unknown"
|
|
38227
|
+
});
|
|
38228
|
+
await exitAfterFlush(1, [process.stderr]);
|
|
38229
|
+
}
|
|
38230
|
+
}
|
|
38231
|
+
|
|
38157
38232
|
// ../argent-cli/src/tools.ts
|
|
38158
38233
|
function summarize(description, max = 80) {
|
|
38159
38234
|
if (!description) return "";
|