@swmansion/argent 0.22.1-next.7 → 0.22.1-next.9

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
@@ -7210,7 +7210,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
7210
7210
  var SESSION_ID2 = randomUUID5();
7211
7211
  function readCliVersion() {
7212
7212
  if (true) {
7213
- return "0.22.1-next.7";
7213
+ return "0.22.1-next.9";
7214
7214
  }
7215
7215
  return "0.0.0";
7216
7216
  }
@@ -8409,6 +8409,78 @@ import * as fsp from "node:fs/promises";
8409
8409
  import { constants as fsConstants2 } from "node:fs";
8410
8410
  import { createHash as createHash5 } from "node:crypto";
8411
8411
  import * as path14 from "node:path";
8412
+
8413
+ // ../argent-cli/src/command-args.ts
8414
+ var UsageError = class extends Error {
8415
+ constructor(message) {
8416
+ super(message);
8417
+ this.name = "UsageError";
8418
+ }
8419
+ };
8420
+ function listChoices(choices) {
8421
+ const quoted = choices.map((c2) => `"${c2}"`);
8422
+ if (quoted.length <= 1) return quoted.join("");
8423
+ return `${quoted.slice(0, -1).join(", ")} or ${quoted[quoted.length - 1]}`;
8424
+ }
8425
+ function resolveName(tok, specs) {
8426
+ if (tok.startsWith("--")) return tok.slice(2) in specs ? tok.slice(2) : null;
8427
+ const short = tok.slice(1);
8428
+ for (const [name, spec] of Object.entries(specs)) {
8429
+ if (spec.alias === short) return name;
8430
+ }
8431
+ return null;
8432
+ }
8433
+ function parseCommandArgs(argv, specs) {
8434
+ const positionals = [];
8435
+ const options = {};
8436
+ for (let i2 = 0; i2 < argv.length; i2++) {
8437
+ const tok = argv[i2];
8438
+ if (tok === "--") {
8439
+ positionals.push(...argv.slice(i2 + 1));
8440
+ break;
8441
+ }
8442
+ if (!tok.startsWith("-") || tok === "-") {
8443
+ positionals.push(tok);
8444
+ continue;
8445
+ }
8446
+ const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
8447
+ const flag = eq === -1 ? tok : tok.slice(0, eq);
8448
+ const inlineValue = eq === -1 ? void 0 : tok.slice(eq + 1);
8449
+ const name = resolveName(flag, specs);
8450
+ if (name === null) throw new UsageError(`Unknown flag: ${tok}`);
8451
+ const spec = specs[name];
8452
+ const display = `--${name}`;
8453
+ if (spec.kind === "boolean") {
8454
+ if (inlineValue !== void 0) throw new UsageError(`${display} does not take a value`);
8455
+ const next = argv[i2 + 1]?.trim().toLowerCase();
8456
+ if (next === "true" || next === "false") {
8457
+ throw new UsageError(
8458
+ `${display} does not take a value \u2014 it is a switch; omit it to leave the option off`
8459
+ );
8460
+ }
8461
+ options[name] = true;
8462
+ continue;
8463
+ }
8464
+ let value = inlineValue;
8465
+ if (value === void 0) {
8466
+ const next = argv[i2 + 1];
8467
+ if (next !== void 0 && (!next.startsWith("-") || next === "-")) {
8468
+ value = next;
8469
+ i2 += 1;
8470
+ }
8471
+ } else if (value === "") {
8472
+ value = void 0;
8473
+ }
8474
+ if (value === void 0) throw new UsageError(`${display} requires a value`);
8475
+ if (spec.choices && !spec.choices.includes(value)) {
8476
+ throw new UsageError(`${display} must be ${listChoices(spec.choices)}, got "${value}"`);
8477
+ }
8478
+ options[name] = value;
8479
+ }
8480
+ return { positionals, options };
8481
+ }
8482
+
8483
+ // ../argent-cli/src/flow.ts
8412
8484
  var STATUS_GLYPH = {
8413
8485
  pass: "\u2713",
8414
8486
  fail: "\u2717",
@@ -8484,72 +8556,39 @@ Examples:
8484
8556
  argent flow run .argent/flows --recursive
8485
8557
  `);
8486
8558
  }
8559
+ var RUN_OPTIONS = {
8560
+ "update-baselines": { kind: "boolean" },
8561
+ "json": { kind: "boolean" },
8562
+ "json-stream": { kind: "boolean" },
8563
+ "recursive": { kind: "boolean", alias: "r" },
8564
+ "device": { kind: "value" },
8565
+ "platform": { kind: "value" },
8566
+ "output": { kind: "value" }
8567
+ };
8487
8568
  function parseRunArgs(argv) {
8569
+ let parsed;
8570
+ try {
8571
+ parsed = parseCommandArgs(argv, RUN_OPTIONS);
8572
+ } catch (err) {
8573
+ if (err instanceof UsageError) throw new FlagParseException(err.message);
8574
+ throw err;
8575
+ }
8576
+ const { positionals, options } = parsed;
8577
+ if (positionals.length > 1) {
8578
+ throw new FlagParseException(
8579
+ `unexpected argument ${JSON.stringify(positionals[1])}; flow run accepts one flow name, YAML file path, or directory path`
8580
+ );
8581
+ }
8488
8582
  const out = {
8489
- updateBaselines: false,
8490
- recursive: false,
8491
- json: false,
8492
- jsonStream: false
8583
+ updateBaselines: options["update-baselines"] === true,
8584
+ recursive: options.recursive === true,
8585
+ json: options.json === true,
8586
+ jsonStream: options["json-stream"] === true
8493
8587
  };
8494
- const takePositional = (tok) => {
8495
- if (out.flowRef !== void 0) {
8496
- throw new FlagParseException(
8497
- `unexpected argument ${JSON.stringify(tok)}; flow run accepts one flow name, YAML file path, or directory path`
8498
- );
8499
- }
8500
- out.flowRef = tok;
8501
- };
8502
- for (let i2 = 0; i2 < argv.length; i2++) {
8503
- const tok = argv[i2];
8504
- if (tok === "--") {
8505
- for (const rest of argv.slice(i2 + 1)) takePositional(rest);
8506
- break;
8507
- }
8508
- if (!tok.startsWith("-")) {
8509
- takePositional(tok);
8510
- continue;
8511
- }
8512
- const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
8513
- const flag = eq === -1 ? tok : tok.slice(0, eq);
8514
- const inline = eq === -1 ? void 0 : tok.slice(eq + 1);
8515
- const takeValue = (name) => {
8516
- if (inline !== void 0) {
8517
- if (inline === "") throw new FlagParseException(`${name} requires a value`);
8518
- return inline;
8519
- }
8520
- const v = argv[i2 + 1];
8521
- if (v === void 0 || v.startsWith("-")) {
8522
- throw new FlagParseException(`${name} requires a value`);
8523
- }
8524
- i2 += 1;
8525
- return v;
8526
- };
8527
- const noValue = (name) => {
8528
- if (inline !== void 0) throw new FlagParseException(`${name} does not take a value`);
8529
- const next = argv[i2 + 1]?.trim().toLowerCase();
8530
- if (next === "true" || next === "false") {
8531
- throw new FlagParseException(
8532
- `${name} does not take a value \u2014 it is a switch; omit it to leave the option off`
8533
- );
8534
- }
8535
- };
8536
- if (flag === "--update-baselines") {
8537
- noValue("--update-baselines");
8538
- out.updateBaselines = true;
8539
- } else if (flag === "--json") {
8540
- noValue("--json");
8541
- out.json = true;
8542
- } else if (flag === "--json-stream") {
8543
- noValue("--json-stream");
8544
- out.jsonStream = true;
8545
- } else if (flag === "--recursive" || flag === "-r") {
8546
- noValue("--recursive");
8547
- out.recursive = true;
8548
- } else if (flag === "--device") out.device = takeValue("--device");
8549
- else if (flag === "--platform") out.platform = takeValue("--platform");
8550
- else if (flag === "--output") out.output = takeValue("--output");
8551
- else throw new FlagParseException(`unknown flag ${tok}`);
8552
- }
8588
+ if (positionals[0] !== void 0) out.flowRef = positionals[0];
8589
+ if (options.device !== void 0) out.device = options.device;
8590
+ if (options.platform !== void 0) out.platform = options.platform;
8591
+ if (options.output !== void 0) out.output = options.output;
8553
8592
  if (out.json && out.jsonStream) {
8554
8593
  throw new FlagParseException("--json and --json-stream cannot be combined");
8555
8594
  }
@@ -9351,67 +9390,39 @@ function logsCmd(follow) {
9351
9390
  }
9352
9391
  var StartFlagError = class extends Error {
9353
9392
  };
9393
+ var START_OPTIONS = {
9394
+ "help": { kind: "boolean", alias: "h" },
9395
+ "detach": { kind: "boolean", alias: "d" },
9396
+ "force": { kind: "boolean" },
9397
+ "no-auth": { kind: "boolean" },
9398
+ "port": { kind: "value", alias: "p" },
9399
+ "host": { kind: "value" },
9400
+ "idle-timeout": { kind: "value" }
9401
+ };
9402
+ function parseOrStartFlagError(parse) {
9403
+ try {
9404
+ return parse();
9405
+ } catch (err) {
9406
+ if (err instanceof UsageError) throw new StartFlagError(err.message);
9407
+ throw err;
9408
+ }
9409
+ }
9354
9410
  function parseStartFlags(argv) {
9355
- const flags2 = {
9356
- port: null,
9357
- host: "127.0.0.1",
9358
- idleTimeoutMinutes: 0,
9359
- detach: false,
9360
- force: false,
9361
- noAuth: false,
9362
- help: false
9363
- };
9364
- for (let i2 = 0; i2 < argv.length; i2++) {
9365
- const tok = argv[i2];
9366
- const takeValue = (name) => {
9367
- const v = argv[i2 + 1];
9368
- if (v === void 0) throw new StartFlagError(`${name} requires a value`);
9369
- i2 += 1;
9370
- return v;
9371
- };
9372
- if (tok === "--help" || tok === "-h") {
9373
- flags2.help = true;
9374
- continue;
9375
- }
9376
- if (tok === "--detach" || tok === "-d") {
9377
- flags2.detach = true;
9378
- continue;
9379
- }
9380
- if (tok === "--force") {
9381
- flags2.force = true;
9382
- continue;
9383
- }
9384
- if (tok === "--no-auth") {
9385
- flags2.noAuth = true;
9386
- continue;
9387
- }
9388
- if (tok === "--port" || tok === "-p") {
9389
- flags2.port = parsePort(takeValue("--port"));
9390
- continue;
9391
- }
9392
- if (tok.startsWith("--port=")) {
9393
- flags2.port = parsePort(tok.slice("--port=".length));
9394
- continue;
9395
- }
9396
- if (tok === "--host") {
9397
- flags2.host = takeValue("--host");
9398
- continue;
9399
- }
9400
- if (tok.startsWith("--host=")) {
9401
- flags2.host = tok.slice("--host=".length);
9402
- continue;
9403
- }
9404
- if (tok === "--idle-timeout") {
9405
- flags2.idleTimeoutMinutes = parseIdle(takeValue("--idle-timeout"));
9406
- continue;
9407
- }
9408
- if (tok.startsWith("--idle-timeout=")) {
9409
- flags2.idleTimeoutMinutes = parseIdle(tok.slice("--idle-timeout=".length));
9410
- continue;
9411
- }
9412
- throw new StartFlagError(`Unknown flag: ${tok}`);
9411
+ const { positionals, options } = parseOrStartFlagError(
9412
+ () => parseCommandArgs(argv, START_OPTIONS)
9413
+ );
9414
+ if (positionals.length > 0) {
9415
+ throw new StartFlagError(`Unexpected argument "${positionals[0]}"`);
9413
9416
  }
9414
- return flags2;
9417
+ return {
9418
+ port: options.port === void 0 ? null : parsePort(options.port),
9419
+ host: options.host ?? "127.0.0.1",
9420
+ idleTimeoutMinutes: options["idle-timeout"] === void 0 ? 0 : parseIdle(options["idle-timeout"]),
9421
+ detach: options.detach === true,
9422
+ force: options.force === true,
9423
+ noAuth: options["no-auth"] === true,
9424
+ help: options.help === true
9425
+ };
9415
9426
  }
9416
9427
  var NON_NEGATIVE_INT = /^\d+$/;
9417
9428
  function parsePort(raw) {
@@ -10315,40 +10326,31 @@ function formatLensFeedback(o2) {
10315
10326
  `[Argent Lens] Feedback from the preview window (round ${o2.round}). ${body}. ` + applyChosen + closing
10316
10327
  );
10317
10328
  }
10329
+ var LENS_OPTIONS = {
10330
+ help: { kind: "boolean", alias: "h" },
10331
+ forget: { kind: "boolean" },
10332
+ terminal: { kind: "value", alias: "t", choices: ["iterm", "terminal"] },
10333
+ agent: { kind: "value", alias: "a" }
10334
+ };
10318
10335
  function parseArgs(argv) {
10319
- let terminal;
10320
- let agent;
10321
- let help = false;
10322
- let forget = false;
10323
- for (let i2 = 0; i2 < argv.length; i2++) {
10324
- const tok = argv[i2];
10325
- if (tok === "--help" || tok === "-h") help = true;
10326
- else if (tok === "--forget") {
10327
- forget = true;
10328
- const next = argv[i2 + 1]?.trim().toLowerCase();
10329
- if (next === "true" || next === "false") {
10330
- process.stderr.write(`lens: --forget does not take a value; omit it to keep the state
10331
- `);
10332
- process.exit(2);
10333
- }
10334
- } else if (tok === "--terminal" || tok === "-t") {
10335
- const v = argv[++i2];
10336
- if (v === "iterm" || v === "terminal") terminal = v;
10337
- else {
10338
- process.stderr.write(`lens: --terminal expects "iterm" or "terminal", got "${v ?? ""}"
10339
- `);
10340
- process.exit(2);
10341
- }
10342
- } else if (tok === "--agent" || tok === "-a") {
10343
- agent = argv[++i2];
10344
- if (!agent) {
10345
- process.stderr.write(`lens: --agent expects one of: ${agentIds().join(", ")}
10346
- `);
10347
- process.exit(2);
10348
- }
10336
+ try {
10337
+ const { positionals, options } = parseCommandArgs(argv, LENS_OPTIONS);
10338
+ if (positionals.length > 0) {
10339
+ throw new UsageError(`Unexpected argument "${positionals[0]}"`);
10349
10340
  }
10341
+ return {
10342
+ terminal: options.terminal,
10343
+ agent: options.agent,
10344
+ help: options.help === true,
10345
+ forget: options.forget === true
10346
+ };
10347
+ } catch (err) {
10348
+ if (!(err instanceof UsageError)) throw err;
10349
+ const hint = err.message.startsWith("--agent") ? ` (one of: ${agentIds().join(", ")})` : "";
10350
+ process.stderr.write(`lens: ${err.message}${hint}
10351
+ `);
10352
+ process.exit(2);
10350
10353
  }
10351
- return { terminal, agent, help, forget };
10352
10354
  }
10353
10355
  function printHelp2() {
10354
10356
  process.stdout.write(
@@ -10649,53 +10651,22 @@ function colorState(enabled) {
10649
10651
  return enabled ? import_picocolors.default.green(label) : import_picocolors.default.red(label);
10650
10652
  }
10651
10653
  var FLAG_NAME_RE = /^[a-zA-Z][a-zA-Z0-9._-]*$/;
10654
+ var TOGGLE_OPTIONS = {
10655
+ scope: { kind: "value", choices: ["project", "global"] }
10656
+ };
10652
10657
  function parseToggleArgs(argv, command) {
10653
- let name = null;
10654
- let scope = "global";
10655
- let positionalOnly = false;
10656
- for (let i2 = 0; i2 < argv.length; i2++) {
10657
- const tok = argv[i2];
10658
- if (positionalOnly) {
10659
- if (name !== null) throw new Error(`Unexpected extra argument: "${tok}"`);
10660
- name = tok;
10661
- continue;
10662
- }
10663
- if (tok === "--") {
10664
- positionalOnly = true;
10665
- continue;
10666
- }
10667
- if (tok === "--scope") {
10668
- const v = argv[i2 + 1];
10669
- if (v === void 0) throw new Error("--scope requires a value (project|global)");
10670
- scope = parseScope(v);
10671
- i2 += 1;
10672
- continue;
10673
- }
10674
- if (tok.startsWith("--scope=")) {
10675
- scope = parseScope(tok.slice("--scope=".length));
10676
- continue;
10677
- }
10678
- if (tok.startsWith("--")) {
10679
- throw new Error(`Unknown flag: ${tok}`);
10680
- }
10681
- if (name !== null) {
10682
- throw new Error(`Unexpected extra argument: "${tok}"`);
10683
- }
10684
- name = tok;
10685
- }
10686
- if (name === null) {
10687
- throw new Error(`Usage: argent ${command} <flag-name> [--scope project|global]`);
10658
+ const { positionals, options } = parseCommandArgs(argv, TOGGLE_OPTIONS);
10659
+ const [name, extra] = positionals;
10660
+ if (extra !== void 0) throw new UsageError(`Unexpected extra argument: "${extra}"`);
10661
+ if (name === void 0) {
10662
+ throw new UsageError(`Usage: argent ${command} <flag-name> [--scope project|global]`);
10688
10663
  }
10689
10664
  if (!FLAG_NAME_RE.test(name)) {
10690
- throw new Error(
10665
+ throw new UsageError(
10691
10666
  `Invalid flag name "${name}". Must start with a letter and contain only letters, digits, ".", "_", or "-".`
10692
10667
  );
10693
10668
  }
10694
- return { name, scope };
10695
- }
10696
- function parseScope(raw) {
10697
- if (raw === "global" || raw === "project") return raw;
10698
- throw new Error(`--scope must be "project" or "global", got "${raw}"`);
10669
+ return { name, scope: options.scope ?? "global" };
10699
10670
  }
10700
10671
  function formatAvailableFlags(registry) {
10701
10672
  if (registry.length === 0) {
@@ -10990,36 +10961,23 @@ other scope / the default on the next read.`);
10990
10961
  reportError(err);
