@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 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 path17 from "node:path";
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 (data) => {
31873
- const applied = await applyClientFileDirectives(data, { allowedDirs: baselineDirs });
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: await settle2(streamed.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: await settle2(json2.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.0";
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/run.ts
36752
- function splitOptions(argv) {
36753
- let json2 = false;
36754
- let outPath = null;
36755
- const rest = [];
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 === "--json") {
36759
- json2 = true;
36760
- continue;
36807
+ if (tok === "--") {
36808
+ positionals.push(...argv.slice(i2 + 1));
36809
+ break;
36761
36810
  }
36762
- if (tok === "--out") {
36763
- const v = argv[i2 + 1];
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
- if (tok.startsWith("--out=")) {
36770
- outPath = tok.slice("--out=".length);
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
- rest.push(tok);
36774
- }
36775
- return { json: json2, outPath, argvForFlags: rest };
36776
- }
36777
- async function readStdin() {
36778
- return new Promise((resolve13, reject) => {
36779
- let data = "";
36780
- process.stdin.setEncoding("utf8");
36781
- process.stdin.on("data", (chunk) => {
36782
- data += chunk;
36783
- });
36784
- process.stdin.on("end", () => resolve13(data));
36785
- process.stdin.on("error", reject);
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
- if (typeof result === "string") return result;
36828
- return JSON.stringify(result, null, 2);
36849
+ return { positionals, options };
36829
36850
  }
36830
- var SAFE_TOOL_RE = /^[a-z][a-z0-9_-]{0,63}$/;
36831
- function safeToolName(toolName) {
36832
- return toolName && SAFE_TOOL_RE.test(toolName) ? toolName : "unknown";
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
- async function trackRunFailure(toolName, startedAt, signal) {
36835
- const tool = toolName ? canonicalToolId(toolName) : toolName;
36836
- track("cli:run_fail", {
36837
- tool: safeToolName(tool),
36838
- ...tool !== toolName ? { alias_used: true } : {},
36839
- duration_ms: performance.now() - startedAt,
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
- async function run(argv, options) {
36845
- init("cli");
36846
- const startedAt = performance.now();
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
- Invoke a tool exposed by the argent tool-server. Run \`argent tools\` to list
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, or --platform chromium); a
37154
- multi-platform launch auto-detects a device instead. Pass --device to attach to
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
- return `${counts.failed === 0 ? "PASS" : "FAIL"} \u2014 ${counts.total} e2e test${counts.total === 1 ? "" : "s"}: ${counts.passed} passed, ${counts.failed} failed, ${counts.skipped} skipped${durationSuffix(durationMs)}`;
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("-") ? `.${path18.sep}${p}` : p);
37389
- const fromCwd = path18.relative(projectRoot, flowPath);
37390
- const shown = path18.isAbsolute(fromCwd) || fromCwd.split(/[\\/]+/).includes("..") ? flowPath : fromCwd;
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(path18.join(args.output, path18.dirname(rel))));
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(path18.join(dir, EXPORT_SOURCE_MARKER));
37404
- return owner !== void 0 ? owner : await readMarkerFile(path18.join(dir, LEGACY_EXPORT_SOURCE_MARKER));
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(path18.join(dir, EXPORT_SOURCE_MARKER), `${flowPath}
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 = path18.join(outputDir, name);
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 = path18.basename(flowPath, ".yaml");
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 = path18.join(outputDir, dirName);
37261
+ dir = path17.join(outputDir, dirName);
37514
37262
  }
37515
- const dest = path18.join(dir, fileName(role));
37516
- const rel = path18.relative(outputDir, dest);
37517
- if (rel.startsWith("..") || path18.isAbsolute(rel)) continue;
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 path18.basename(baseline).replace(/\.png$/, "");
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 ? path18.join(relDir, entry.name) : entry.name;
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(path18.join(dir, entry.name), rel).catch(() => [])
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(path18.basename(entry.name, ".yaml"))) continue;
37599
- if (await isRunnableFlowFile(path18.join(dir, entry.name))) found.push(rel);
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: path18.relative(projectRoot, picked.path),
37355
+ suppliedPath: path17.relative(projectRoot, picked.path),
37608
37356
  fromName: true,
37609
- ...picked.shadowed === void 0 ? {} : { shadowed: path18.relative(projectRoot, picked.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: path18.resolve(projectRoot, rel) }));
37361
+ const dirs = E2E_DIRS.map((rel) => ({ rel, abs: path17.resolve(projectRoot, rel) }));
37614
37362
  for (const { abs } of dirs) {
37615
- const within = path18.relative(abs, missingPath);
37616
- if (!within.startsWith("..") && !path18.isAbsolute(within)) return "";
37363
+ const within = path17.relative(abs, missingPath);
37364
+ if (!within.startsWith("..") && !path17.isAbsolute(within)) return "";
37617
37365
  }
37618
- const wanted = `${path18.basename(missingPath, ".yaml")}.yaml`;
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 = path18.basename(actual, ".yaml");
37372
+ const name = path17.basename(actual, ".yaml");
37625
37373
  if (!SAFE_FLOW_NAME.test(name)) return "";
37626
- if (!await isRunnableFlowFile(path18.join(abs, actual))) return "";
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 ? path18.join(rel, entry.name) : entry.name;
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(path18.join(current, entry.name), entryRel).catch(() => {
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(path18.basename(entry.name, ".yaml"))) continue;
37646
- if (await isRunnableFlowFile(path18.join(current, entry.name))) found.push(entryRel);
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 ? path18.resolve(args.output) : void 0;
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 = path18.join(dir, rel);
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 ? path18.join(outputBase, path18.dirname(rel)) : void 0,
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
- { ok: counts.failed === 0, ...counts, durationMs, e2eTests: results, flows: results },
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(path18.join(process.cwd(), dir))).sort();
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 = path18.join(dir, rel);
37901
- const name = path18.dirname(rel) === "." ? path18.basename(rel, ".yaml") : void 0;
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 = path18.basename(suppliedPath);
37740
+ const suppliedBase2 = path17.basename(suppliedPath);
37973
37741
  if (suppliedBase2 !== ".." && suppliedBase2 !== ".") {
37974
37742
  try {
37975
- const resolved = path18.join(await fsp.realpath(path18.dirname(suppliedPath)), suppliedBase2);
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 = path18.resolve(projectRoot, suppliedPath);
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) ? `.${path18.sep}${separatorTrimmedPath}` : 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 (path18.extname(suppliedPath) !== ".yaml") {
38018
- const looksLikeName = !suppliedPath.includes("/") && !suppliedPath.includes("\\") && path18.extname(suppliedPath) === "";
38019
- if (path18.basename(suppliedPath).toLowerCase() === ".yaml") {
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 (path18.extname(suppliedPath).toLowerCase() === ".yaml") {
37800
+ if (path17.extname(suppliedPath).toLowerCase() === ".yaml") {
38033
37801
  return fail(
38034
- `E2E test extension must be lowercase .yaml, not ${path18.extname(suppliedPath)}: ${suppliedPath}`,
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 = path18.basename(suppliedPath, ".yaml");
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 = path18.basename(flowPath);
38065
- const siblings = await fsp.readdir(path18.dirname(flowPath)).catch(() => null);
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 && path18.extname(actual) === ".yaml" && SAFE_FLOW_NAME.test(path18.basename(actual, ".yaml"));
38069
- const recovery = actualRunnable ? fromName ? `Did you mean: argent e2e run ${path18.basename(actual, ".yaml")}` : `Did you mean: argent e2e run ${shellQuoteArg(path18.join(path18.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.";
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 ? path18.resolve(args.output) : void 0,
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 "";