@swmansion/argent 0.32.1-next.2 → 0.32.1-next.20

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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/assets/manifest.json +2 -2
  3. package/bin/argent-android-devtools-0.4.0.apk +0 -0
  4. package/bin/darwin/argent-macos-helper +0 -0
  5. package/bin/darwin/ax-service +0 -0
  6. package/bin/darwin/tvos-ax-service +0 -0
  7. package/bin/darwin/tvos-hid-daemon +0 -0
  8. package/bin/network-inspector/network-inspector.jar +0 -0
  9. package/bin/tcp/ax-service +0 -0
  10. package/dist/cli-cmds.mjs +552 -440
  11. package/dist/client.d.ts +5 -0
  12. package/dist/client.js +71 -9
  13. package/dist/installer.mjs +384 -202
  14. package/dist/mcp-server.mjs +167 -20
  15. package/dist/tool-args.generated.d.ts +91 -50
  16. package/dist/tool-server.cjs +34732 -29334
  17. package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
  18. package/dylibs/libKeyboardPatch.dylib +0 -0
  19. package/dylibs/libNativeDevtoolsIos.dylib +0 -0
  20. package/dylibs/macos/libArgentInjectionBootstrap.dylib +0 -0
  21. package/dylibs/macos/libNativeDevtoolsMacos.dylib +0 -0
  22. package/dylibs/tcp/libArgentInjectionBootstrap.dylib +0 -0
  23. package/dylibs/tcp/libKeyboardPatch.dylib +0 -0
  24. package/dylibs/tcp/libNativeDevtoolsIos.dylib +0 -0
  25. package/dylibs/tvos/libArgentInjectionBootstrap.dylib +0 -0
  26. package/dylibs/tvos/libKeyboardPatch.dylib +0 -0
  27. package/dylibs/tvos/libNativeDevtoolsIos.dylib +0 -0
  28. package/package.json +2 -2
  29. package/rules/argent.md +1 -1
  30. package/skills/argent-create-e2e/references/e2e-yaml.md +8 -4
  31. package/skills/argent-create-e2e/references/live-authoring.md +1 -0
  32. package/skills/argent-create-e2e/references/reliability-and-recovery.md +3 -3
  33. package/skills/argent-device-interact/SKILL.md +3 -2
  34. package/skills/argent-device-interact/references/chromium.md +1 -1
  35. package/skills/argent-device-interact/references/harmonyos.md +1 -1
  36. package/skills/argent-ios-simulator-setup/SKILL.md +1 -1
  37. package/skills/argent-macos-app-interact/SKILL.md +44 -0
  38. package/skills/argent-macos-app-interact/references/e2e.md +6 -0
  39. package/skills/argent-qa-e2e/SKILL.md +1 -1
  40. package/skills/argent-react-native-app-workflow/SKILL.md +2 -0
  41. package/skills/argent-react-native-optimization/SKILL.md +2 -3
  42. package/skills/argent-react-native-profiler/SKILL.md +8 -8
  43. package/skills/argent-screenshot-diff/SKILL.md +1 -1
  44. package/skills/argent-test-ui-flow/SKILL.md +1 -1
  45. package/skills/argent-tv-interact/SKILL.md +4 -4
  46. package/bin/argent-android-devtools-0.3.0.apk +0 -0
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";
@@ -9481,6 +9481,10 @@ var FLAG_REGISTRY = [
9481
9481
  name: "video-watermark",
9482
9482
  description: "Overlay the argent corner watermark on recorded screen videos. On by default; turn it off with `argent disable video-watermark`.",
9483
9483
  defaultEnabled: true
9484
+ },
9485
+ {
9486
+ name: "macos-apps",
9487
+ description: "Drive native Mac apps (AppKit, SwiftUI, react-native-macos) from inside the app. Adds the macos platform to list-devices and boot-device, and the file-panel and menu-bar tools. Off by default."
9484
9488
  }
9485
9489
  ];