10991
10962
  }
10992
10963
  }
10964
+ var CONFIG_OPTIONS = {
10965
+ scope: { kind: "value", choices: ["global", "project"] },
10966
+ json: { kind: "boolean" }
10967
+ };
10993
10968
  function parseArgs2(argv) {
10994
- const positionals = [];
10995
- let scope = null;
10996
- let json = false;
10997
- for (let i2 = 0; i2 < argv.length; i2++) {
10998
- const tok = argv[i2];
10999
- if (tok === "--json") {
11000
- json = true;
11001
- continue;
11002
- }
11003
- if (tok === "--scope") {
11004
- scope = parseScope2(argv[++i2]);
11005
- continue;
11006
- }
11007
- if (tok.startsWith("--scope=")) {
11008
- scope = parseScope2(tok.slice("--scope=".length));
11009
- continue;
11010
- }
11011
- if (tok.startsWith("--")) {
11012
- console.error(`Error: unknown flag "${tok}".`);
11013
- process.exit(2);
11014
- }
11015
- positionals.push(tok);
10969
+ try {
10970
+ const { positionals, options } = parseCommandArgs(argv, CONFIG_OPTIONS);
10971
+ return {
10972
+ positionals,
10973
+ scope: options.scope ?? null,
10974
+ json: options.json === true
10975
+ };
10976
+ } catch (err) {
10977
+ if (!(err instanceof UsageError)) throw err;
10978
+ console.error(`Error: ${err.message}.`);
10979
+ process.exit(2);
11016
10980
  }
11017
- return { positionals, scope, json };
11018
- }
11019
- function parseScope2(raw) {
11020
- if (raw === "global" || raw === "project") return raw;
11021
- console.error(`Error: --scope must be "global" or "project"${raw ? `, got "${raw}"` : ""}.`);
11022
- process.exit(2);
11023
10981
  }
11024
10982
  function wantsHelp(argv) {
11025
10983
  return argv.includes("--help") || argv.includes("-h");
@@ -12382,91 +12340,60 @@ function validateConnectPort(raw) {
12382
12340
  }
12383
12341
  return port;
12384
12342
  }
12343
+ var LINK_OPTIONS = {
12344
+ "help": { kind: "boolean", alias: "h" },
12345
+ "yes": { kind: "boolean", alias: "y" },
12346
+ "no-verify": { kind: "boolean" },
12347
+ "host": { kind: "value" },
12348
+ "port": { kind: "value", alias: "p" },
12349
+ "token": { kind: "value" }
12350
+ };
12385
12351
  function parseLinkFlags(argv) {
12352
+ const { positionals, options } = parseOrStartFlagError(
12353
+ () => parseCommandArgs(argv, LINK_OPTIONS)
12354
+ );
12386
12355
  const flags2 = {
12387
12356
  host: null,
12388
12357
  port: null,
12389
12358
  token: null,
12390
12359
  url: null,
12391
- yes: false,
12392
- noVerify: false,
12393
- help: false
12360
+ yes: options.yes === true,
12361
+ noVerify: options["no-verify"] === true,
12362
+ help: options.help === true
12394
12363
  };
12395
- for (let i2 = 0; i2 < argv.length; i2++) {
12396
- const tok = argv[i2];
12397
- const takeValue = (name) => {
12398
- const v = argv[i2 + 1];
12399
- if (v === void 0) throw new StartFlagError(`${name} requires a value`);
12400
- i2 += 1;
12401
- return v;
12402
- };
12403
- if (tok === "--help" || tok === "-h") {
12404
- flags2.help = true;
12405
- continue;
12406
- }
12407
- if (tok === "--yes" || tok === "-y") {
12408
- flags2.yes = true;
12409
- continue;
12410
- }
12411
- if (tok === "--no-verify") {
12412
- flags2.noVerify = true;
12413
- continue;
12414
- }
12415
- if (tok === "--host") {
12416
- flags2.host = validateHost(takeValue("--host"));
12417
- continue;
12418
- }
12419
- if (tok.startsWith("--host=")) {
12420
- flags2.host = validateHost(tok.slice("--host=".length));
12421
- continue;
12422
- }
12423
- if (tok === "--port" || tok === "-p") {
12424
- flags2.port = validateConnectPort(takeValue("--port"));
12425
- continue;
12426
- }
12427
- if (tok.startsWith("--port=")) {
12428
- flags2.port = validateConnectPort(tok.slice("--port=".length));
12429
- continue;
12430
- }
12431
- if (tok === "--token") {
12432
- flags2.token = takeValue("--token");
12433
- continue;
12434
- }
12435
- if (tok.startsWith("--token=")) {
12436
- flags2.token = tok.slice("--token=".length);
12437
- continue;
12438
- }
12439
- if (!tok.startsWith("-")) {
12440
- const parsed = parseLinkTarget(tok);
12441
- if (!parsed) {
12442
- throw new StartFlagError(
12443
- `Unrecognized argument "${tok}". Expected an argent://\u2026 pairing string, an http(s):// URL, or flags (see --help).`
12444
- );
12445
- }
12446
- flags2.host = validateHost(parsed.host);
12447
- flags2.port = validateConnectPort(String(parsed.port));
12448
- flags2.url = parsed.url;
12449
- if (parsed.token) flags2.token = parsed.token;
12450
- continue;
12364
+ if (positionals.length > 1) {
12365
+ throw new StartFlagError(`Unexpected argument "${positionals[1]}"`);
12366
+ }
12367
+ const target = positionals[0];
12368
+ if (target !== void 0) {
12369
+ const parsed = parseLinkTarget(target);
12370
+ if (!parsed) {
12371
+ throw new StartFlagError(
12372
+ `Unrecognized argument "${target}". Expected an argent://\u2026 pairing string, an http(s):// URL, or flags (see --help).`
12373
+ );
12451
12374
  }
12452
- throw new StartFlagError(`Unknown flag: ${tok}`);
12375
+ flags2.host = validateHost(parsed.host);
12376
+ flags2.port = validateConnectPort(String(parsed.port));
12377
+ flags2.url = parsed.url;
12378
+ if (parsed.token) flags2.token = parsed.token;
12453
12379
  }
12380
+ if (options.host !== void 0) flags2.host = validateHost(options.host);
12381
+ if (options.port !== void 0) flags2.port = validateConnectPort(options.port);
12382
+ if (options.token !== void 0) flags2.token = options.token;
12454
12383
  return flags2;
12455
12384
  }
12385
+ var UNLINK_OPTIONS = {
12386
+ help: { kind: "boolean", alias: "h" },
12387
+ yes: { kind: "boolean", alias: "y" }
12388
+ };
12456
12389
  function parseUnlinkFlags(argv) {
12457
- const flags2 = { yes: false, help: false };
12458
- for (const tok of argv) {
12459
- if (tok === "--help" || tok === "-h") {
12460
- flags2.help = true;
12461
- continue;
12462
- }
12463
- if (tok === "--yes" || tok === "-y") {
12464
- flags2.yes = true;
12465
- continue;
12466
- }
12467
- throw new StartFlagError(`Unknown flag: ${tok}`);
12390
+ const { positionals, options } = parseOrStartFlagError(
12391
+ () => parseCommandArgs(argv, UNLINK_OPTIONS)
12392
+ );
12393
+ if (positionals.length > 0) {
12394
+ throw new StartFlagError(`Unexpected argument "${positionals[0]}"`);
12468
12395
  }
12469
- return flags2;
12396
+ return { yes: options.yes === true, help: options.help === true };
12470
12397
  }
12471
12398
  function printLinkHelp() {
12472
12399
  console.log(`Usage: argent link [<target>] [flags]
@@ -12824,61 +12751,6 @@ async function unlink3(argv) {
12824
12751
 
12825
12752
  // ../argent-cli/src/telemetry.ts
12826
12753
  var import_picocolors5 = __toESM(require_picocolors(), 1);
12827
-
12828
- // ../argent-cli/src/command-args.ts
12829
- var UsageError = class extends Error {
12830
- constructor(message) {
12831
- super(message);
12832
- this.name = "UsageError";
12833
- }
12834
- };
12835
- function parseCommandArgs(argv, specs) {
12836
- const positionals = [];
12837
- const options = {};
12838
- for (let i2 = 0; i2 < argv.length; i2++) {
12839
- const tok = argv[i2];
12840
- if (tok === "--") {
12841
- positionals.push(...argv.slice(i2 + 1));
12842
- break;
12843
- }
12844
- if (!tok.startsWith("--")) {
12845
- positionals.push(tok);
12846
- continue;
12847
- }
12848
- const eq = tok.indexOf("=");
12849
- const name = eq === -1 ? tok.slice(2) : tok.slice(2, eq);
12850
- const inlineValue = eq === -1 ? void 0 : tok.slice(eq + 1);
12851
- const spec = specs[name];
12852
- if (!spec) throw new UsageError(`Unknown flag "${tok}".`);
12853
- if (spec.kind === "boolean") {
12854
- if (inlineValue !== void 0) throw new UsageError(`--${name} does not take a value.`);
12855
- options[name] = true;
12856
- continue;
12857
- }
12858
- let value = inlineValue;
12859
- if (value === void 0) {
12860
- const next = argv[i2 + 1];
12861
- if (next !== void 0 && !next.startsWith("--")) {
12862
- value = next;
12863
- i2 += 1;
12864
- }
12865
- }
12866
- if (value === void 0 || value === "") {
12867
- throw new UsageError(
12868
- `--${name} requires a value${spec.choices ? ` (${spec.choices.join("|")})` : ""}.`
12869
- );
12870
- }
12871
- if (spec.choices && !spec.choices.includes(value)) {
12872
- throw new UsageError(
12873
- `--${name} must be one of ${spec.choices.map((c2) => `"${c2}"`).join(", ")} (got "${value}").`
12874
- );
12875
- }
12876
- options[name] = value;
12877
- }
12878
- return { positionals, options };
12879
- }
12880
-
12881
- // ../argent-cli/src/telemetry.ts
12882
12754
  var SCOPES = ["global", "project"];
12883
12755
  var TELEMETRY_OPTIONS = {
12884
12756
  scope: { kind: "value", choices: SCOPES }
@@ -16428,7 +16428,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
16428
16428
  var SESSION_ID = randomUUID4();
16429
16429
  function readCliVersion() {
16430
16430
  if (true) {
16431
- return "0.22.1-next.7";
16431
+ return "0.22.1-next.9";
16432
16432
  }
16433
16433
  return "0.0.0";
16434
16434
  }
@@ -95734,7 +95734,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
95734
95734
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
95735
95735
  function readCliVersion() {
95736
95736
  if (true) {
95737
- return "0.22.1-next.7";
95737
+ return "0.22.1-next.9";
95738
95738
  }
95739
95739
  return "0.0.0";
95740
95740
  }
@@ -111155,7 +111155,7 @@ function isNativeDevtoolsBlockResult(toolId, result) {
111155
111155
  async function precheckNativeDevtools(api, udid, bundleId) {
111156
111156
  if (bundleId !== void 0 && !isInjectableBundleId(bundleId)) {
111157
111157
  throw new FailureError(
111158
- `${bundleId} is an Apple system app: it is a platform binary with library validation, so Argent native devtools cannot be relied on to inject into it \u2014 treat it as unavailable rather than retrying. ` + NON_INJECTABLE_RECOVERY,
111158
+ `${bundleId} is an Apple system app: it is never the app under test, so Argent native devtools refuse to read one \u2014 treat it as unavailable rather than retrying. ` + NON_INJECTABLE_RECOVERY,
111159
111159
  {
111160
111160
  error_code: FAILURE_CODES.NATIVE_DEVTOOLS_NOT_INJECTABLE,
111161
111161
  failure_stage: "native_devtools_precheck",
@@ -111854,7 +111854,7 @@ function tcpArtifactHint(err) {
111854
111854
  return /TCP-transport (?:binary|dylib) not found/.test(message) ? message : void 0;
111855
111855
  }
111856
111856
  var TVOS_HINT = "This is an Apple TV (tvOS) simulator, which the iOS accessibility service does not support. Use the `describe` tool to read the focused and focusable elements, `tv-remote` (up/down/left/right/select/back/menu/home) to move focus, and `keyboard` to type. See the argent-tv-interact skill.";
111857
- var NON_INJECTABLE_HINT = "This is an Apple system app (com.apple.*), which cannot be relied on to load argent's native-devtools instrumentation \u2014 the native view hierarchy is unavailable and restarting the app will NOT help. Take a `screenshot` to see the screen and interact by coordinate. " + NON_INJECTABLE_NATIVE_WARNING;
111857
+ var NON_INJECTABLE_HINT = "This is an Apple system app (com.apple.*), which argent's native-devtools instrumentation does not support \u2014 the native view hierarchy is unavailable and restarting the app will NOT help. Take a `screenshot` to see the screen and interact by coordinate. " + NON_INJECTABLE_NATIVE_WARNING;
111858
111858
  function emptyTree() {
111859
111859
  return parseDescribeResult({
111860
111860
  role: "AXGroup",
@@ -116225,10 +116225,10 @@ Returns { envSetup, appRunning, connected, requiresRestart, state, message, next
116225
116225
  - state: why devtools are or aren't live, measured from the running process. "connected"; "not_running"; "stale_process" (the process cannot reach this simulator's devtools endpoint \u2014 launched either before argent's instrumentation was in place or against an earlier tool-server's listener \u2014 so restart-app fixes it); "unregistered" (the process IS injected and pointed at this simulator's devtools endpoint yet the service never registered it, so restarting the app cannot help); "connecting" (the process IS injected but launched moments ago and is still connecting, so waiting is what helps); "indeterminate" (the process could not be inspected). Omitted when injectable is false, which is terminal on its own.
116226
116226
  - message: the remedy for that state, in full. Omitted when connected or non-injectable. Prefer it over inferring one from the booleans \u2014 it is the only field that can tell you to stop restarting the app.
116227
116227
  - nextLaunchWillBeInjected: if you launch this bundle now, native devtools env setup is already in place (always false for a non-injectable app)
116228
- - injectable: whether native devtools can be relied on to inject into this app. Apple system apps (bundle ids under com.apple.) are platform binaries with library validation, so the dylib cannot be counted on to load into them \u2014 it has been observed both loading and not loading, depending on the simulator runtime.
116228
+ - injectable: whether this app is a supported target for Argent native devtools. Apple system apps (bundle ids under com.apple.) are not: they are never the app under test, so the native tools refuse to read one.
116229
116229
 
116230
116230
  Call this before using app-scoped native hierarchy tools or native-network-logs.
116231
- If injectable is false: treat this as TERMINAL \u2014 injection cannot be relied on for this app, and no relaunch changes which way it goes. Do NOT restart/retry. Use the standard \`describe\` tool (its accessibility path reads the screen without injection) or \`screenshot\` (then interact by coordinate). Do not fall back to the native-devtools feature tools (native-describe-screen, native-find-views, native-full-hierarchy, native-network-logs, native-view-at-point, native-user-interactable-view-at-point) \u2014 they run the same injection precheck and fail with the same non-injectable error.
116231
+ If injectable is false: treat this as TERMINAL \u2014 the app is not a supported native-devtools target, and no relaunch changes that. Do NOT restart/retry. Use the standard \`describe\` tool (its accessibility path reads the screen without injection) or \`screenshot\` (then interact by coordinate). Do not fall back to the native-devtools feature tools (native-describe-screen, native-find-views, native-full-hierarchy, native-network-logs, native-view-at-point, native-user-interactable-view-at-point) \u2014 they run the same injection precheck and fail with the same non-injectable error.
116232
116232
  If appRunning is false and nextLaunchWillBeInjected is true: use launch-app normally.
116233
116233
  If requiresRestart is true: call restart-app once, then proceed with the native feature. Read state before acting on a second such reading \u2014 indeterminate reaches this rule too, and its line below bounds it at that one restart.
116234
116234
  If state is unregistered: do NOT restart the app again \u2014 it already launched under the terms a restart would recreate. Restart the tool-server (\`argent server stop && argent server start --detach\`), then retry. If it reads unregistered again after that restart, stop: the process loads argent's dylib but never dials, and no further restart on either side changes it \u2014 treat native devtools as unavailable, then use \`describe\` or \`screenshot\` and drive by coordinate.
@@ -144653,7 +144653,7 @@ var FULL_HIERARCHY_FIELDS = [
144653
144653
  ];
144654
144654
  async function unreadableHierarchyReason(nativeApi, bundleId) {
144655
144655
  if (!isInjectableBundleId(bundleId)) {
144656
- return `${bundleId} is an Apple system app: it is a platform binary with library validation, so argent's native devtools cannot be relied on to inject into it, and without them a flow has no view hierarchy to resolve selectors against. Replace the selector steps with coordinate ones \u2014 \`tap: { x: 0.5, y: 0.35 }\` takes a point directly and reads no tree \u2014 or target an app argent installs.`;
144656
+ return `${bundleId} is an Apple system app: it is never the app under test, so argent's native devtools refuse to read one, and without them a flow has no view hierarchy to resolve selectors against. Replace the selector steps with coordinate ones \u2014 \`tap: { x: 0.5, y: 0.35 }\` takes a point directly and reads no tree \u2014 or target an app argent installs.`;
144657
144657
  }
144658
144658
  const state3 = await nativeApi.appConnectionState(bundleId).catch(() => "indeterminate");
144659
144659
  if (state3 === "connected") {
@@ -144661,7 +144661,10 @@ async function unreadableHierarchyReason(nativeApi, bundleId) {
144661
144661
  }
144662
144662
  return `${buildAppStateMessage(bundleId, state3)} Flows resolve selectors against the full view hierarchy native devtools serve.`;
144663
144663
  }
144664
- async function queryFullHierarchyTree(registry2, device, launchedNativeApp) {
144664
+ function systemAppFlowTargetRefusal(bundleId) {
144665
+ return `${bundleId} is an Apple system app (com.apple.*) - never a valid flow target: it is not the app under test, and argent's native devtools refuse to read one (a system process either never services the read, or describes offscreen UI as if it were the launched app), so this flow has no view hierarchy to resolve selectors against and no relaunch or retry changes this verdict. Replace the selector steps with coordinate ones - \`tap: { x: 0.5, y: 0.35 }\` takes a point directly and reads no tree - or point this flow's \`launch\` step at the app under test.`;
144666
+ }
144667
+ async function queryFullHierarchyTree(registry2, device, target) {
144665
144668
  let nativeApi;
144666
144669
  try {
144667
144670
  const ndRef = nativeDevtoolsRef(device);
@@ -144672,22 +144675,113 @@ async function queryFullHierarchyTree(registry2, device, launchedNativeApp) {
144672
144675
  { cause: err }
144673
144676
  );
144674
144677
  }
144675
- if (launchedNativeApp !== void 0 && nativeApi.listConnectedBundleIds().length === 0) {
144676
- throw new Error(await unreadableHierarchyReason(nativeApi, launchedNativeApp));
144677
- }
144678
- let target;
144679
- try {
144680
- target = await resolveNativeTargetApp(nativeApi, void 0);
144681
- } catch (err) {
144682
- const timedOut = getFailureSignal(err)?.error_code === FAILURE_CODES.NATIVE_DEVTOOLS_RPC_TIMEOUT;
144683
- if (timedOut && launchedNativeApp !== void 0 && nativeApi.listConnectedBundleIds().includes(launchedNativeApp)) {
144684
- target = { bundleId: launchedNativeApp };
144685
- } else {
144686
- throw err;
144678
+ let bundleId;
144679
+ if (target?.pinned) {
144680
+ bundleId = target.bundleId;
144681
+ if (!isInjectableBundleId(bundleId)) {
144682
+ throw new FailureError(systemAppFlowTargetRefusal(bundleId), {
144683
+ error_code: FAILURE_CODES.NATIVE_DEVTOOLS_NOT_INJECTABLE,
144684
+ failure_stage: "flow_tree_pinned_target",
144685
+ failure_area: "tool_server",
144686
+ error_kind: "validation"
144687
+ });
144688
+ }
144689
+ if (!nativeApi.isConnected(bundleId)) {
144690
+ throw new FailureError(
144691
+ `${bundleId} lost its devtools connection after launch (the app crashed, was terminated, or its socket closed) - restart it (restart-app, or a flow \`launch\` step) so the full view hierarchy is readable; launch-app recovers only the causes that killed the process, since on iOS it just foregrounds one that is still alive`,
144692
+ {
144693
+ error_code: FAILURE_CODES.NATIVE_DEVTOOLS_NOT_CONNECTED,
144694
+ failure_stage: "flow_tree_pinned_target",
144695
+ failure_area: "tool_server",
144696
+ error_kind: "not_found"
144697
+ }
144698
+ );
144687
144699
  }
144700
+ let pinnedState;
144701
+ try {
144702
+ pinnedState = await nativeApi.getAppState(bundleId);
144703
+ target.probeAnswered = true;
144704
+ } catch (err) {
144705
+ if (getFailureSignal(err)?.error_code !== FAILURE_CODES.NATIVE_DEVTOOLS_RPC_TIMEOUT) {
144706
+ throw err;
144707
+ }
144708
+ if (target.probeAnswered) {
144709
+ throw new FailureError(
144710
+ `${bundleId} (the launched app) stopped answering Application.getState - the probe timed out although an earlier one in this run answered, so the app's main queue is no longer being serviced. For a pinned app that is usually the suspension iOS applies once a flow leaves it (e.g. a tap that opened another app), and a suspended app's hierarchy is not what is on screen; in-app work blocking the main thread past the probe timeout looks the same from here. Reading anyway parks on that same unserviced queue: certain to time out if the app is suspended, and paying the longer hierarchy timeout to find that out if it is not. If this flow's subject IS the other app, give the flow a \`launch:\` step for that app - it re-pins reads to it, and a pinned read probes only the app it names, so the silent ${bundleId} is never touched; \`tool: launch-app\` or \`tool: restart-app\` naming that app works too - each re-targets reads at the app it starts, and is how a recorded flow switches apps. A foreground-NEUTRAL raw \`tool:\` step does not work here, because demoting the pin sends reads back to auto-resolve, which probes every connection at once and is sunk by this same silent one. If the flow left ${bundleId} but is still about it, make it return before reading the UI. If it never left, the main thread is busy: raise the step's \`timeout:\` so the poll re-reads past the work, or \`launch\` ${bundleId} again.`,
144711
+ {
144712
+ // The timeout's own code, NOT
144713
+ // NATIVE_TARGET_SINGLE_APP_NOT_FOREGROUND: nothing answered, so no
144714
+ // app state was observed to classify it by.
144715
+ error_code: FAILURE_CODES.NATIVE_DEVTOOLS_RPC_TIMEOUT,
144716
+ failure_stage: "flow_tree_pinned_target",
144717
+ failure_area: "tool_server",
144718
+ error_kind: "timeout"
144719
+ },
144720
+ err instanceof Error ? { cause: err } : void 0
144721
+ );
144722
+ }
144723
+ }
144724
+ if (pinnedState && !chooseFrontmostConnectedApp([pinnedState])) {
144725
+ throw new FailureError(
144726
+ `${bundleId} (the launched app) has no foreground presence at all (applicationState=${pinnedState.applicationState}, foregroundActiveScenes=${pinnedState.foregroundActiveSceneCount}, foregroundInactiveScenes=${pinnedState.foregroundInactiveSceneCount}) - a step in this flow left the app (e.g. a tap that opened another app), so a read of its hierarchy would describe a screen that is not on screen. Transitional states are NOT refused here: an \`inactive\` app, or one still holding a foreground scene, is read as usual - under a system alert or mid-transition it is still the app on screen. If this flow's subject IS another app, give the flow a \`launch:\` step for that app - it re-pins reads to it, and no wedged sibling connection can sink a pinned read; a raw \`tool:\` step demotes the pin and returns reads to frontmost auto-resolve, which works when the app on screen answers but is sunk by a single wedged connection. Otherwise make the flow return to ${bundleId} before reading the UI, or \`launch\` it again.`,
144727
+ {
144728
+ error_code: FAILURE_CODES.NATIVE_TARGET_SINGLE_APP_NOT_FOREGROUND,
144729
+ failure_stage: "flow_tree_pinned_target",
144730
+ failure_area: "tool_server",
144731
+ error_kind: "validation"
144732
+ }
144733
+ );
144734
+ }
144735
+ } else {
144736
+ if (target && nativeApi.listConnectedBundleIds().length === 0) {
144737
+ throw new Error(await unreadableHierarchyReason(nativeApi, target.bundleId));
144738
+ }
144739
+ let resolved;
144740
+ try {
144741
+ resolved = await resolveNativeTargetApp(nativeApi, void 0);
144742
+ } catch (err) {
144743
+ const timedOut = getFailureSignal(err)?.error_code === FAILURE_CODES.NATIVE_DEVTOOLS_RPC_TIMEOUT;
144744
+ if (!timedOut || !target) throw err;
144745
+ if (!isInjectableBundleId(target.bundleId)) {
144746
+ throw new FailureError(
144747
+ systemAppFlowTargetRefusal(target.bundleId),
144748
+ {
144749
+ error_code: FAILURE_CODES.NATIVE_DEVTOOLS_NOT_INJECTABLE,
144750
+ failure_stage: "flow_tree_unpinned_hint",
144751
+ failure_area: "tool_server",
144752
+ error_kind: "validation"
144753
+ },
144754
+ err instanceof Error ? { cause: err } : void 0
144755
+ );
144756
+ }
144757
+ if (!nativeApi.listConnectedBundleIds().includes(target.bundleId)) throw err;
144758
+ let hintState;
144759
+ try {
144760
+ hintState = await nativeApi.getAppState(target.bundleId);
144761
+ } catch (probeErr) {
144762
+ if (getFailureSignal(probeErr)?.error_code === FAILURE_CODES.NATIVE_DEVTOOLS_RPC_TIMEOUT) {
144763
+ throw err;
144764
+ }
144765
+ throw probeErr;
144766
+ }
144767
+ if (!chooseFrontmostConnectedApp([hintState])) {
144768
+ throw new FailureError(
144769
+ `${target.bundleId} (the launched app) has no foreground presence at all (applicationState=${hintState.applicationState}, foregroundActiveScenes=${hintState.foregroundActiveSceneCount}, foregroundInactiveScenes=${hintState.foregroundInactiveSceneCount}) - auto-resolve's probe of every connection timed out and the read fell back to the launched app, but a step in this flow left it (e.g. a tap that opened another app), so reading its hierarchy would describe a screen that is not on screen. Transitional states are NOT refused here: an \`inactive\` app, or one still holding a foreground scene, is read as usual. If this flow's subject IS another app, give the flow a \`launch:\` step for that app; otherwise make the flow return to ${target.bundleId} before reading the UI, or \`launch\` it again.`,
144770
+ {
144771
+ error_code: FAILURE_CODES.NATIVE_TARGET_SINGLE_APP_NOT_FOREGROUND,
144772
+ failure_stage: "flow_tree_unpinned_hint",
144773
+ failure_area: "tool_server",
144774
+ error_kind: "validation"
144775
+ },
144776
+ err instanceof Error ? { cause: err } : void 0
144777
+ );
144778
+ }
144779
+ resolved = { bundleId: target.bundleId };
144780
+ }
144781
+ bundleId = resolved.bundleId;
144688
144782
  }
144689
144783
  const rawResult = await nativeApi.queryViewHierarchy(
144690
- target.bundleId,
144784
+ bundleId,
144691
144785
  "ViewHierarchy.getFullHierarchy",
144692
144786
  {
144693
144787
  fields: FULL_HIERARCHY_FIELDS,
@@ -144695,11 +144789,11 @@ async function queryFullHierarchyTree(registry2, device, launchedNativeApp) {
144695
144789
  }
144696
144790
  );
144697
144791
  if (rawResult.error) {
144698
- throw new Error(`getFullHierarchy failed for ${target.bundleId}: ${rawResult.error}`);
144792
+ throw new Error(`getFullHierarchy failed for ${bundleId}: ${rawResult.error}`);
144699
144793
  }
144700
144794
  if (!Array.isArray(rawResult.windows) || rawResult.windows.length === 0) {
144701
144795
  throw new Error(
144702
- `getFullHierarchy returned no windows for ${target.bundleId} \u2014 the app is not injectable (e.g. an Apple system app) or has no readable foreground window, so flows cannot resolve selectors against its view hierarchy`
144796
+ `getFullHierarchy returned no windows for ${bundleId} - it has no window attached to read (backgrounded, or its first window not attached yet), so flows cannot resolve selectors against its view hierarchy; foreground or relaunch it, and if that bundle id is a com.apple.* system process the read resolved to a background system app rather than the app under test, so give this flow a \`launch\` step to pin reads to the right app`
144703
144797
  );
144704
144798
  }
144705
144799
  const { tree, screen } = adaptFullHierarchy(rawResult);
@@ -144906,13 +145000,15 @@ async function queryVegaTree(device) {
144906
145000
  }
144907
145001
 
144908
145002
  // ../tool-server/src/tools/flows/flow-tree.ts
144909
- async function fetchFlowTree(registry2, device, launchedNativeApp) {
145003
+ async function fetchFlowTree(registry2, device, target) {
144910
145004
  const source = FLOW_TREE_SOURCES[device.platform];
144911
145005
  if (!source) return fetchTree(registry2, device);
144912
- return source(registry2, device, launchedNativeApp);
145006
+ return source(registry2, device, target);
144913
145007
  }
144914
145008
  var FLOW_TREE_SOURCES = {
144915
- ios: (registry2, device, launchedNativeApp) => queryFullHierarchyTree(registry2, device, launchedNativeApp),
145009
+ // Only iOS consumes the target: the platforms below resolve their tree
145010
+ // source per-device and never auto-resolve.
145011
+ ios: (registry2, device, target) => queryFullHierarchyTree(registry2, device, target),
144916
145012
  android: (registry2, device) => queryAndroidFullHierarchy(registry2, device),
144917
145013
  chromium: (registry2, device) => queryChromiumTree(registry2, device),
144918
145014
  vega: (_registry, device) => queryVegaTree(device)
@@ -145118,7 +145214,7 @@ function provenTreeOutage(env) {
145118
145214
  return proven && proven.deviceId === env.device.id ? proven.error : void 0;
145119
145215
  }
145120
145216
  function readFlowTree(env) {
145121
- return fetchFlowTree(env.registry, env.device, env.launchedNativeApp).then((data) => {
145217
+ return fetchFlowTree(env.registry, env.device, env.treeTarget).then((data) => {
145122
145218
  if (env.treeOutage) env.treeOutage.proven = void 0;
145123
145219
  return data;
145124
145220
  });
@@ -146173,7 +146269,11 @@ async function captureTapSelector(registry2, session, udid, point) {
146173
146269
  try {
146174
146270
  const device = resolveDevice(udid);
146175
146271
  const launched = recordedLaunchedApp(session, device.platform);
146176
- const { tree, source } = await fetchFlowTree(registry2, device, launched);
146272
+ const { tree, source } = await fetchFlowTree(
146273
+ registry2,
146274
+ device,
146275
+ launched ? { bundleId: launched, pinned: false, probeAnswered: false } : void 0
146276
+ );
146177
146277
  const node = nodeAtPoint(tree, point);
146178
146278
  if (!node) return { warning: "no element found under the tap; kept coordinates (brittle)" };
146179
146279
  const selector = deriveSelector(node);
@@ -149111,6 +149211,7 @@ async function runLaunch(state3, app) {
149111
149211
  reason: `no app id declared for platform "${device.platform}" \u2014 add a launch entry for it`
149112
149212
  };
149113
149213
  }
149214
+ state3.treeTarget = void 0;
149114
149215
  let restart;
149115
149216
  try {
149116
149217
  restart = await invokeOnDevice(env, "restart-app", { bundleId });
@@ -149125,7 +149226,7 @@ async function runLaunch(state3, app) {
149125
149226
  const gate = await treeSourceGate(registry2, device, bundleId, signal);
149126
149227
  if (signal?.aborted) return ABORTED_OUTCOME;
149127
149228
  if (gate) return { ok: false, reason: gate };
149128
- state3.launchedNativeApp = bundleId;
149229
+ state3.treeTarget = { bundleId, pinned: true, probeAnswered: false };
149129
149230
  return { ok: true };
149130
149231
  }
149131
149232
  async function runChromiumLaunch(state3, app) {
@@ -149312,7 +149413,11 @@ function createRunFlowTool(registry2) {
149312
149413
  },
149313
149414
  description: `Run a saved flow from the .argent/flows/ directory, or an explicit boundary-managed flow_path.
149314
149415
  Steps run in order: \`launch\` starts an app from scratch (terminate + relaunch) and waits until it is
149315
- ready; \`tool\` calls dispatch through the registry; \`tap\`/\`long-press\`/\`type\` resolve a selector to an
149416
+ ready (on iOS it also pins later element lookups to that app rather than auto-detecting the frontmost
149417
+ one); \`tool\` calls dispatch through the registry (a raw \`tool\` step ends that iOS pin, so lookups
149418
+ auto-detect again until the next \`launch\`, though a tool that cannot change the foreground app leaves the
149419
+ launched id as a fallback for a timed-out auto-detect, and \`launch-app\`/\`restart-app\` leave the id they
149420
+ started as that fallback instead); \`tap\`/\`long-press\`/\`type\` resolve a selector to an
149316
149421
  element and act on it (\`tap: { on, times: 2 }\` double-taps; \`long-press: { on, duration }\` presses and
149317
149422
  holds; \`tap\`/\`long-press\` alternatively take a raw normalized point \u2014 bare \`{ x, y }\` or \`on: { x, y }\`;
149318
149423
  any selector may scope its matches geometrically, the CSS combinators read off frames: \`within: <selector>\`
@@ -150021,14 +150126,17 @@ async function execLeafStep(state3, step, index, scope) {
150021
150126
  if (step.delayMs && !await sleepOrAbort(step.delayMs, signal)) {
150022
150127
  return { ...base, status: "skip", tool: step.name, reason: "run aborted during delay" };
150023
150128
  }
150129
+ if (FOREGROUND_CHANGING_TOOLS.has(step.name)) {
150130
+ state3.treeTarget = void 0;
150131
+ if (state3.treeOutage) state3.treeOutage.proven = void 0;
150132
+ } else if (state3.treeTarget?.pinned) {
150133
+ state3.treeTarget = { ...state3.treeTarget, pinned: false };
150134
+ if (state3.treeOutage) state3.treeOutage.proven = void 0;
150135
+ }
150136
+ if (isNestedOrchestratorTool(step.name) && state3.treeOutage) {
150137
+ state3.treeOutage.proven = void 0;
150138
+ }
150024
150139
  try {
150025
- if (FOREGROUND_CHANGING_TOOLS.has(step.name)) {
150026
- state3.launchedNativeApp = void 0;
150027
- if (state3.treeOutage) state3.treeOutage.proven = void 0;
150028
- }
150029
- if (isNestedOrchestratorTool(step.name) && state3.treeOutage) {
150030
- state3.treeOutage.proven = void 0;
150031
- }
150032
150140
  const result = await invokeSubTool(registry2, ctx, step.name, args);
150033
150141
  if (isUnmetUiWaitResult(step.name, result)) {
150034
150142
  const note = result.note;
@@ -150075,7 +150183,9 @@ async function execLeafStep(state3, step, index, scope) {
150075
150183
  }
150076
150184
  if (step.name === "launch-app" || step.name === "restart-app") {
150077
150185
  const launched = args.bundleId;
150078
- if (typeof launched === "string") state3.launchedNativeApp = launched;
150186
+ if (typeof launched === "string") {
150187
+ state3.treeTarget = { bundleId: launched, pinned: false, probeAnswered: false };
150188
+ }
150079
150189
  }
150080
150190
  return { ...base, status: "pass", tool: step.name, result, outputHint, args };
150081
150191
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.22.1-next.7",
3
+ "version": "0.22.1-next.9",
4
4
  "mcpName": "io.github.software-mansion/argent",
5
5
  "description": "MCP server for iOS Simulator and Android Emulator control",
6
6
  "license": "Apache-2.0",
@@ -70,6 +70,12 @@ On iOS, Android, and Chromium, an id absent from `describe` can still resolve in
70
70
 
71
71
  The recorder rechecks each successful `await-ui-element` against the runner tree. Follow any `message` warning and replay each conversion. On Vega, a mismatch usually means the screen changed. A `text` check can also select different elements from the same source. See [Live waits and checks](live-authoring.md#live-waits-and-checks).
72
72
 
73
+ **On iOS, a `launch:` step also decides which app the runner reads.** A successful `launch:` pins later runner-tree reads to that app, so a read probes only that app instead of fanning out over every connected one to find the frontmost. A pinned read still refuses, naming the reason, when the app has no foreground presence left, when it stops answering after an earlier read got through, when its devtools connection dropped, or when the pinned id is a `com.apple.*` system app.
74
+
75
+ Any raw `tool:` step ends the pin, because its effect on the screen is opaque to the runner, and reads auto-detect the frontmost app again until the next `launch:` re-pins. A tool that cannot change the foreground app leaves the launched id as an unpinned fallback, which takes the read only when auto-detection times out and the launched app vouches for itself with a probe of its own. `launch-app`, `restart-app`, `reinstall-app`, `open-url`, and `button` drop even that; `launch-app` and `restart-app` replace it with the app they just started, still unpinned. Nested `run:` fragments inherit both the pin and its clearing.
76
+
77
+ So on iOS recording and replay can read different apps, not only different projections: recording has no run state and always auto-detects the frontmost connected app, while a replay read between a `launch:` and the next raw `tool:` step reads the launched app.
78
+
73
79
  **On iOS, never copy a `role` from `describe` into a flow selector.** The runner derives iOS roles from the UIView class name and `describe` from accessibility traits, so a React Native `Pressable` (class `RCTView`) is `AXGroup` to the runner and `AXButton` to `describe`. Select on `id`/`text`, or confirm the role against the runner's own tree.
74
80
 
75
81
  When several nodes match, the directive decides:
@@ -99,7 +105,7 @@ Scopes can combine and nest, with at most six scope keys. Use strict selectors f
99
105
 
100
106
  Directives stop the flow on failure and skip later steps. `flow-execute` documents their shapes. The available directives are `launch`, `tap`, `long-press`, `type`, `scroll-to`, `pinch`, `rotate`, `await`, `assert`, `wait`, `snapshot`, `run`, `when`, `echo`, and `tool`.
101
107
 
102
- Use the launch map for cross-platform flows. A bare launch applies everywhere and becomes an app path on Chromium. The map takes `native:`, `ios:`, `android:`, `vega:`, and `chromium:`. `native:` is one id shared by iOS, Android, and Vega, and a per-platform key overrides it for that platform. `chromium:` accepts a relative or absolute app path. A launch that declares no id for the run's platform is an error, not a cue to switch platforms.
108
+ Use the launch map for cross-platform flows. A bare launch applies everywhere and becomes an app path on Chromium. The map takes `native:`, `ios:`, `android:`, `vega:`, and `chromium:`. `native:` is one id shared by iOS, Android, and Vega, and a per-platform key overrides it for that platform. `chromium:` accepts a relative or absolute app path. A launch that declares no id for the run's platform is an error, not a cue to switch platforms. On iOS, a successful launch also pins later tree reads to that app until the next raw `tool:` step, so read [The runner tree is not the discovery tree](#the-runner-tree-is-not-the-discovery-tree) when a read describes the wrong screen.
103
109
 
104
110
  ```yaml
105
111
  - launch: { native: com.acme.app, chromium: ../../app }
@@ -53,9 +53,9 @@ Use the same explicit UDID throughout. Multiple booted simulators are not an inj
53
53
 
54
54
  This fallback applies only to `com.apple.*` system apps. A connection failure in another app never authorizes it.
55
55
 
56
- Apple system apps are platform binaries with library validation, so the instrumentation cannot be relied on to load into them it has been seen both loading and not loading, depending on the simulator runtime. Either way it is no basis for a selector.
56
+ Argent refuses `com.apple.*` bundle ids at every native-devtools read that names one, because a system app is never the app under test. The instrumentation has been seen both loading and not loading into one, depending on the simulator runtime either way it is no basis for a selector. `restart-app`, `launch-app`, and `describe` still work on one; it just never gets a flow tree.
57
57
 
58
- Give the flow a `launch:` step as usual. On iOS the launch waits the full devtools budget out, then passes for one of these bundle ids: starting the app is all that step is for, and a coordinate-driven flow needs nothing more. The flow stays e2e; it just pays roughly sixteen seconds at the launch. Where the impossibility bites is selector resolution, and the first selector step reports it there — terminally, naming the coordinate remedy — rather than as a launch failure. The rest of the injection-free form:
58
+ Give the flow a `launch:` step as usual. On iOS the launch waits the full devtools budget out, then passes for one of these bundle ids: starting the app is all that step is for, and a coordinate-driven flow needs nothing more. The flow stays e2e; it just pays roughly sixteen seconds at the launch. Where the refusal bites is selector resolution, and the first selector step reports it there — terminally, naming the coordinate remedy — rather than as a launch failure. The rest of the tree-free form:
59
59
 
60
60
  - Raw `tool: await-ui-element` accessibility checks.
61
61
  - Point taps or long-presses derived from `describe`, each named by an echo.
@@ -66,9 +66,9 @@ Every point tap or long-press in such a flow passes **carrying a warning** for a
66
66
 
67
67
  A recorded wait carries a different warning: it adds about one second and reports that the runner tree is unavailable. That warning is expected too. Keep the wait as a raw `tool:` step.
68
68
 
69
- Report that the flow is injection-free and its coordinates are not portable. It cannot satisfy the QA contract. Report the artifact and platform blocker instead.
69
+ Report that the flow has no flow tree and its coordinates are not portable. It cannot satisfy the QA contract. Report the artifact and platform blocker instead.
70
70
 
71
- A normally injectable app that is broken in the environment gets the same coordinate-only treatment, but not the same launch: there the `launch:` step fails, since the gate withholds its verdict only for a bundle id injection may never reach. Start such a flow with a raw `tool: restart-app`, which terminates and relaunches without the readiness gate, and accept that the result is a **fragment** — its first non-echo step is not `launch:`, so the runner never classifies it as e2e, and it cannot complete `argent-qa-flows`, which requires a leading `launch:`. Report the blocker rather than labeling that fallback a completed QA test.
71
+ A normally injectable app that is broken in the environment gets the same coordinate-only treatment, but not the same launch: there the `launch:` step fails, since the gate withholds its verdict only for a bundle id argent refuses outright. Start such a flow with a raw `tool: restart-app`, which terminates and relaunches without the readiness gate, and accept that the result is a **fragment** — its first non-echo step is not `launch:`, so the runner never classifies it as e2e, and it cannot complete `argent-qa-flows`, which requires a leading `launch:`. Report the blocker rather than labeling that fallback a completed QA test.
72
72
 
73
73
  ## Tree source recovery on Android, Chromium, and Vega
74
74