touchpress 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,17 +2,17 @@ import { createAgentDeviceClient, normalizeAgentDeviceError } from "agent-device
2
2
  import pixelmatch from "pixelmatch";
3
3
  import { PNG } from "pngjs";
4
4
  //#region src/core/errors.ts
5
- var TangereError = class extends Error {
5
+ var TouchpressError = class extends Error {
6
6
  info;
7
7
  constructor(info) {
8
8
  super(formatError(info));
9
- this.name = "TangereError";
9
+ this.name = "TouchpressError";
10
10
  this.info = info;
11
11
  }
12
12
  };
13
13
  function formatError(info) {
14
14
  switch (info.kind) {
15
- case "config": return `Invalid tangere option: use.${info.field} ${info.detail}`;
15
+ case "config": return `Invalid touchpress option: use.${info.field} ${info.detail}`;
16
16
  case "device-in-use": return [
17
17
  `Device ${info.device} is held by ${info.owner === null ? "another session" : `session "${info.owner}"`}.`,
18
18
  `Release it with: ${info.releaseCommand}`,
@@ -59,6 +59,36 @@ function formatError(info) {
59
59
  info.screen
60
60
  ].join("\n");
61
61
  case "driver": return `${info.command} failed: ${describeFailure(info.failure)}`;
62
+ case "ai-not-configured": return ["device.act and device.extract need a model.", "Set use.aiModel to a gateway model id, such as 'anthropic/claude-sonnet-5', or to a provider model instance."].join("\n");
63
+ case "ai-missing-peer": return ["device.act and device.extract need the optional peer dependency 'ai'.", "Install it with: pnpm add -D ai"].join("\n");
64
+ case "ai-blocked": return [
65
+ `act stopped without finishing: ${info.summary}`,
66
+ ``,
67
+ `Instruction: ${info.instruction}`,
68
+ ``,
69
+ `Screen:`,
70
+ info.screen
71
+ ].join("\n");
72
+ case "ai-incomplete": return [
73
+ `act ran ${String(info.steps)} steps without reaching an outcome.`,
74
+ ``,
75
+ `Instruction: ${info.instruction}`,
76
+ ``,
77
+ "Raise maxSteps, or split the instruction into smaller ones.",
78
+ ``,
79
+ `Screen:`,
80
+ info.screen
81
+ ].join("\n");
82
+ case "ai-timeout": return [
83
+ `act ran out of its ${String(info.timeoutMs)}ms budget before reaching an outcome.`,
84
+ ``,
85
+ `Instruction: ${info.instruction}`,
86
+ ``,
87
+ "Raise the act timeout, and the test timeout with it.",
88
+ ``,
89
+ `Screen:`,
90
+ info.screen
91
+ ].join("\n");
62
92
  default: throw new Error(`unhandled error info ${JSON.stringify(info)}`);
63
93
  }
64
94
  }
@@ -183,14 +213,14 @@ function describeIndex(index) {
183
213
  * to the same values, so a caller that reaches the parser without the fixtures,
184
214
  * such as `preflight`, resolves identically.
185
215
  */
186
- const TANGERE_DEFAULTS = {
216
+ const TOUCHPRESS_DEFAULTS = {
187
217
  relaunch: "per-test",
188
218
  onDeviceInUse: "fail",
189
219
  settleQuietMs: 500,
190
220
  launchTimeout: 9e4,
191
221
  dismissDevOverlay: false,
192
222
  evidence: "on-failure",
193
- sessionPrefix: "tangere"
223
+ sessionPrefix: "touchpress"
194
224
  };
195
225
  const DEFAULT_ACTION_TIMEOUT_MS = 1e4;
196
226
  const ROLES = [
@@ -226,18 +256,18 @@ function parseDeviceOptions(raw) {
226
256
  readyWhen: parseReadyWhen(read(raw, "readyWhen")),
227
257
  device: parseDeviceChoice(read(raw, "deviceName")),
228
258
  launchUrl: optionalText("launchUrl", read(raw, "launchUrl")),
229
- relaunch: oneOf("relaunch", read(raw, "relaunch"), ["per-test", "per-worker"], TANGERE_DEFAULTS.relaunch),
230
- onDeviceInUse: oneOf("onDeviceInUse", read(raw, "onDeviceInUse"), ["fail", "reclaim"], TANGERE_DEFAULTS.onDeviceInUse),
259
+ relaunch: oneOf("relaunch", read(raw, "relaunch"), ["per-test", "per-worker"], TOUCHPRESS_DEFAULTS.relaunch),
260
+ onDeviceInUse: oneOf("onDeviceInUse", read(raw, "onDeviceInUse"), ["fail", "reclaim"], TOUCHPRESS_DEFAULTS.onDeviceInUse),
231
261
  actionTimeout: parseActionTimeout(read(raw, "actionTimeout")),
232
- settleQuietMs: positive("settleQuietMs", read(raw, "settleQuietMs"), TANGERE_DEFAULTS.settleQuietMs),
233
- launchTimeout: positive("launchTimeout", read(raw, "launchTimeout"), TANGERE_DEFAULTS.launchTimeout),
234
- dismissDevOverlay: flag("dismissDevOverlay", read(raw, "dismissDevOverlay"), TANGERE_DEFAULTS.dismissDevOverlay),
262
+ settleQuietMs: positive("settleQuietMs", read(raw, "settleQuietMs"), TOUCHPRESS_DEFAULTS.settleQuietMs),
263
+ launchTimeout: positive("launchTimeout", read(raw, "launchTimeout"), TOUCHPRESS_DEFAULTS.launchTimeout),
264
+ dismissDevOverlay: flag("dismissDevOverlay", read(raw, "dismissDevOverlay"), TOUCHPRESS_DEFAULTS.dismissDevOverlay),
235
265
  evidence: oneOf("evidence", read(raw, "evidence"), [
236
266
  "on-failure",
237
267
  "always",
238
268
  "off"
239
- ], TANGERE_DEFAULTS.evidence),
240
- sessionPrefix: text("sessionPrefix", read(raw, "sessionPrefix"), TANGERE_DEFAULTS.sessionPrefix)
269
+ ], TOUCHPRESS_DEFAULTS.evidence),
270
+ sessionPrefix: text("sessionPrefix", read(raw, "sessionPrefix"), TOUCHPRESS_DEFAULTS.sessionPrefix)
241
271
  };
242
272
  }
243
273
  /** A non-object source reads as every key unset, so a caller that passes nothing fails on the first required key. */
@@ -246,7 +276,7 @@ function read(source, key) {
246
276
  return key in source ? Reflect.get(source, key) : void 0;
247
277
  }
248
278
  /**
249
- * Playwright's own `use.actionTimeout`, not one of tangere's. Playwright defaults
279
+ * Playwright's own `use.actionTimeout`, not one of touchpress's. Playwright defaults
250
280
  * it to 0, which means "no timeout" there and would mean "give up at once" here,
251
281
  * so 0 falls back the way an unset value does.
252
282
  */
@@ -348,96 +378,13 @@ function text(field, value, fallback) {
348
378
  return value;
349
379
  }
350
380
  function fail(field, detail) {
351
- return new TangereError({
381
+ return new TouchpressError({
352
382
  kind: "config",
353
383
  field,
354
384
  detail
355
385
  });
356
386
  }
357
387
  //#endregion
358
- //#region src/core/checks.ts
359
- /**
360
- * A `many` outcome never passes anything but `toHaveCount`. An ambiguous locator
361
- * is a strictness violation, and it reports through the same message path as a
362
- * plain mismatch rather than guessing which node was meant.
363
- */
364
- function evaluate(check, resolution) {
365
- if (check.name === "toHaveCount") {
366
- const count = countOf(resolution);
367
- return {
368
- pass: count === check.expected,
369
- actual: String(count)
370
- };
371
- }
372
- if (resolution.outcome === "none") return {
373
- pass: false,
374
- actual: null
375
- };
376
- if (resolution.outcome === "many") return {
377
- pass: false,
378
- actual: `${String(resolution.nodes.length)} matching nodes`
379
- };
380
- const node = resolution.node;
381
- switch (check.name) {
382
- case "toBeVisible": return {
383
- pass: true,
384
- actual: describeNode(node)
385
- };
386
- case "toHaveText": {
387
- const text = node.name ?? node.value;
388
- return {
389
- pass: matchesText(check.expected, text),
390
- actual: text === null ? null : `"${text}"`
391
- };
392
- }
393
- case "toHaveValue": return {
394
- pass: matchesText(check.expected, node.value),
395
- actual: node.value === null ? null : `"${node.value}"`
396
- };
397
- case "toBeEnabled": return {
398
- pass: node.enabled,
399
- actual: node.enabled ? "enabled" : "disabled"
400
- };
401
- case "toBeSelected": return {
402
- pass: node.selected,
403
- actual: node.selected ? "selected" : "not selected"
404
- };
405
- case "toBeFocused": return {
406
- pass: node.focused,
407
- actual: node.focused ? "focused" : "not focused"
408
- };
409
- default: throw new Error(`unhandled check ${JSON.stringify(check)}`);
410
- }
411
- }
412
- function countOf(resolution) {
413
- switch (resolution.outcome) {
414
- case "one": return 1;
415
- case "none": return 0;
416
- case "many": return resolution.nodes.length;
417
- default: throw new Error(`unhandled resolution ${JSON.stringify(resolution)}`);
418
- }
419
- }
420
- /** The `Expected:` line. */
421
- function describeCheck(check) {
422
- switch (check.name) {
423
- case "toBeVisible": return "visible";
424
- case "toHaveText": return `text ${describeExpected(check.expected)}`;
425
- case "toHaveValue": return `value ${describeExpected(check.expected)}`;
426
- case "toBeEnabled": return "enabled";
427
- case "toBeSelected": return "selected";
428
- case "toBeFocused": return "focused";
429
- case "toHaveCount": return `count ${String(check.expected)}`;
430
- default: throw new Error(`unhandled check ${JSON.stringify(check)}`);
431
- }
432
- }
433
- function describeExpected(match) {
434
- return match.kind === "regex" ? String(match.value) : `"${match.value}"`;
435
- }
436
- function describeNode(node) {
437
- const name = node.name === null ? "" : ` "${node.name}"`;
438
- return `${node.ref} [${node.role}]${name}`;
439
- }
440
- //#endregion
441
388
  //#region src/core/screen.ts
442
389
  const IOS_ROLES = {
443
390
  Application: "application",
@@ -716,6 +663,7 @@ function renderNode(node) {
716
663
  //#endregion
717
664
  //#region src/core/report.ts
718
665
  const FILL_TEXT_LIMIT = 40;
666
+ const PROMPT_LIMIT = 80;
719
667
  /** Rendered here rather than in an adapter so every runner produces the same text. */
720
668
  function renderTitle(record) {
721
669
  switch (record.kind) {
@@ -729,18 +677,21 @@ function renderTitle(record) {
729
677
  case "relaunch": return `relaunch ${record.app}`;
730
678
  case "dismiss-overlay": return "dismiss the React Native dev overlay";
731
679
  case "screenshot": return `screenshot ${record.path}`;
680
+ case "act": return `act "${truncate(record.instruction, PROMPT_LIMIT)}"`;
681
+ case "extract": return `extract "${truncate(record.question, PROMPT_LIMIT)}"`;
682
+ case "tool": return record.target === void 0 ? record.name : `${record.name} ${record.target}`;
732
683
  default: throw new Error(`unhandled action record ${JSON.stringify(record)}`);
733
684
  }
734
685
  }
735
686
  function renderTyped(typed) {
736
687
  switch (typed.kind) {
737
- case "text": return `type "${truncate(typed.value)}"`;
688
+ case "text": return `type "${truncate(typed.value, FILL_TEXT_LIMIT)}"`;
738
689
  case "hidden": return `type ${String(typed.length)} characters`;
739
690
  default: throw new Error(`unhandled typed value ${JSON.stringify(typed)}`);
740
691
  }
741
692
  }
742
- function truncate(text) {
743
- return text.length <= FILL_TEXT_LIMIT ? text : `${text.slice(0, FILL_TEXT_LIMIT)}...`;
693
+ function truncate(text, limit) {
694
+ return text.length <= limit ? text : `${text.slice(0, limit)}...`;
744
695
  }
745
696
  /** Discards everything. The default for scripts, unit tests, and runners with no reporting. */
746
697
  const silentSink = {
@@ -750,6 +701,89 @@ const silentSink = {
750
701
  outputPath: (fileName) => fileName
751
702
  };
752
703
  //#endregion
704
+ //#region src/core/checks.ts
705
+ /**
706
+ * A `many` outcome never passes anything but `toHaveCount`. An ambiguous locator
707
+ * is a strictness violation, and it reports through the same message path as a
708
+ * plain mismatch rather than guessing which node was meant.
709
+ */
710
+ function evaluate(check, resolution) {
711
+ if (check.name === "toHaveCount") {
712
+ const count = countOf(resolution);
713
+ return {
714
+ pass: count === check.expected,
715
+ actual: String(count)
716
+ };
717
+ }
718
+ if (resolution.outcome === "none") return {
719
+ pass: false,
720
+ actual: null
721
+ };
722
+ if (resolution.outcome === "many") return {
723
+ pass: false,
724
+ actual: `${String(resolution.nodes.length)} matching nodes`
725
+ };
726
+ const node = resolution.node;
727
+ switch (check.name) {
728
+ case "toBeVisible": return {
729
+ pass: true,
730
+ actual: describeNode(node)
731
+ };
732
+ case "toHaveText": {
733
+ const text = node.name ?? node.value;
734
+ return {
735
+ pass: matchesText(check.expected, text),
736
+ actual: text === null ? null : `"${text}"`
737
+ };
738
+ }
739
+ case "toHaveValue": return {
740
+ pass: matchesText(check.expected, node.value),
741
+ actual: node.value === null ? null : `"${node.value}"`
742
+ };
743
+ case "toBeEnabled": return {
744
+ pass: node.enabled,
745
+ actual: node.enabled ? "enabled" : "disabled"
746
+ };
747
+ case "toBeSelected": return {
748
+ pass: node.selected,
749
+ actual: node.selected ? "selected" : "not selected"
750
+ };
751
+ case "toBeFocused": return {
752
+ pass: node.focused,
753
+ actual: node.focused ? "focused" : "not focused"
754
+ };
755
+ default: throw new Error(`unhandled check ${JSON.stringify(check)}`);
756
+ }
757
+ }
758
+ function countOf(resolution) {
759
+ switch (resolution.outcome) {
760
+ case "one": return 1;
761
+ case "none": return 0;
762
+ case "many": return resolution.nodes.length;
763
+ default: throw new Error(`unhandled resolution ${JSON.stringify(resolution)}`);
764
+ }
765
+ }
766
+ /** The `Expected:` line. */
767
+ function describeCheck(check) {
768
+ switch (check.name) {
769
+ case "toBeVisible": return "visible";
770
+ case "toHaveText": return `text ${describeExpected(check.expected)}`;
771
+ case "toHaveValue": return `value ${describeExpected(check.expected)}`;
772
+ case "toBeEnabled": return "enabled";
773
+ case "toBeSelected": return "selected";
774
+ case "toBeFocused": return "focused";
775
+ case "toHaveCount": return `count ${String(check.expected)}`;
776
+ default: throw new Error(`unhandled check ${JSON.stringify(check)}`);
777
+ }
778
+ }
779
+ function describeExpected(match) {
780
+ return match.kind === "regex" ? String(match.value) : `"${match.value}"`;
781
+ }
782
+ function describeNode(node) {
783
+ const name = node.name === null ? "" : ` "${node.name}"`;
784
+ return `${node.ref} [${node.role}]${name}`;
785
+ }
786
+ //#endregion
753
787
  //#region src/core/session.ts
754
788
  const READY_POLL_MS = 250;
755
789
  const SNAPSHOT_TIMEOUT_MS = 15e3;
@@ -806,7 +840,7 @@ async function openWithRecovery(driver, options, name, deviceName) {
806
840
  if (failure === null) throw error;
807
841
  if (failure.kind === "device-busy") {
808
842
  const owner = failure.owner;
809
- if (!(owner !== null && (owner.startsWith(options.sessionPrefix) || options.onDeviceInUse === "reclaim"))) throw new TangereError({
843
+ if (!(owner !== null && (owner.startsWith(options.sessionPrefix) || options.onDeviceInUse === "reclaim"))) throw new TouchpressError({
810
844
  kind: "device-in-use",
811
845
  owner,
812
846
  device: deviceName ?? options.platform,
@@ -820,7 +854,7 @@ async function openWithRecovery(driver, options, name, deviceName) {
820
854
  await driver.close(name);
821
855
  return await driver.open(request);
822
856
  }
823
- throw new TangereError({
857
+ throw new TouchpressError({
824
858
  kind: "launch-failed",
825
859
  app: options.app,
826
860
  device: deviceName ?? options.platform,
@@ -877,7 +911,7 @@ function createSession(driver, options, name, binding) {
877
911
  if (remaining <= 0) break;
878
912
  await sleep(Math.min(READY_POLL_MS, remaining));
879
913
  }
880
- throw new TangereError({
914
+ throw new TouchpressError({
881
915
  kind: "not-ready",
882
916
  locator: describeQuery(options.readyWhen),
883
917
  timeoutMs: Date.now() - started,
@@ -920,12 +954,12 @@ function createSession(driver, options, name, binding) {
920
954
  };
921
955
  }
922
956
  function unusable(state) {
923
- if (state.phase === "broken") return new TangereError({
957
+ if (state.phase === "broken") return new TouchpressError({
924
958
  kind: "driver",
925
959
  command: "device command",
926
960
  failure: state.failure
927
961
  });
928
- return new TangereError({
962
+ return new TouchpressError({
929
963
  kind: "session-closed",
930
964
  command: "run a device command"
931
965
  });
@@ -943,7 +977,7 @@ function sessionName(options, project, slot) {
943
977
  return `${options.sessionPrefix}-${project === "" ? "default" : project}-${String(slot)}`;
944
978
  }
945
979
  function failureOf(error) {
946
- if (!(error instanceof TangereError)) return null;
980
+ if (!(error instanceof TouchpressError)) return null;
947
981
  if (error.info.kind === "driver") return error.info.failure;
948
982
  if (error.info.kind === "launch-failed") return error.info.failure;
949
983
  return null;
@@ -1240,7 +1274,7 @@ function perform(session, sink, record, options, dispatch, write) {
1240
1274
  let target = record.query;
1241
1275
  let lastActual = null;
1242
1276
  let screen = await device.capture();
1243
- const unconfirmed = (expected) => new TangereError({
1277
+ const unconfirmed = (expected) => new TouchpressError({
1244
1278
  kind: "fill-unconfirmed",
1245
1279
  locator,
1246
1280
  expected,
@@ -1288,7 +1322,7 @@ function perform(session, sink, record, options, dispatch, write) {
1288
1322
  continue;
1289
1323
  }
1290
1324
  const remaining = deadline - Date.now();
1291
- if (remaining <= 0) throw new TangereError({
1325
+ if (remaining <= 0) throw new TouchpressError({
1292
1326
  kind: "not-found",
1293
1327
  locator,
1294
1328
  timeoutMs: timeout,
@@ -1326,7 +1360,7 @@ function scrollIntoView(session, sink, query, options) {
1326
1360
  if (resolution.outcome === "many") throw ambiguous(locator, resolution.nodes, screen);
1327
1361
  if (resolution.outcome === "one") return;
1328
1362
  const remaining = deadline - Date.now();
1329
- if (remaining <= 0 || !await search.step(screen, query, remaining)) throw new TangereError({
1363
+ if (remaining <= 0 || !await search.step(screen, query, remaining)) throw new TouchpressError({
1330
1364
  kind: "not-found",
1331
1365
  locator,
1332
1366
  timeoutMs: timeout,
@@ -1349,7 +1383,7 @@ function reportingScrolls(device, sink) {
1349
1383
  };
1350
1384
  }
1351
1385
  function ambiguous(locator, nodes, screen) {
1352
- return new TangereError({
1386
+ return new TouchpressError({
1353
1387
  kind: "strict-mode",
1354
1388
  locator,
1355
1389
  matches: nodes.map((node) => describeNode(node)),
@@ -1495,7 +1529,7 @@ function createAgentDeviceDriver(client, session, selection) {
1495
1529
  try {
1496
1530
  return await body();
1497
1531
  } catch (error) {
1498
- throw new TangereError({
1532
+ throw new TouchpressError({
1499
1533
  kind: "driver",
1500
1534
  command,
1501
1535
  failure: classifyError(error)
@@ -1862,4 +1896,4 @@ function notBooted(platform, name, booted) {
1862
1896
  return `No booted ${platform} device is named '${name}'. Booted right now: ${booted.map((device) => `'${device.name}'`).join(", ")}. Set use.deviceName to one of those or boot '${name}'.`;
1863
1897
  }
1864
1898
  //#endregion
1865
- export { textMatch as A, describeCheck as C, parseDeviceOptions as D, deviceNameForSlot as E, describeQuery as O, resolve as S, TANGERE_DEFAULTS as T, sleep as _, sizeOf as a, parseScreen as b, createClient as c, createScrollSearch as d, directionToward as f, sessionName as g, openSession as h, relativeTo as i, TangereError as j, normalizeText as k, captureEvidence as l, probe as m, compareScreenshot as n, toPixelBox as o, formatFailure as p, cropScreenshot as r, createAgentDeviceDriver as s, preflight as t, createDevice as u, renderTitle as v, evaluate as w, renderScreen as x, silentSink as y };
1899
+ export { textMatch as A, renderScreen as C, parseDeviceOptions as D, deviceNameForSlot as E, describeQuery as O, parseScreen as S, TOUCHPRESS_DEFAULTS as T, sleep as _, sizeOf as a, renderTitle as b, createClient as c, createScrollSearch as d, directionToward as f, sessionName as g, openSession as h, relativeTo as i, TouchpressError as j, normalizeText as k, captureEvidence as l, probe as m, compareScreenshot as n, toPixelBox as o, formatFailure as p, cropScreenshot as r, createAgentDeviceDriver as s, preflight as t, createDevice as u, describeCheck as v, resolve as w, silentSink as x, evaluate as y };
@@ -323,7 +323,7 @@ type CaptureOptions = {
323
323
  * Contract every implementation owes the core. `open` converges when called on
324
324
  * an already-open session. Mutations take a `PinnedRef` and never a selector, so
325
325
  * the driver's own matcher is never a second opinion on which node was meant.
326
- * Every failure throws a `TangereError` carrying a `DeviceFailure`.
326
+ * Every failure throws a `TouchpressError` carrying a `DeviceFailure`.
327
327
  */
328
328
  type DeviceDriver = {
329
329
  /** Takes no session, so it is the one call that binds nothing. */
@@ -448,6 +448,18 @@ type ActionRecord = {
448
448
  } | {
449
449
  readonly kind: 'screenshot';
450
450
  readonly path: string;
451
+ } | {
452
+ readonly kind: 'act';
453
+ readonly instruction: string;
454
+ } | {
455
+ readonly kind: 'extract';
456
+ readonly question: string;
457
+ } |
458
+ /** One command the model ran inside an `act`. `target` is its snapshot ref, or a scroll's direction. */
459
+ {
460
+ readonly kind: 'tool';
461
+ readonly name: string;
462
+ readonly target?: string;
451
463
  };
452
464
  /** A boxed step reports as one line rather than as something to open. */
453
465
  type StepOptions = {
@@ -475,14 +487,14 @@ declare const silentSink: ActionSink;
475
487
  //#endregion
476
488
  //#region src/core/config.d.ts
477
489
  /**
478
- * The keys tangere adds to Playwright's `use`. Each is its own option fixture, so
490
+ * The keys touchpress adds to Playwright's `use`. Each is its own option fixture, so
479
491
  * a project overrides one without restating the rest.
480
492
  *
481
493
  * Playwright types `use` as a partial of this, so a config may leave any key out.
482
494
  * `parseDeviceOptions` is what makes `platform`, `app`, and `readyWhen` required.
483
495
  * It runs once at worker start and nothing downstream re-validates.
484
496
  */
485
- type TangereOptions = {
497
+ type TouchpressOptions = {
486
498
  /** Required. */
487
499
  platform: Platform | undefined;
488
500
  /** Required. Bundle id on iOS, package name on Android. Never a path to an artifact. */
@@ -513,7 +525,7 @@ type TangereOptions = {
513
525
  dismissDevOverlay: boolean;
514
526
  /** @default 'on-failure' */
515
527
  evidence: 'on-failure' | 'always' | 'off';
516
- /** @default 'tangere'. Session names are `${prefix}-${project}-${parallelIndex}`. */
528
+ /** @default 'touchpress'. Session names are `${prefix}-${project}-${parallelIndex}`. */
517
529
  sessionPrefix: string;
518
530
  };
519
531
  /**
@@ -521,7 +533,7 @@ type TangereOptions = {
521
533
  * to the same values, so a caller that reaches the parser without the fixtures,
522
534
  * such as `preflight`, resolves identically.
523
535
  */
524
- declare const TANGERE_DEFAULTS: Omit<TangereOptions, 'platform' | 'app' | 'readyWhen' | 'deviceName' | 'launchUrl'>;
536
+ declare const TOUCHPRESS_DEFAULTS: Omit<TouchpressOptions, 'platform' | 'app' | 'readyWhen' | 'deviceName' | 'launchUrl'>;
525
537
  /** The config-file friendly subset of a locator. Matched by the same rules as any other query. */
526
538
  type ReadyQuery = {
527
539
  text: string;
@@ -825,8 +837,28 @@ type ErrorInfo = {
825
837
  readonly kind: 'driver';
826
838
  readonly command: string;
827
839
  readonly failure: DeviceFailure;
840
+ } | {
841
+ readonly kind: 'ai-not-configured';
842
+ } | {
843
+ readonly kind: 'ai-missing-peer';
844
+ } | {
845
+ readonly kind: 'ai-blocked';
846
+ readonly instruction: string;
847
+ /** The model's own account of what stopped it. */
848
+ readonly summary: string;
849
+ readonly screen: string;
850
+ } | {
851
+ readonly kind: 'ai-incomplete';
852
+ readonly instruction: string;
853
+ readonly steps: number;
854
+ readonly screen: string;
855
+ } | {
856
+ readonly kind: 'ai-timeout';
857
+ readonly instruction: string;
858
+ readonly timeoutMs: number;
859
+ readonly screen: string;
828
860
  };
829
- declare class TangereError extends Error {
861
+ declare class TouchpressError extends Error {
830
862
  readonly info: ErrorInfo;
831
863
  constructor(info: ErrorInfo);
832
864
  }
@@ -848,6 +880,6 @@ type PreflightReport = {
848
880
  * runner names every one of them at once. A malformed config still throws, the
849
881
  * way it does everywhere else. `driver` is the seam tests inject.
850
882
  */
851
- declare function preflight(options: Partial<TangereOptions>, driver?: DeviceDriver): Promise<PreflightReport>;
883
+ declare function preflight(options: Partial<TouchpressOptions>, driver?: DeviceDriver): Promise<PreflightReport>;
852
884
  //#endregion
853
- export { Tree as $, deviceNameForSlot as A, ProbeResult as B, openSession as C, ResolvedOptions as D, ReadyQuery as E, StepOptions as F, CaptureOptions as G, formatFailure as H, Typed as I, DeviceInfo as J, DeviceDriver as K, renderTitle as L, ActionRecord as M, ActionSink as N, TANGERE_DEFAULTS as O, EvidenceFile as P, Settled as Q, silentSink as R, SessionState as S, DeviceChoice as T, probe as U, ProbeTarget as V, Binding as W, OpenRequest as X, DeviceSelection as Y, ScrollDirection as Z, RoleOptions as _, Screen as _t, ExpectedValue as a, Filter as at, DeviceSession as b, renderScreen as bt, ScrollSearch as c, TextMatch as ct, directionToward as d, textMatch as dt, Check as et, ActionOptions as f, PinnedRef as ft, Locator as g, Resolution as gt, FilterOptions as h, Rect as ht, ErrorInfo as i, evaluate as it, parseDeviceOptions as j, TangereOptions as k, ScrollTrail as l, describeQuery as lt, FillOptions as m, RawSnapshot as mt, PreflightReport as n, Verdict as nt, TangereError as o, Query as ot, Device as p, Platform as pt, DeviceFailure as q, preflight as r, describeCheck as rt, ScrollDevice as s, Role as st, PreflightDevice as t, CheckName as tt, createScrollSearch as u, normalizeText as ut, TextOptions as v, ScreenNode as vt, sessionName as w, OpenSessionInput as x, resolve as xt, createDevice as y, parseScreen as yt, ProbeOptions as z };
885
+ export { Tree as $, deviceNameForSlot as A, ProbeResult as B, openSession as C, ResolvedOptions as D, ReadyQuery as E, StepOptions as F, CaptureOptions as G, formatFailure as H, Typed as I, DeviceInfo as J, DeviceDriver as K, renderTitle as L, ActionRecord as M, ActionSink as N, TOUCHPRESS_DEFAULTS as O, EvidenceFile as P, Settled as Q, silentSink as R, SessionState as S, DeviceChoice as T, probe as U, ProbeTarget as V, Binding as W, OpenRequest as X, DeviceSelection as Y, ScrollDirection as Z, RoleOptions as _, Screen as _t, ExpectedValue as a, Filter as at, DeviceSession as b, renderScreen as bt, ScrollSearch as c, TextMatch as ct, directionToward as d, textMatch as dt, Check as et, ActionOptions as f, PinnedRef as ft, Locator as g, Resolution as gt, FilterOptions as h, Rect as ht, ErrorInfo as i, evaluate as it, parseDeviceOptions as j, TouchpressOptions as k, ScrollTrail as l, describeQuery as lt, FillOptions as m, RawSnapshot as mt, PreflightReport as n, Verdict as nt, TouchpressError as o, Query as ot, Device as p, Platform as pt, DeviceFailure as q, preflight as r, describeCheck as rt, ScrollDevice as s, Role as st, PreflightDevice as t, CheckName as tt, createScrollSearch as u, normalizeText as ut, TextOptions as v, ScreenNode as vt, sessionName as w, OpenSessionInput as x, resolve as xt, createDevice as y, parseScreen as yt, ProbeOptions as z };
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "touchpress",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "End-to-end testing for mobile apps.",
5
- "homepage": "https://github.com/wobsoriano/tangere#readme",
5
+ "homepage": "https://github.com/wobsoriano/touchpress#readme",
6
6
  "bugs": {
7
- "url": "https://github.com/wobsoriano/tangere/issues"
7
+ "url": "https://github.com/wobsoriano/touchpress/issues"
8
8
  },
9
9
  "license": "MIT",
10
10
  "author": "Robert Soriano",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/wobsoriano/tangere.git"
13
+ "url": "git+https://github.com/wobsoriano/touchpress.git"
14
14
  },
15
15
  "files": [
16
16
  "dist"
@@ -33,13 +33,21 @@
33
33
  "@playwright/test": "1.63.0",
34
34
  "@types/node": "^26.1.1",
35
35
  "@types/pngjs": "^6.0.5",
36
+ "ai": "^7.0.93",
36
37
  "bumpp": "^11.1.0",
37
38
  "typescript": "^7.0.2",
38
39
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
39
- "vite-plus": "0.3.0"
40
+ "vite-plus": "0.3.0",
41
+ "zod": "^4.5.4"
40
42
  },
41
43
  "peerDependencies": {
42
- "@playwright/test": ">=1.63"
44
+ "@playwright/test": ">=1.63",
45
+ "ai": "^6.0.0 || ^7.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "ai": {
49
+ "optional": true
50
+ }
43
51
  },
44
52
  "devEngines": {
45
53
  "packageManager": {