9486
9490
  function getFlagDefinition(name, registry2 = FLAG_REGISTRY) {
@@ -10452,6 +10456,8 @@ var FAILURE_CODES = {
10452
10456
  CLI_RUN_ARGS_JSON_INVALID: "CLI_RUN_ARGS_JSON_INVALID",
10453
10457
  CLI_RUN_INPUT_VALIDATION_FAILED: "CLI_RUN_INPUT_VALIDATION_FAILED",
10454
10458
  CLI_RUN_TOOL_CALL_FAILED: "CLI_RUN_TOOL_CALL_FAILED",
10459
+ // The result carried the tool-server's `failure` field.
10460
+ CLI_RUN_TOOL_RESULT_FAILED: "CLI_RUN_TOOL_RESULT_FAILED",
10455
10461
  CLI_RUN_SAVE_IMAGE_FAILED: "CLI_RUN_SAVE_IMAGE_FAILED",
10456
10462
  TOOL_CAPABILITY_UNSUPPORTED_OPERATION: "TOOL_CAPABILITY_UNSUPPORTED_OPERATION",
10457
10463
  TOOL_PLATFORM_NOT_IMPLEMENTED: "TOOL_PLATFORM_NOT_IMPLEMENTED",
@@ -10467,6 +10473,7 @@ var FAILURE_CODES = {
10467
10473
  INSTALL_MODE_FLAG_CONFLICT: "INSTALL_MODE_FLAG_CONFLICT",
10468
10474
  INSTALL_FROM_TAR_PACKAGE_FAILED: "INSTALL_FROM_TAR_PACKAGE_FAILED",
10469
10475
  INSTALL_INIT_TRIGGERED_UPDATE_FAILED: "INSTALL_INIT_TRIGGERED_UPDATE_FAILED",
10476
+ INSTALL_SKILLS_FAILED: "INSTALL_SKILLS_FAILED",
10470
10477
  INSTALL_SKILLS_REFRESH_FAILED: "INSTALL_SKILLS_REFRESH_FAILED",
10471
10478
  INSTALL_UNCLASSIFIED_FAILED: "INSTALL_UNCLASSIFIED_FAILED",
10472
10479
  UPDATE_INSTALLED_VERSION_DETECT_FAILED: "UPDATE_INSTALLED_VERSION_DETECT_FAILED",
@@ -10550,15 +10557,21 @@ var FAILURE_CODES = {
10550
10557
  ANDROID_LAUNCH_AM_START_FAILED: "ANDROID_LAUNCH_AM_START_FAILED",
10551
10558
  ANDROID_OPEN_URL_FAILED: "ANDROID_OPEN_URL_FAILED",
10552
10559
  ANDROID_REINSTALL_INSTALL_FAILED: "ANDROID_REINSTALL_INSTALL_FAILED",
10560
+ ANDROID_REINSTALL_UNINSTALL_FAILED: "ANDROID_REINSTALL_UNINSTALL_FAILED",
10553
10561
  ANDROID_RESTART_FAILED: "ANDROID_RESTART_FAILED",
10554
10562
  ANDROID_SETTINGS_PERMISSION_FAILED: "ANDROID_SETTINGS_PERMISSION_FAILED",
10555
10563
  IOS_LAUNCH_SIMCTL_FAILED: "IOS_LAUNCH_SIMCTL_FAILED",
10556
10564
  IOS_OPEN_URL_FAILED: "IOS_OPEN_URL_FAILED",
10557
10565
  IOS_REINSTALL_INSTALL_FAILED: "IOS_REINSTALL_INSTALL_FAILED",
10566
+ IOS_REINSTALL_UNINSTALL_FAILED: "IOS_REINSTALL_UNINSTALL_FAILED",
10558
10567
  IOS_RESTART_LAUNCH_FAILED: "IOS_RESTART_LAUNCH_FAILED",
10568
+ IOS_RESTART_TERMINATE_FAILED: "IOS_RESTART_TERMINATE_FAILED",
10569
+ IOS_TERMINATE_APP_FAILED: "IOS_TERMINATE_APP_FAILED",
10559
10570
  IOS_SETTINGS_PERMISSION_FAILED: "IOS_SETTINGS_PERMISSION_FAILED",
10560
10571
  IOS_SHAKE_FAILED: "IOS_SHAKE_FAILED",
10561
10572
  ANDROID_SHAKE_FAILED: "ANDROID_SHAKE_FAILED",
10573
+ STOP_METRO_PORT_PROBE_FAILED: "STOP_METRO_PORT_PROBE_FAILED",
10574
+ STOP_METRO_SIGNAL_FAILED: "STOP_METRO_SIGNAL_FAILED",
10562
10575
  // The hinge of a foldable iOS simulator: the server refused the request on a
10563
10576
  // foldable (a bad angle, a hinge that would not move) vs. nothing to fold —
10564
10577
  // a device that is not foldable, no hinge route, a remote simulator.
@@ -10657,6 +10670,8 @@ var FAILURE_CODES = {
10657
10670
  REACT_PROFILER_START_FAILED: "REACT_PROFILER_START_FAILED",
10658
10671
  REACT_PROFILER_START_VERIFY_FAILED: "REACT_PROFILER_START_VERIFY_FAILED",
10659
10672
  REACT_PROFILER_SESSION_TORN_DOWN: "REACT_PROFILER_SESSION_TORN_DOWN",
10673
+ REACT_PROFILER_SESSION_TAKEN_OVER: "REACT_PROFILER_SESSION_TAKEN_OVER",
10674
+ REACT_PROFILER_STOP_IN_PROGRESS: "REACT_PROFILER_STOP_IN_PROGRESS",
10660
10675
  REACT_PROFILER_HOOK_ERROR: "REACT_PROFILER_HOOK_ERROR",
10661
10676
  REACT_PROFILER_ANALYZE_NO_DATA: "REACT_PROFILER_ANALYZE_NO_DATA",
10662
10677
  NATIVE_PROFILER_FACTORY_OPTIONS_MISSING: "NATIVE_PROFILER_FACTORY_OPTIONS_MISSING",
@@ -10704,10 +10719,13 @@ var FAILURE_CODES = {
10704
10719
  FLOW_NAME_INVALID: "FLOW_NAME_INVALID",
10705
10720
  FLOW_NO_ACTIVE_RECORDING: "FLOW_NO_ACTIVE_RECORDING",
10706
10721
  FLOW_FILE_INVALID: "FLOW_FILE_INVALID",
10722
+ FLOW_FILE_NEWER_VERSION: "FLOW_FILE_NEWER_VERSION",
10707
10723
  FLOW_FILE_WRITE_FAILED: "FLOW_FILE_WRITE_FAILED",
10708
10724
  FLOW_ENTRY_UNRECOGNIZED: "FLOW_ENTRY_UNRECOGNIZED",
10709
10725
  FLOW_E2E_HAS_PREREQUISITE: "FLOW_E2E_HAS_PREREQUISITE",
10710
10726
  FLOW_DEVICE_RESOLUTION: "FLOW_DEVICE_RESOLUTION",
10727
+ // The target does not satisfy the test's `requires:` block; a directory run skips it.
10728
+ FLOW_REQUIREMENTS_UNMET: "FLOW_REQUIREMENTS_UNMET",
10711
10729
  PROFILER_QUERY_MODE_INVALID: "PROFILER_QUERY_MODE_INVALID",
10712
10730
  PROFILER_QUERY_REQUIRED_PARAM_MISSING: "PROFILER_QUERY_REQUIRED_PARAM_MISSING",
10713
10731
  PROFILER_DATA_NOT_LOADED: "PROFILER_DATA_NOT_LOADED",
@@ -10726,11 +10744,38 @@ var FAILURE_CODES = {
10726
10744
  CHROMIUM_STORAGE_EVAL_FAILED: "CHROMIUM_STORAGE_EVAL_FAILED",
10727
10745
  CHROMIUM_TAB_NOT_FOUND: "CHROMIUM_TAB_NOT_FOUND",
10728
10746
  CHROMIUM_TAB_OPEN_FAILED: "CHROMIUM_TAB_OPEN_FAILED",
10747
+ CHROMIUM_NAVIGATE_FAILED: "CHROMIUM_NAVIGATE_FAILED",
10729
10748
  CHROMIUM_DESCRIBE_FAILED: "CHROMIUM_DESCRIBE_FAILED",
10730
10749
  CHROMIUM_ELECTRON_APP_PATH_INVALID: "CHROMIUM_ELECTRON_APP_PATH_INVALID",
10731
10750
  CHROMIUM_ELECTRON_SPAWN_FAILED: "CHROMIUM_ELECTRON_SPAWN_FAILED",
10732
10751
  CHROMIUM_ELECTRON_CDP_TIMEOUT: "CHROMIUM_ELECTRON_CDP_TIMEOUT",
10733
10752
  CHROMIUM_ELECTRON_EXITED_BEFORE_READY: "CHROMIUM_ELECTRON_EXITED_BEFORE_READY",
10753
+ /**
10754
+ * Native Mac apps (the `macos-apps` flag)
10755
+ * @see [macos-app.ts](../../tool-server/src/blueprints/macos-app.ts)
10756
+ */
10757
+ MACOS_APP_UNSUPPORTED_HOST: "MACOS_APP_UNSUPPORTED_HOST",
10758
+ MACOS_APP_PATH_INVALID: "MACOS_APP_PATH_INVALID",
10759
+ MACOS_APP_BUNDLE_INVALID: "MACOS_APP_BUNDLE_INVALID",
10760
+ MACOS_APP_NOT_FOUND: "MACOS_APP_NOT_FOUND",
10761
+ MACOS_APP_HARDENED_RUNTIME: "MACOS_APP_HARDENED_RUNTIME",
10762
+ MACOS_APP_LIBRARY_MISSING: "MACOS_APP_LIBRARY_MISSING",
10763
+ MACOS_APP_LIBRARY_STAGE_FAILED: "MACOS_APP_LIBRARY_STAGE_FAILED",
10764
+ MACOS_APP_ALREADY_RUNNING: "MACOS_APP_ALREADY_RUNNING",
10765
+ MACOS_APP_SPAWN_FAILED: "MACOS_APP_SPAWN_FAILED",
10766
+ MACOS_APP_EXITED_BEFORE_READY: "MACOS_APP_EXITED_BEFORE_READY",
10767
+ MACOS_APP_HANDSHAKE_TIMEOUT: "MACOS_APP_HANDSHAKE_TIMEOUT",
10768
+ MACOS_APP_HANDSHAKE_INVALID: "MACOS_APP_HANDSHAKE_INVALID",
10769
+ MACOS_APP_NOT_RUNNING: "MACOS_APP_NOT_RUNNING",
10770
+ MACOS_APP_NOT_CONNECTED: "MACOS_APP_NOT_CONNECTED",
10771
+ MACOS_APP_CONNECTION_LOST: "MACOS_APP_CONNECTION_LOST",
10772
+ MACOS_APP_CONNECTION_CLOSED: "MACOS_APP_CONNECTION_CLOSED",
10773
+ MACOS_APP_RPC_TIMEOUT: "MACOS_APP_RPC_TIMEOUT",
10774
+ MACOS_APP_RPC_ERROR: "MACOS_APP_RPC_ERROR",
10775
+ MACOS_APP_TERMINATE_FAILED: "MACOS_APP_TERMINATE_FAILED",
10776
+ MACOS_APP_BUNDLE_ID_MISMATCH: "MACOS_APP_BUNDLE_ID_MISMATCH",
10777
+ MACOS_APP_OPEN_URL_FAILED: "MACOS_APP_OPEN_URL_FAILED",
10778
+ MACOS_APP_FILE_ACCESS_FAILED: "MACOS_APP_FILE_ACCESS_FAILED",
10734
10779
  KEYBOARD_KEY_UNSUPPORTED: "KEYBOARD_KEY_UNSUPPORTED",
10735
10780
  KEYBOARD_CHARACTER_UNSUPPORTED: "KEYBOARD_CHARACTER_UNSUPPORTED",
10736
10781
  KEYBOARD_TEXT_AND_KEY_COMBINED: "KEYBOARD_TEXT_AND_KEY_COMBINED",
@@ -10742,6 +10787,8 @@ var FAILURE_CODES = {
10742
10787
  SCREENSHOT_DIFF_INPUT_INVALID: "SCREENSHOT_DIFF_INPUT_INVALID",
10743
10788
  BOOT_DEVICE_TARGET_SELECTION_INVALID: "BOOT_DEVICE_TARGET_SELECTION_INVALID",
10744
10789
  BOOT_IOS_UNSUPPORTED_HOST: "BOOT_IOS_UNSUPPORTED_HOST",
10790
+ BOOT_IOS_SIMCTL_LIST_TIMEOUT: "BOOT_IOS_SIMCTL_LIST_TIMEOUT",
10791
+ BOOT_IOS_SIMCTL_TIMEOUT: "BOOT_IOS_SIMCTL_TIMEOUT",
10745
10792
  BOOT_ANDROID_NO_AVDS: "BOOT_ANDROID_NO_AVDS",
10746
10793
  BOOT_ANDROID_AVD_NOT_FOUND: "BOOT_ANDROID_AVD_NOT_FOUND",
10747
10794
  BOOT_ANDROID_ADB_UNAVAILABLE: "BOOT_ANDROID_ADB_UNAVAILABLE",
@@ -31639,7 +31686,8 @@ async function consumeToolStream(name, body, onProgress, signal) {
31639
31686
  final = {
31640
31687
  data: msg.data,
31641
31688
  note: msg.note,
31642
- ...msg.profiling === true ? { profiling: true } : {}
31689
+ ...msg.profiling === true ? { profiling: true } : {},
31690
+ ...typeof msg.failure === "string" ? { failure: msg.failure } : {}
31643
31691
  };
31644
31692
  } else if (msg.event === "error") {
31645
31693
  throw new ToolInvocationError(msg.error ?? "tool invocation failed", {
@@ -31691,7 +31739,8 @@ async function consumeToolStream(name, body, onProgress, signal) {
31691
31739
  return {
31692
31740
  data: final.data,
31693
31741
  note: final.note,
31694
- ...final.profiling ? { profiling: true } : {}
31742
+ ...final.profiling ? { profiling: true } : {},
31743
+ ...final.failure !== void 0 ? { failure: final.failure } : {}
31695
31744
  };
31696
31745
  }
31697
31746
  function errorBodyMessage(body) {
@@ -31856,6 +31905,7 @@ function createToolsClient(options = {}) {
31856
31905
  }
31857
31906
  const shadowWarning = await e2eShadowWarningFor(name, args);
31858
31907
  const baselineDirs = [];
31908
+ let unwrittenBaselines2 = [];
31859
31909
  if (meta3?.fileInputs?.length) {
31860
31910
  if (remote) assertRequiredPresent(meta3, finalArgs);
31861
31911
  finalArgs = await prepareFileInputs(meta3.fileInputs, finalArgs ?? {}, {
@@ -31869,8 +31919,9 @@ function createToolsClient(options = {}) {
31869
31919
  signal: opts?.signal
31870
31920
  });
31871
31921
  }
31872
- const settle2 = async (data) => {
31873
- const applied = await applyClientFileDirectives(data, { allowedDirs: baselineDirs });
31922
+ const settle2 = async (data2) => {
31923
+ const applied = await applyClientFileDirectives(data2, { allowedDirs: baselineDirs });
31924
+ unwrittenBaselines2 = applied.failed.map((f) => f.path);
31874
31925
  for (const { path: file2, error: error63 } of applied.failed) {
31875
31926
  diagnose(`The baseline ${file2} was not written on this client: ${error63}`);
31876
31927
  }
@@ -31887,6 +31938,7 @@ function createToolsClient(options = {}) {
31887
31938
  }
31888
31939
  return { ...result, warning: shadowWarning };
31889
31940
  };
31941
+ const unwrittenFailure = () => unwrittenBaselines2.length > 0 ? `Baselines not written on this client: ${unwrittenBaselines2.join(", ")}` : void 0;
31890
31942
  const stream = opts?.onProgress !== void 0 || carriesMembers(finalArgs);
31891
31943
  const sentOnce = carriesUpload(finalArgs);
31892
31944
  const forwarded = remote ? await forwardedSecrets(finalArgs) : void 0;
@@ -31950,11 +32002,14 @@ function createToolsClient(options = {}) {
31950
32002
  }),
31951
32003
  opts?.signal
31952
32004
  );
32005
+ const data2 = await settle2(streamed.data);
32006
+ const failure3 = streamed.failure ?? unwrittenFailure();
31953
32007
  return {
31954
- data: await settle2(streamed.data),
32008
+ data: data2,
31955
32009
  note: streamed.note,
31956
32010
  outputHint: meta3?.outputHint,
31957
- ...streamed.profiling ? { profiling: true } : {}
32011
+ ...streamed.profiling ? { profiling: true } : {},
32012
+ ...failure3 !== void 0 ? { failure: failure3 } : {}
31958
32013
  };
31959
32014
  }
31960
32015
  let json2;
@@ -31990,11 +32045,14 @@ function createToolsClient(options = {}) {
31990
32045
  });
31991
32046
  }
31992
32047
  opts?.signal?.throwIfAborted();
32048
+ const data = await settle2(json2.data);
32049
+ const failure2 = (typeof json2.failure === "string" ? json2.failure : void 0) ?? unwrittenFailure();
31993
32050
  return {
31994
- data: await settle2(json2.data),
32051
+ data,
31995
32052
  note: json2.note,
31996
32053
  outputHint: meta3?.outputHint,
31997
- ...json2.profiling === true ? { profiling: true } : {}
32054
+ ...json2.profiling === true ? { profiling: true } : {},
32055
+ ...failure2 !== void 0 ? { failure: failure2 } : {}
31998
32056
  };
31999
32057
  }
32000
32058
  return { fetchTools, fetchTool, callTool, baseUrl };
@@ -35057,7 +35115,8 @@ var PLATFORMS = [
35057
35115
  "vega",
35058
35116
  "harmony",
35059
35117
  "tvos",
35060
- "android-tv"
35118
+ "android-tv",
35119
+ "macos"
35061
35120
  ];
35062
35121
  var DEVICE_KINDS = [
35063
35122
  "simulator",
@@ -35209,6 +35268,9 @@ var ALLOWED = {
35209
35268
  warned_count: COUNT
35210
35269
  },
35211
35270
  "installation:skill_install": {
35271
+ source: oneOf(["pinned", "bundled"]),
35272
+ used_fallback: bool,
35273
+ fallback_reason: oneOf(["ref_missing", "unclassified"]),
35212
35274
  method: oneOf(["default", "interactive", "manual"]),
35213
35275
  is_online: bool,
35214
35276
  has_offline_cache: bool,
@@ -35809,7 +35871,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
35809
35871
  var SESSION_ID2 = randomUUID5();
35810
35872
  function readCliVersion() {
35811
35873
  if (true) {
35812
- return "0.32.1-next.2";
35874
+ return "0.32.1-next.20";
35813
35875
  }
35814
35876
  return "0.0.0";
35815
35877
  }
@@ -36748,391 +36810,106 @@ function isValidationIssue(value) {
36748
36810
  return typeof issue2.code === "string" && Array.isArray(issue2.path) && typeof issue2.message === "string";
36749
36811
  }
36750
36812
 
36751
- // ../argent-cli/src/run.ts
36752
- function splitOptions(argv) {
36753
- let json2 = false;
36754
- let outPath = null;
36755
- const rest = [];
36813
+ // ../argent-cli/src/e2e.ts
36814
+ import * as fsp from "node:fs/promises";
36815
+ import { constants as fsConstants2 } from "node:fs";
36816
+ import { createHash as createHash5 } from "node:crypto";
36817
+ import * as path17 from "node:path";
36818
+
36819
+ // ../argent-cli/src/command-args.ts
36820
+ var UsageError = class extends Error {
36821
+ constructor(message) {
36822
+ super(message);
36823
+ this.name = "UsageError";
36824
+ }
36825
+ };
36826
+ function listChoices(choices) {
36827
+ const quoted = choices.map((c2) => `"${c2}"`);
36828
+ if (quoted.length <= 1) return quoted.join("");
36829
+ return `${quoted.slice(0, -1).join(", ")} or ${quoted[quoted.length - 1]}`;
36830
+ }
36831
+ function resolveName(tok, specs) {
36832
+ if (tok.startsWith("--")) return tok.slice(2) in specs ? tok.slice(2) : null;
36833
+ const short = tok.slice(1);
36834
+ for (const [name, spec] of Object.entries(specs)) {
36835
+ if (spec.alias === short) return name;
36836
+ }
36837
+ return null;
36838
+ }
36839
+ function parseCommandArgs(argv, specs) {
36840
+ const positionals = [];
36841
+ const options = {};
36756
36842
  for (let i2 = 0; i2 < argv.length; i2++) {
36757
36843
  const tok = argv[i2];
36758
- if (tok === "--json") {
36759
- json2 = true;
36760
- continue;
36844
+ if (tok === "--") {
36845
+ positionals.push(...argv.slice(i2 + 1));
36846
+ break;
36761
36847
  }
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;
36848
+ if (!tok.startsWith("-") || tok === "-") {
36849
+ positionals.push(tok);
36767
36850
  continue;
36768
36851
  }
36769
- if (tok.startsWith("--out=")) {
36770
- outPath = tok.slice("--out=".length);
36852
+ const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
36853
+ const flag = eq === -1 ? tok : tok.slice(0, eq);
36854
+ const inlineValue = eq === -1 ? void 0 : tok.slice(eq + 1);
36855
+ const name = resolveName(flag, specs);
36856
+ if (name === null) throw new UsageError(`Unknown flag: ${tok}`);
36857
+ const spec = specs[name];
36858
+ const display = `--${name}`;
36859
+ if (spec.kind === "boolean") {
36860
+ if (inlineValue !== void 0) throw new UsageError(`${display} does not take a value`);
36861
+ const next = argv[i2 + 1]?.trim().toLowerCase();
36862
+ if (next === "true" || next === "false") {
36863
+ throw new UsageError(
36864
+ `${display} does not take a value \u2014 it is a switch; omit it to leave the option off`
36865
+ );
36866
+ }
36867
+ options[name] = true;
36771
36868
  continue;
36772
36869
  }
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}`;
36870
+ let value = inlineValue;
36871
+ if (value === void 0) {
36872
+ const next = argv[i2 + 1];
36873
+ if (next !== void 0 && (!next.startsWith("-") || next === "-")) {
36874
+ value = next;
36875
+ i2 += 1;
36876
+ }
36877
+ } else if (value === "") {
36878
+ value = void 0;
36879
+ }
36880
+ if (value === void 0) throw new UsageError(`${display} requires a value`);
36881
+ if (spec.choices && !spec.choices.includes(value)) {
36882
+ throw new UsageError(`${display} must be ${listChoices(spec.choices)}, got "${value}"`);
36825
36883
  }
36884
+ options[name] = value;
36826
36885
  }
36827
- if (typeof result === "string") return result;
36828
- return JSON.stringify(result, null, 2);
36886
+ return { positionals, options };
36829
36887
  }
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";
36888
+
36889
+ // ../argent-cli/src/e2e.ts
36890
+ var STATUS_GLYPH = {
36891
+ pass: "\u2713",
36892
+ fail: "\u2717",
36893
+ error: "\u2717",
36894
+ skip: "\xB7"
36895
+ };
36896
+ var MAX_RENDER_DEPTH = 20;
36897
+ function stepIndent(depth) {
36898
+ if (typeof depth !== "number" || !Number.isInteger(depth) || depth <= 0) return "";
36899
+ return " ".repeat(Math.min(depth, MAX_RENDER_DEPTH));
36833
36900
  }
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();
36901
+ function durationSuffix(ms) {
36902
+ if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) return "";
36903
+ const tenths = Math.round(ms / 100);
36904
+ if (tenths < 600) return ` (${(tenths / 10).toFixed(1)}s)`;
36905
+ const seconds = Math.round(ms / 1e3);
36906
+ return ` (${Math.floor(seconds / 60)}m ${seconds % 60}s)`;
36843
36907
  }
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]
36908
+ function printHelp(toStderr = false) {
36909
+ const print = toStderr ? console.error : console.log;
36910
+ print(`Usage: argent e2e <subcommand> [options]
36851
36911
 
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:
36912
+ Run a YAML e2e test without an LLM in the loop. \`run\` takes any of these forms:
37136
36913
 
37137
36914
  a name An e2e test saved under .argent/e2e or .argent/flows, resolved
37138
36915
  from the current directory. "checkout" runs
@@ -37150,8 +36927,9 @@ contain only letters, numbers, "_", or "-" \u2014 the same charset a name must
37150
36927
  match. A test is standalone when its first non-\`echo\`/\`script\` step is \`launch\`.
37151
36928
  Other tests are fragments and use the device's current state. Exception: a fragment whose first step \`run:\`s a chromium standalone
37152
36929
  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
36930
+ chromium (a lone \`{ chromium: ... }\` target, --platform chromium, or a
36931
+ \`requires:\` block that allows only chromium); a multi-platform launch
36932
+ auto-detects a device instead. Pass --device to attach to
37155
36933
  a running instance.
37156
36934
 
37157
36935
  A directory run prints only the steps that need attention (each failure, each
@@ -37164,6 +36942,12 @@ file, or a device it cannot resolve. A transport failure, a rejection the server
37164
36942
  does not mark as validation, or a reply that is not a report stops the batch and
37165
36943
  counts the remaining tests skipped.
37166
36944
 
36945
+ A test can declare a \`requires:\` block (platform, runtimeKind) that names the
36946
+ targets it supports. A directory run skips each test whose requirements the
36947
+ target does not meet, so the same directory runs on each platform without a
36948
+ separate list of tests. A single-test run fails instead. A directory run where
36949
+ no test ran exits 2.
36950
+
37167
36951
  The CLI sends a run to its tool-server: the local one that starts
37168
36952
  automatically, or the one that \`argent link\` or ARGENT_TOOLS_URL names. Over
37169
36953
  a link, the CLI uploads these files and writes the new baselines that the run
@@ -37193,7 +36977,7 @@ Subcommands:
37193
36977
 
37194
36978
  Options (run):
37195
36979
  --device <id> Device id to run against (auto-detected when omitted)
37196
- --platform <p> ios | android | chromium | vega | ios-remote \u2014 narrow
36980
+ --platform <p> ios | android | chromium | vega |${isFlagEnabled("macos-apps") ? " macos |" : ""} ios-remote \u2014 narrow
37197
36981
  auto-detection (ios never picks a remote simulator)
37198
36982
  --update-baselines Write/refresh screenshot baselines instead of diffing,
37199
36983
  also those of tests that tool: e2e-execute steps run,
@@ -37346,7 +37130,8 @@ function renderFailedSteps(report) {
37346
37130
  return lines;
37347
37131
  }
37348
37132
  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)}`;
37133
+ const verdict = counts.failed > 0 ? "FAIL" : counts.passed === 0 ? "NONE RAN" : "PASS";
37134
+ return `${verdict} \u2014 ${counts.total} e2e test${counts.total === 1 ? "" : "s"}: ${counts.passed} passed, ${counts.failed} failed, ${counts.skipped} skipped${durationSuffix(durationMs)}`;
37350
37135
  }
37351
37136
  function summarizeFailure(report) {
37352
37137
  let n3 = 0;
@@ -37385,14 +37170,14 @@ function shellQuoteArg(arg) {
37385
37170
  return `'${arg.replaceAll("'", "'\\''")}'`;
37386
37171
  }
37387
37172
  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;
37173
+ const pathArg = (p) => shellQuoteArg(p.startsWith("-") ? `.${path17.sep}${p}` : p);
37174
+ const fromCwd = path17.relative(projectRoot, flowPath);
37175
+ const shown = path17.isAbsolute(fromCwd) || fromCwd.split(/[\\/]+/).includes("..") ? flowPath : fromCwd;
37391
37176
  const parts = ["argent e2e run", pathArg(shown)];
37392
37177
  if (args.device) parts.push("--device", shellQuoteArg(args.device));
37393
37178
  if (args.platform) parts.push("--platform", shellQuoteArg(args.platform));
37394
37179
  if (args.updateBaselines) parts.push("--update-baselines");
37395
- if (args.output) parts.push("--output", pathArg(path18.join(args.output, path18.dirname(rel))));
37180
+ if (args.output) parts.push("--output", pathArg(path17.join(args.output, path17.dirname(rel))));
37396
37181
  return parts.join(" ");
37397
37182
  }
37398
37183
  var EXPORT_SOURCE_MARKER = ".argent-e2e-source";
@@ -37400,8 +37185,8 @@ var LEGACY_EXPORT_SOURCE_MARKER = ".argent-flow-source";
37400
37185
  var MARKER_READ_RETRIES = 4;
37401
37186
  var MARKER_READ_DELAY_MS = 5;
37402
37187
  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));
37188
+ const owner = await readMarkerFile(path17.join(dir, EXPORT_SOURCE_MARKER));
37189
+ return owner !== void 0 ? owner : await readMarkerFile(path17.join(dir, LEGACY_EXPORT_SOURCE_MARKER));
37405
37190
  }
37406
37191
  async function readMarkerFile(marker) {
37407
37192
  for (let attempt = 0; ; attempt++) {
@@ -37438,7 +37223,7 @@ function occupiedBy(owner) {
37438
37223
  async function takeExportDir(dir, flowPath) {
37439
37224
  try {
37440
37225
  await fsp.mkdir(dir, { recursive: true });
37441
- await fsp.writeFile(path18.join(dir, EXPORT_SOURCE_MARKER), `${flowPath}
37226
+ await fsp.writeFile(path17.join(dir, EXPORT_SOURCE_MARKER), `${flowPath}
37442
37227
  `, { flag: "wx" });
37443
37228
  return null;
37444
37229
  } catch (err) {
@@ -37457,7 +37242,7 @@ async function claimExportDirName(outputDir, flowPath, stem) {
37457
37242
  }
37458
37243
  const avoided = [];
37459
37244
  for (const name of candidates) {
37460
- const dir = path18.join(outputDir, name);
37245
+ const dir = path17.join(outputDir, name);
37461
37246
  const claim2 = await classifyExportDir(dir, flowPath);
37462
37247
  const reason = claim2.state === "foreign" ? occupiedBy(claim2.owner) : await takeExportDir(dir, flowPath);
37463
37248
  if (reason !== null) {
@@ -37477,7 +37262,7 @@ async function claimExportDirName(outputDir, flowPath, stem) {
37477
37262
  return null;
37478
37263
  }
37479
37264
  async function exportFailureArtifacts(report, outputDir, flowPath, ctx) {
37480
- const stem = path18.basename(flowPath, ".yaml");
37265
+ const stem = path17.basename(flowPath, ".yaml");
37481
37266
  if (!SAFE_ARTIFACT_NAME.test(stem)) {
37482
37267
  console.error(
37483
37268
  `warning: skipping artifact export for unsafe e2e test filename ${JSON.stringify(stem)}`
@@ -37510,11 +37295,11 @@ async function exportFailureArtifacts(report, outputDir, flowPath, ctx) {
37510
37295
  if (dir === null) {
37511
37296
  const dirName = await claimExportDirName(outputDir, flowPath, stem);
37512
37297
  if (dirName === null) return;
37513
- dir = path18.join(outputDir, dirName);
37298
+ dir = path17.join(outputDir, dirName);
37514
37299
  }
37515
- const dest = path18.join(dir, fileName(role));
37516
- const rel = path18.relative(outputDir, dest);
37517
- if (rel.startsWith("..") || path18.isAbsolute(rel)) continue;
37300
+ const dest = path17.join(dir, fileName(role));
37301
+ const rel = path17.relative(outputDir, dest);
37302
+ if (rel.startsWith("..") || path17.isAbsolute(rel)) continue;
37518
37303
  try {
37519
37304
  await fsp.copyFile(value, dest);
37520
37305
  s.artifacts[role] = dest;
@@ -37529,7 +37314,7 @@ async function exportFailureArtifacts(report, outputDir, flowPath, ctx) {
37529
37314
  function keyFromBaselinePath(artifacts) {
37530
37315
  const baseline = artifacts.baseline;
37531
37316
  if (typeof baseline !== "string") return null;
37532
- return path18.basename(baseline).replace(/\.png$/, "");
37317
+ return path17.basename(baseline).replace(/\.png$/, "");
37533
37318
  }
37534
37319
  function resolveArtifactDisplayPaths(report) {
37535
37320
  for (const s of report.steps) {
@@ -37586,17 +37371,17 @@ async function collectRunnableFlowPaths(dir, relDir = "") {
37586
37371
  const found = [];
37587
37372
  const entries = await fsp.readdir(dir, { withFileTypes: true });
37588
37373
  for (const entry of entries) {
37589
- const rel = relDir ? path18.join(relDir, entry.name) : entry.name;
37374
+ const rel = relDir ? path17.join(relDir, entry.name) : entry.name;
37590
37375
  if (entry.isDirectory()) {
37591
37376
  if (entry.name === "__baselines__") continue;
37592
37377
  found.push(
37593
- ...await collectRunnableFlowPaths(path18.join(dir, entry.name), rel).catch(() => [])
37378
+ ...await collectRunnableFlowPaths(path17.join(dir, entry.name), rel).catch(() => [])
37594
37379
  );
37595
37380
  continue;
37596
37381
  }
37597
37382
  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);
37383
+ if (!SAFE_FLOW_NAME.test(path17.basename(entry.name, ".yaml"))) continue;
37384
+ if (await isRunnableFlowFile(path17.join(dir, entry.name))) found.push(rel);
37600
37385
  }
37601
37386
  return found;
37602
37387
  }
@@ -37604,26 +37389,26 @@ async function resolveFlowRef(ref, projectRoot) {
37604
37389
  if (!SAFE_FLOW_NAME.test(ref)) return { suppliedPath: ref, fromName: false };
37605
37390
  const picked = await resolveE2eFile(projectRoot, ref);
37606
37391
  return {
37607
- suppliedPath: path18.relative(projectRoot, picked.path),
37392
+ suppliedPath: path17.relative(projectRoot, picked.path),
37608
37393
  fromName: true,
37609
- ...picked.shadowed === void 0 ? {} : { shadowed: path18.relative(projectRoot, picked.shadowed) }
37394
+ ...picked.shadowed === void 0 ? {} : { shadowed: path17.relative(projectRoot, picked.shadowed) }
37610
37395
  };
37611
37396
  }
37612
37397
  async function savedFlowHint(projectRoot, missingPath) {
37613
- const dirs = E2E_DIRS.map((rel) => ({ rel, abs: path18.resolve(projectRoot, rel) }));
37398
+ const dirs = E2E_DIRS.map((rel) => ({ rel, abs: path17.resolve(projectRoot, rel) }));
37614
37399
  for (const { abs } of dirs) {
37615
- const within = path18.relative(abs, missingPath);
37616
- if (!within.startsWith("..") && !path18.isAbsolute(within)) return "";
37400
+ const within = path17.relative(abs, missingPath);
37401
+ if (!within.startsWith("..") && !path17.isAbsolute(within)) return "";
37617
37402
  }
37618
- const wanted = `${path18.basename(missingPath, ".yaml")}.yaml`;
37403
+ const wanted = `${path17.basename(missingPath, ".yaml")}.yaml`;
37619
37404
  for (const { rel, abs } of dirs) {
37620
37405
  const entries = await fsp.readdir(abs).catch(() => null);
37621
37406
  if (entries === null) continue;
37622
37407
  const actual = entries.includes(wanted) ? wanted : entries.find((name2) => name2.toLowerCase() === wanted.toLowerCase());
37623
37408
  if (actual === void 0) continue;
37624
- const name = path18.basename(actual, ".yaml");
37409
+ const name = path17.basename(actual, ".yaml");
37625
37410
  if (!SAFE_FLOW_NAME.test(name)) return "";
37626
- if (!await isRunnableFlowFile(path18.join(abs, actual))) return "";
37411
+ if (!await isRunnableFlowFile(path17.join(abs, actual))) return "";
37627
37412
  return `
37628
37413
  An e2e test named "${name}" is saved under ${rel} \u2014 did you mean: argent e2e run ${name}`;
37629
37414
  }
@@ -37633,17 +37418,17 @@ async function collectFlowFiles(dir, recursive2) {
37633
37418
  const found = [];
37634
37419
  const walk = async (current, rel) => {
37635
37420
  for (const entry of await fsp.readdir(current, { withFileTypes: true })) {
37636
- const entryRel = rel ? path18.join(rel, entry.name) : entry.name;
37421
+ const entryRel = rel ? path17.join(rel, entry.name) : entry.name;
37637
37422
  if (entry.isDirectory()) {
37638
37423
  if (recursive2 && !entry.name.startsWith(".") && entry.name !== "node_modules") {
37639
- await walk(path18.join(current, entry.name), entryRel).catch(() => {
37424
+ await walk(path17.join(current, entry.name), entryRel).catch(() => {
37640
37425
  });
37641
37426
  }
37642
37427
  continue;
37643
37428
  }
37644
37429
  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);
37430
+ if (!SAFE_FLOW_NAME.test(path17.basename(entry.name, ".yaml"))) continue;
37431
+ if (await isRunnableFlowFile(path17.join(current, entry.name))) found.push(entryRel);
37647
37432
  }
37648
37433
  };
37649
37434
  await walk(dir, "");
@@ -37732,8 +37517,12 @@ function rejectionVerdict(code) {
37732
37517
  case FAILURE_CODES.FLOW_ENTRY_UNRECOGNIZED:
37733
37518
  case FAILURE_CODES.FLOW_E2E_HAS_PREREQUISITE:
37734
37519
  return "not run (invalid e2e test)";
37520
+ case FAILURE_CODES.FLOW_FILE_NEWER_VERSION:
37521
+ return "not run (needs a newer Argent)";
37735
37522
  case FAILURE_CODES.FLOW_DEVICE_RESOLUTION:
37736
37523
  return "not run (no device resolved)";
37524
+ case FAILURE_CODES.FLOW_REQUIREMENTS_UNMET:
37525
+ return "not run (requires not met)";
37737
37526
  case FAILURE_CODES.FILE_INPUT_UPLOAD_FAILED:
37738
37527
  return "not run (upload failed)";
37739
37528
  default:
@@ -37786,7 +37575,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
37786
37575
  );
37787
37576
  }
37788
37577
  const { callTool, baseUrl } = runToolsClient(args, options);
37789
- const outputBase = args.output ? path18.resolve(args.output) : void 0;
37578
+ const outputBase = args.output ? path17.resolve(args.output) : void 0;
37790
37579
  const results = [];
37791
37580
  const failures = [];
37792
37581
  const batchStartedAt = Date.now();
@@ -37799,7 +37588,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
37799
37588
  if (!args.json) console.log(` ${STATUS_GLYPH.skip} not run (batch stopped)`);
37800
37589
  continue;
37801
37590
  }
37802
- const flowPath = path18.join(dir, rel);
37591
+ const flowPath = path17.join(dir, rel);
37803
37592
  const rerun = rerunCommand(flowPath, rel, projectRoot, args);
37804
37593
  let report;
37805
37594
  try {
@@ -37810,6 +37599,14 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
37810
37599
  const err = unreachableErr ?? thrown;
37811
37600
  const message = err instanceof Error ? err.message : String(err);
37812
37601
  const toolErr = err instanceof ToolInvocationError ? err : void 0;
37602
+ if (toolErr?.errorCode === FAILURE_CODES.FLOW_REQUIREMENTS_UNMET) {
37603
+ const reason = message.replace(/^\[Tool:[^\]]+\] /, "");
37604
+ results.push({ path: rel, status: "skip", reason });
37605
+ if (!args.json)
37606
+ console.log(` ${STATUS_GLYPH.skip} skipped (requires not met)
37607
+ ${reason}`);
37608
+ continue;
37609
+ }
37813
37610
  const rejectedThisFlowOnly = toolErr?.errorKind === "validation";
37814
37611
  const verdict = unreachableErr ? UNREACHABLE_VERDICT : rejectedThisFlowOnly ? rejectionVerdict(toolErr?.errorCode) : "did not finish (run error)";
37815
37612
  if (!args.json) console.log(` ${STATUS_GLYPH.error} ${verdict}`);
@@ -37833,7 +37630,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
37833
37630
  printWarnings(nestedStepWarnings(report.steps), args.json);
37834
37631
  await exportAndResolveArtifacts(
37835
37632
  report,
37836
- outputBase ? path18.join(outputBase, path18.dirname(rel)) : void 0,
37633
+ outputBase ? path17.join(outputBase, path17.dirname(rel)) : void 0,
37837
37634
  flowPath,
37838
37635
  baseUrl
37839
37636
  );
@@ -37860,11 +37657,18 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
37860
37657
  skipped: results.filter((r2) => r2.status === "skip").length
37861
37658
  };
37862
37659
  const durationMs = Date.now() - batchStartedAt;
37660
+ const noneRan = counts.passed === 0 && counts.failed === 0;
37863
37661
  if (args.json) {
37864
37662
  console.log(
37865
37663
  JSON.stringify(
37866
37664
  // `flows` is the deprecated spelling of `e2eTests`.
37867
- { ok: counts.failed === 0, ...counts, durationMs, e2eTests: results, flows: results },
37665
+ {
37666
+ ok: counts.failed === 0 && !noneRan,
37667
+ ...counts,
37668
+ durationMs,
37669
+ e2eTests: results,
37670
+ flows: results
37671
+ },
37868
37672
  null,
37869
37673
  2
37870
37674
  )
@@ -37873,8 +37677,9 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
37873
37677
  for (const line of renderFailedFlows(failures)) console.log(line);
37874
37678
  console.log(`
37875
37679
  ${renderBatchSummary(counts, durationMs)}`);
37680
+ if (noneRan) console.error(`No e2e test in ${dir} ran on the selected target.`);
37876
37681
  }
37877
- return exitAfterFlush(unreachable ? 2 : counts.failed === 0 ? 0 : 1);
37682
+ return exitAfterFlush(unreachable || noneRan ? 2 : counts.failed === 0 ? 0 : 1);
37878
37683
  }
37879
37684
  async function e2e(argv, options) {
37880
37685
  const [sub, ...rest] = argv;
@@ -37891,14 +37696,14 @@ async function e2e(argv, options) {
37891
37696
  for (const dir of E2E_DIRS) {
37892
37697
  let rels;
37893
37698
  try {
37894
- rels = (await collectRunnableFlowPaths(path18.join(process.cwd(), dir))).sort();
37699
+ rels = (await collectRunnableFlowPaths(path17.join(process.cwd(), dir))).sort();
37895
37700
  } catch {
37896
37701
  continue;
37897
37702
  }
37898
37703
  anyDir = true;
37899
37704
  for (const rel of rels) {
37900
- const shown = path18.join(dir, rel);
37901
- const name = path18.dirname(rel) === "." ? path18.basename(rel, ".yaml") : void 0;
37705
+ const shown = path17.join(dir, rel);
37706
+ const name = path17.dirname(rel) === "." ? path17.basename(rel, ".yaml") : void 0;
37902
37707
  const usedByName = name === void 0 ? void 0 : byName.get(name);
37903
37708
  if (name !== void 0 && usedByName === void 0) byName.set(name, shown);
37904
37709
  lines.push(shown);
@@ -37969,10 +37774,10 @@ async function e2e(argv, options) {
37969
37774
  }
37970
37775
  if (suppliedPath.split(/[\\/]+/).includes("..")) {
37971
37776
  let recovery = "Pass the fully resolved path to the e2e test's YAML.";
37972
- const suppliedBase2 = path18.basename(suppliedPath);
37777
+ const suppliedBase2 = path17.basename(suppliedPath);
37973
37778
  if (suppliedBase2 !== ".." && suppliedBase2 !== ".") {
37974
37779
  try {
37975
- const resolved = path18.join(await fsp.realpath(path18.dirname(suppliedPath)), suppliedBase2);
37780
+ const resolved = path17.join(await fsp.realpath(path17.dirname(suppliedPath)), suppliedBase2);
37976
37781
  await fsp.stat(resolved);
37977
37782
  recovery = `Did you mean: argent e2e run ${shellQuoteArg(resolved)}`;
37978
37783
  } catch {
@@ -37984,7 +37789,7 @@ async function e2e(argv, options) {
37984
37789
  2
37985
37790
  );
37986
37791
  }
37987
- const resolvedPath = path18.resolve(projectRoot, suppliedPath);
37792
+ const resolvedPath = path17.resolve(projectRoot, suppliedPath);
37988
37793
  let isDirectory = false;
37989
37794
  try {
37990
37795
  isDirectory = (await fsp.stat(resolvedPath)).isDirectory();
@@ -38007,16 +37812,16 @@ async function e2e(argv, options) {
38007
37812
  }
38008
37813
  const separatorTrimmedPath = suppliedPath.replace(/[\\/]+$/, "");
38009
37814
  if (separatorTrimmedPath !== suppliedPath && separatorTrimmedPath !== "") {
38010
- const hint = SAFE_FLOW_NAME.test(separatorTrimmedPath) ? `.${path18.sep}${separatorTrimmedPath}` : separatorTrimmedPath;
37815
+ const hint = SAFE_FLOW_NAME.test(separatorTrimmedPath) ? `.${path17.sep}${separatorTrimmedPath}` : separatorTrimmedPath;
38011
37816
  return fail(
38012
37817
  `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
37818
  Did you mean: argent e2e run ${shellQuoteArg(hint)}`,
38014
37819
  2
38015
37820
  );
38016
37821
  }
38017
- if (path18.extname(suppliedPath) !== ".yaml") {
38018
- const looksLikeName = !suppliedPath.includes("/") && !suppliedPath.includes("\\") && path18.extname(suppliedPath) === "";
38019
- if (path18.basename(suppliedPath).toLowerCase() === ".yaml") {
37822
+ if (path17.extname(suppliedPath) !== ".yaml") {
37823
+ const looksLikeName = !suppliedPath.includes("/") && !suppliedPath.includes("\\") && path17.extname(suppliedPath) === "";
37824
+ if (path17.basename(suppliedPath).toLowerCase() === ".yaml") {
38020
37825
  return fail(
38021
37826
  `E2E test filename must have a non-empty name containing only letters, numbers, "_", or "-": ${suppliedPath}`,
38022
37827
  2
@@ -38029,15 +37834,15 @@ Names run \`${E2E_DIRS.map((dir) => `${dir}/<name>.yaml`).join("` or `")}\`; pas
38029
37834
  2
38030
37835
  );
38031
37836
  }
38032
- if (path18.extname(suppliedPath).toLowerCase() === ".yaml") {
37837
+ if (path17.extname(suppliedPath).toLowerCase() === ".yaml") {
38033
37838
  return fail(
38034
- `E2E test extension must be lowercase .yaml, not ${path18.extname(suppliedPath)}: ${suppliedPath}`,
37839
+ `E2E test extension must be lowercase .yaml, not ${path17.extname(suppliedPath)}: ${suppliedPath}`,
38035
37840
  2
38036
37841
  );
38037
37842
  }
38038
37843
  return fail(`E2E test path must end in .yaml: ${suppliedPath}`, 2);
38039
37844
  }
38040
- const flowName = path18.basename(suppliedPath, ".yaml");
37845
+ const flowName = path17.basename(suppliedPath, ".yaml");
38041
37846
  if (!SAFE_FLOW_NAME.test(flowName)) {
38042
37847
  return fail(
38043
37848
  `E2E test filename must have a non-empty name containing only letters, numbers, "_", or "-": ${suppliedPath}`,
@@ -38061,12 +37866,12 @@ No e2e test named "${args.flowRef}" is saved in ${E2E_DIRS.join(" or ")} \u2014
38061
37866
  }
38062
37867
  return fail(`${detail}: ${flowPath}${recovery}`, 2);
38063
37868
  }
38064
- const suppliedBase = path18.basename(flowPath);
38065
- const siblings = await fsp.readdir(path18.dirname(flowPath)).catch(() => null);
37869
+ const suppliedBase = path17.basename(flowPath);
37870
+ const siblings = await fsp.readdir(path17.dirname(flowPath)).catch(() => null);
38066
37871
  if (siblings !== null && !siblings.includes(suppliedBase)) {
38067
37872
  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.";
37873
+ const actualRunnable = actual !== void 0 && path17.extname(actual) === ".yaml" && SAFE_FLOW_NAME.test(path17.basename(actual, ".yaml"));
37874
+ 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
37875
  return fail(
38071
37876
  `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
37877
  ${recovery}`,
@@ -38125,7 +37930,7 @@ ${recovery}`,
38125
37930
  try {
38126
37931
  await exportAndResolveArtifacts(
38127
37932
  report,
38128
- args.output ? path18.resolve(args.output) : void 0,
37933
+ args.output ? path17.resolve(args.output) : void 0,
38129
37934
  flowPath,
38130
37935
  baseUrl
38131
37936
  );
@@ -38154,6 +37959,313 @@ ${renderSummary(report, { withDevice: true })}`);
38154
37959
  return exitAfterFlush(report.ok && unwritten.length === 0 ? 0 : 1);
38155
37960
  }
38156
37961
 
37962
+ // ../argent-cli/src/run.ts
37963
+ function splitOptions(argv) {
37964
+ let json2 = false;
37965
+ let outPath = null;
37966
+ const rest = [];
37967
+ for (let i2 = 0; i2 < argv.length; i2++) {
37968
+ const tok = argv[i2];
37969
+ if (tok === "--json") {
37970
+ json2 = true;
37971
+ continue;
37972
+ }
37973
+ if (tok === "--out") {
37974
+ const v = argv[i2 + 1];
37975
+ if (!v) throw new FlagParseException("--out requires a path");
37976
+ outPath = v;
37977
+ i2 += 1;
37978
+ continue;
37979
+ }
37980
+ if (tok.startsWith("--out=")) {
37981
+ outPath = tok.slice("--out=".length);
37982
+ continue;
37983
+ }
37984
+ rest.push(tok);
37985
+ }
37986
+ return { json: json2, outPath, argvForFlags: rest };
37987
+ }
37988
+ async function readStdin() {
37989
+ return new Promise((resolve13, reject) => {
37990
+ let data = "";
37991
+ process.stdin.setEncoding("utf8");
37992
+ process.stdin.on("data", (chunk) => {
37993
+ data += chunk;
37994
+ });
37995
+ process.stdin.on("end", () => resolve13(data));
37996
+ process.stdin.on("error", reject);
37997
+ });
37998
+ }
37999
+ function printToolHelp(meta3) {
38000
+ const description = meta3.description?.trim() ?? "";
38001
+ const schema = meta3.inputSchema;
38002
+ const hasOwnArgsField = schema?.properties?.args !== void 0;
38003
+ console.log(`argent run ${meta3.name} [flags]`);
38004
+ if (description) console.log(`
38005
+ ${description}
38006
+ `);
38007
+ console.log("Flags:");
38008
+ console.log(formatSchemaUsage(schema));
38009
+ console.log("\nGlobal flags:");
38010
+ if (!hasOwnArgsField) {
38011
+ console.log(" --args <json> Pass the entire payload as JSON (overrides flags)");
38012
+ console.log(" --args - Read the entire payload as JSON from stdin");
38013
+ }
38014
+ console.log(" --<field>-json <json> Pass a single field as JSON (objects/nested arrays)");
38015
+ console.log(" --json Print the raw JSON result");
38016
+ console.log(" --out <path> For image results, save to <path> instead of fetching URL");
38017
+ console.log(" --help, -h Show this help");
38018
+ }
38019
+ async function fetchImageToFile(result, outPath) {
38020
+ const url2 = result.url;
38021
+ if (!url2) {
38022
+ throw new Error("Tool result did not include a `url`; cannot save image");
38023
+ }
38024
+ const res = await fetch(url2);
38025
+ if (!res.ok) throw new Error(`Failed to download image: ${res.status} ${res.statusText}`);
38026
+ const buf = Buffer.from(await res.arrayBuffer());
38027
+ fs12.mkdirSync(path18.dirname(path18.resolve(outPath)), { recursive: true });
38028
+ fs12.writeFileSync(outPath, buf);
38029
+ }
38030
+ function renderResult(result, outputHint, images, json2) {
38031
+ if (json2) return JSON.stringify(result, null, 2);
38032
+ if (outputHint === "image") {
38033
+ if (images.length > 0) return `Saved screenshot: ${images[0].localPath}`;
38034
+ if (result && typeof result === "object" && "path" in result && typeof result.path === "string") {
38035
+ return `Saved screenshot: ${result.path}`;
38036
+ }
38037
+ }
38038
+ if (typeof result === "string") return result;
38039
+ return JSON.stringify(result, null, 2);
38040
+ }
38041
+ var SAFE_TOOL_RE = /^[a-z][a-z0-9_-]{0,63}$/;
38042
+ function safeToolName(toolName) {
38043
+ return toolName && SAFE_TOOL_RE.test(toolName) ? toolName : "unknown";
38044
+ }
38045
+ async function trackRunFailure(toolName, startedAt, signal) {
38046
+ const tool = toolName ? canonicalToolId(toolName) : toolName;
38047
+ track("cli:run_fail", {
38048
+ tool: safeToolName(tool),
38049
+ ...tool !== toolName ? { alias_used: true } : {},
38050
+ duration_ms: performance.now() - startedAt,
38051
+ ...signal
38052
+ });
38053
+ await shutdown();
38054
+ }
38055
+ async function run(argv, options) {
38056
+ init("cli");
38057
+ const startedAt = performance.now();
38058
+ const { fetchTool, callTool, baseUrl } = createToolsClient({ paths: options.paths });
38059
+ const [toolName, ...rest] = argv;
38060
+ if (!toolName || toolName === "--help" || toolName === "-h") {
38061
+ console.log(`Usage: argent run <tool> [flags]
38062
+
38063
+ Invoke a tool exposed by the argent tool-server. Run \`argent tools\` to list
38064
+ available tools, or \`argent tools describe <name>\` to see one tool's flags.
38065
+
38066
+ Examples:
38067
+ argent run list-devices
38068
+ argent run gesture-tap --udid <UDID> --x 0.5 --y 0.5
38069
+ argent run screenshot --udid <UDID> --out ./screen.png
38070
+ argent run run-sequence --udid <UDID> --steps-json '[{"tool":"button","args":{"button":"home"}}]'
38071
+ argent run gesture-tap --args '{"udid":"<UDID>","x":0.5,"y":0.5}'
38072
+
38073
+ Exits 1 when the tool fails. Also exits 1 when the result reports that the
38074
+ call did not do its job: an e2e test that does not pass, an await-ui-element
38075
+ wait with success=false, a run-sequence that a step stopped, a debugger tool
38076
+ with no connection or an error, a native-devtools precheck that stopped the
38077
+ tool, a native-profiler-analyze with status analysis_failed, an e2e recording
38078
+ call that recorded nothing, or a baseline that was not written on this client.
38079
+ Other results exit 0, also a negative answer such as await-screen-idle's
38080
+ settled=false or debugger-status's not_connected.
38081
+ `);
38082
+ return;
38083
+ }
38084
+ let cliOptions;
38085
+ try {
38086
+ cliOptions = splitOptions(rest);
38087
+ } catch (err) {
38088
+ if (err instanceof FlagParseException) {
38089
+ console.error(`Error: ${err.message}
38090
+ `);
38091
+ console.error(`Run \`argent run ${toolName} --help\` to see this tool's flags.`);
38092
+ await trackRunFailure(toolName, startedAt, {
38093
+ error_code: FAILURE_CODES.CLI_RUN_FLAG_PARSE_FAILED,
38094
+ failure_stage: "cli_run_split_options",
38095
+ failure_area: "cli",
38096
+ error_kind: "validation"
38097
+ });
38098
+ process.exit(2);
38099
+ }
38100
+ throw err;
38101
+ }
38102
+ const { json: json2, outPath, argvForFlags } = cliOptions;
38103
+ const meta3 = await fetchTool(toolName);
38104
+ if (!meta3) {
38105
+ console.error(`Tool "${toolName}" not found. Run \`argent tools\` to list available tools.`);
38106
+ await trackRunFailure(toolName, startedAt, {
38107
+ error_code: FAILURE_CODES.CLI_RUN_TOOL_NOT_FOUND,
38108
+ failure_stage: "cli_run_fetch_tool",
38109
+ failure_area: "cli",
38110
+ error_kind: "not_found"
38111
+ });
38112
+ process.exit(1);
38113
+ }
38114
+ const schema = meta3.inputSchema;
38115
+ const failInvocation = async (summary, report, failure3) => {
38116
+ if (json2) {
38117
+ console.error(
38118
+ JSON.stringify(
38119
+ {
38120
+ error: summary,
38121
+ missing: report ? missingFlagNames(report, schema) : [],
38122
+ issues: report?.rawIssues ?? []
38123
+ },
38124
+ null,
38125
+ 2
38126
+ )
38127
+ );
38128
+ } else {
38129
+ console.error(`Error: ${summary}
38130
+ `);
38131
+ printToolHelp(meta3);
38132
+ }
38133
+ await trackRunFailure(toolName, startedAt, {
38134
+ ...failure3,
38135
+ failure_area: "cli",
38136
+ error_kind: "validation"
38137
+ });
38138
+ process.exit(2);
38139
+ };
38140
+ const failValidation = (report, stage) => failInvocation(formatValidationError(report, schema), report, {
38141
+ error_code: FAILURE_CODES.CLI_RUN_INPUT_VALIDATION_FAILED,
38142
+ failure_stage: stage
38143
+ });
38144
+ let parsed;
38145
+ try {
38146
+ parsed = parseFlags(argvForFlags, schema);
38147
+ } catch (err) {
38148
+ if (err instanceof FlagParseException) {
38149
+ await failInvocation(err.message, null, {
38150
+ error_code: FAILURE_CODES.CLI_RUN_FLAG_PARSE_FAILED,
38151
+ failure_stage: "cli_run_parse_flags"
38152
+ });
38153
+ }
38154
+ throw err;
38155
+ }
38156
+ if (parsed.helpRequested) {
38157
+ printToolHelp(meta3);
38158
+ return;
38159
+ }
38160
+ if (parsed.positional.length > 0) {
38161
+ console.error(
38162
+ `Note: ignoring unused argument(s): ${parsed.positional.join(", ")}. Pass values as --flag <value> or --flag=<value>.`
38163
+ );
38164
+ }
38165
+ let payload = {};
38166
+ if (parsed.rawArgs !== null) {
38167
+ let rawJson = parsed.rawArgs;
38168
+ if (rawJson === "-") {
38169
+ rawJson = await readStdin();
38170
+ }
38171
+ try {
38172
+ const parsedRaw = JSON.parse(rawJson);
38173
+ if (parsedRaw === null || typeof parsedRaw !== "object" || Array.isArray(parsedRaw)) {
38174
+ console.error("--args must be a JSON object");
38175
+ await trackRunFailure(toolName, startedAt, {
38176
+ error_code: FAILURE_CODES.CLI_RUN_ARGS_NOT_OBJECT,
38177
+ failure_stage: "cli_run_parse_raw_args",
38178
+ failure_area: "cli",
38179
+ error_kind: "validation"
38180
+ });
38181
+ process.exit(2);
38182
+ }
38183
+ payload = parsedRaw;
38184
+ } catch (err) {
38185
+ console.error(`--args is not valid JSON: ${err instanceof Error ? err.message : err}`);
38186
+ await trackRunFailure(toolName, startedAt, {
38187
+ error_code: FAILURE_CODES.CLI_RUN_ARGS_JSON_INVALID,
38188
+ failure_stage: "cli_run_parse_raw_args",
38189
+ failure_area: "cli",
38190
+ error_kind: "validation"
38191
+ });
38192
+ process.exit(2);
38193
+ }
38194
+ }
38195
+ for (const [k, v] of Object.entries(parsed.args)) {
38196
+ payload[k] = v;
38197
+ }
38198
+ const missing = findMissingRequired(payload, schema);
38199
+ if (missing.length > 0) {
38200
+ await failValidation({ missing, invalid: [], rawIssues: null }, "cli_run_required_flags");
38201
+ }
38202
+ let result;
38203
+ let note;
38204
+ let failure2;
38205
+ let images = [];
38206
+ try {
38207
+ const resp = await callTool(toolName, payload);
38208
+ const { url: url2, token } = await baseUrl();
38209
+ const materialized = await materializeArtifacts(resp.data, {
38210
+ toolsUrl: url2,
38211
+ authToken: token,
38212
+ deviceId: getDeviceIdFromArgs(payload)
38213
+ });
38214
+ result = materialized.result;
38215
+ images = materialized.images;
38216
+ note = resp.note;
38217
+ failure2 = resp.failure;
38218
+ } catch (err) {
38219
+ const report = describeServerValidationFailure(err, payload, schema);
38220
+ if (report) {
38221
+ await failValidation(report, "cli_run_server_validation");
38222
+ }
38223
+ console.error(err instanceof Error ? err.message : String(err));
38224
+ await trackRunFailure(toolName, startedAt, {
38225
+ error_code: FAILURE_CODES.CLI_RUN_TOOL_CALL_FAILED,
38226
+ failure_stage: "cli_run_call_tool",
38227
+ failure_area: "cli",
38228
+ error_kind: "unknown"
38229
+ });
38230
+ process.exit(1);
38231
+ }
38232
+ if (outPath && meta3.outputHint === "image") {
38233
+ try {
38234
+ if (images.length > 0) {
38235
+ fs12.mkdirSync(path18.dirname(path18.resolve(outPath)), { recursive: true });
38236
+ fs12.writeFileSync(outPath, images[0].data);
38237
+ } else if (result && typeof result === "object") {
38238
+ await fetchImageToFile(result, outPath);
38239
+ }
38240
+ } catch (err) {
38241
+ console.error(`Failed to save image: ${err instanceof Error ? err.message : err}`);
38242
+ await trackRunFailure(toolName, startedAt, {
38243
+ error_code: FAILURE_CODES.CLI_RUN_SAVE_IMAGE_FAILED,
38244
+ failure_stage: "cli_run_save_image",
38245
+ failure_area: "cli",
38246
+ error_kind: "unknown"
38247
+ });
38248
+ process.exit(1);
38249
+ }
38250
+ }
38251
+ if (note) console.error(note);
38252
+ console.log(renderResult(result, meta3.outputHint, images, json2));
38253
+ if (outPath && meta3.outputHint === "image" && !json2) {
38254
+ console.log(`Wrote: ${outPath}`);
38255
+ }
38256
+ if (failure2) {
38257
+ await new Promise((resolve13) => process.stdout.write("", () => resolve13()));
38258
+ console.error(failure2);
38259
+ await trackRunFailure(toolName, startedAt, {
38260
+ error_code: FAILURE_CODES.CLI_RUN_TOOL_RESULT_FAILED,
38261
+ failure_stage: "cli_run_tool_result",
38262
+ failure_area: "cli",
38263
+ error_kind: "unknown"
38264
+ });
38265
+ await exitAfterFlush(1, [process.stderr]);
38266
+ }
38267
+ }
38268
+
38157
38269
  // ../argent-cli/src/tools.ts
38158
38270
  function summarize(description, max = 80) {
38159
38271
  if (!description) return "";