testdriverai 6.0.16-canary.ea858d7.0 → 6.0.16

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 (109) hide show
  1. package/agent/events.js +12 -2
  2. package/agent/index.js +34 -33
  3. package/agent/lib/censorship.js +70 -0
  4. package/agent/lib/commander.js +25 -16
  5. package/agent/lib/commands.js +23 -19
  6. package/debugger/index.html +2 -0
  7. package/docs/_scripts/link-replacer.js +164 -0
  8. package/docs/account/dashboard.mdx +5 -2
  9. package/docs/account/pricing.mdx +12 -5
  10. package/docs/account/projects.mdx +3 -2
  11. package/docs/account/team.mdx +7 -3
  12. package/docs/apps/chrome-extensions.mdx +8 -2
  13. package/docs/apps/desktop-apps.mdx +5 -5
  14. package/docs/apps/mobile-apps.mdx +7 -2
  15. package/docs/cli/overview.mdx +6 -6
  16. package/docs/commands/assert.mdx +9 -6
  17. package/docs/commands/hover-image.mdx +9 -6
  18. package/docs/commands/if.mdx +11 -6
  19. package/docs/commands/match-image.mdx +13 -6
  20. package/docs/commands/remember.mdx +10 -5
  21. package/docs/commands/run.mdx +8 -2
  22. package/docs/commands/type.mdx +11 -5
  23. package/docs/commands/wait-for-image.mdx +9 -3
  24. package/docs/commands/wait.mdx +9 -4
  25. package/docs/features/generation.mdx +1 -1
  26. package/docs/features/reusable-snippets.mdx +8 -5
  27. package/docs/features/selectorless.mdx +13 -6
  28. package/docs/features/visual-assertions.mdx +53 -39
  29. package/docs/getting-started/editing.mdx +1 -1
  30. package/docs/guide/assertions.mdx +12 -17
  31. package/docs/guide/code.mdx +2 -2
  32. package/docs/guide/lifecycle.mdx +3 -3
  33. package/docs/guide/locating.mdx +13 -6
  34. package/docs/guide/waiting.mdx +41 -32
  35. package/docs/interactive/assert.mdx +11 -0
  36. package/docs/interactive/generate.mdx +11 -0
  37. package/docs/interactive/run.mdx +8 -0
  38. package/docs/interactive/save.mdx +7 -0
  39. package/docs/interactive/undo.mdx +11 -3
  40. package/docs/overview/comparison.mdx +50 -49
  41. package/docs/overview/faq.mdx +40 -2
  42. package/docs/overview/performance.mdx +25 -25
  43. package/docs/overview/what-is-testdriver.mdx +24 -22
  44. package/docs/scenarios/ai-chatbot.mdx +6 -5
  45. package/docs/scenarios/cookie-banner.mdx +4 -1
  46. package/docs/scenarios/file-upload.mdx +7 -4
  47. package/docs/scenarios/form-filling.mdx +4 -1
  48. package/docs/scenarios/pdf-generation.mdx +5 -2
  49. package/docs/scenarios/spell-check.mdx +3 -3
  50. package/docs/security/agent.mdx +14 -2
  51. package/docs/security/platform.mdx +17 -5
  52. package/docs/snippets/calendar-link.mdx +4 -1
  53. package/docs/snippets/comments.mdx +1 -1
  54. package/docs/snippets/gitignore-warning.mdx +6 -3
  55. package/docs/snippets/lifecycle-warning.mdx +6 -1
  56. package/docs/snippets/tests/assert-replay.mdx +5 -1
  57. package/docs/snippets/tests/exec-js-replay.mdx +5 -1
  58. package/docs/snippets/tests/exec-shell-replay.mdx +5 -1
  59. package/docs/snippets/tests/hover-image-replay.mdx +5 -1
  60. package/docs/snippets/tests/hover-image-yaml.mdx +2 -2
  61. package/docs/snippets/tests/hover-text-replay.mdx +5 -1
  62. package/docs/snippets/tests/hover-text-with-description-replay.mdx +5 -2
  63. package/docs/snippets/tests/hover-text-with-description-yaml.mdx +2 -2
  64. package/docs/snippets/tests/match-image-replay.mdx +5 -1
  65. package/docs/snippets/tests/match-image-yaml.mdx +2 -2
  66. package/docs/snippets/tests/press-keys-replay.mdx +5 -1
  67. package/docs/snippets/tests/prompt-replay.mdx +5 -1
  68. package/docs/snippets/tests/prompt-yaml.mdx +1 -1
  69. package/docs/snippets/tests/remember-replay.mdx +5 -1
  70. package/docs/snippets/tests/scroll-replay.mdx +5 -1
  71. package/docs/snippets/tests/scroll-until-text-replay.mdx +5 -1
  72. package/docs/snippets/tests/type-repeated-replay.mdx +5 -1
  73. package/docs/snippets/tests/type-replay.mdx +5 -1
  74. package/docs/snippets/yml-warning.mdx +4 -1
  75. package/docs/styles.css +6 -0
  76. package/interfaces/logger.js +1 -19
  77. package/package.json +6 -2
  78. package/styles/Microsoft/HeadingAcronyms.yml +1 -1
  79. package/styles/Microsoft/Headings.yml +1 -1
  80. package/styles/Microsoft/Quotes.yml +1 -1
  81. package/styles/Microsoft/Semicolon.yml +1 -1
  82. package/styles/Microsoft/SentenceLength.yml +0 -1
  83. package/styles/Microsoft/Spacing.yml +2 -2
  84. package/styles/Microsoft/Units.yml +4 -4
  85. package/styles/Microsoft/Vocab.yml +1 -1
  86. package/styles/alex/Ablist.yml +58 -29
  87. package/styles/alex/Gendered.yml +4 -2
  88. package/styles/alex/Press.yml +2 -1
  89. package/styles/alex/ProfanityLikely.yml +1284 -1284
  90. package/styles/alex/ProfanityMaybe.yml +1 -1
  91. package/styles/alex/ProfanityUnlikely.yml +1 -1
  92. package/styles/alex/Race.yml +4 -2
  93. package/styles/alex/Suicide.yml +4 -2
  94. package/styles/alex/meta.json +1 -1
  95. package/styles/proselint/AnimalLabels.yml +39 -39
  96. package/styles/proselint/DenizenLabels.yml +44 -44
  97. package/styles/proselint/GenderBias.yml +37 -37
  98. package/styles/proselint/GroupTerms.yml +31 -31
  99. package/styles/proselint/Hedging.yml +1 -1
  100. package/styles/proselint/Hyperbole.yml +1 -1
  101. package/styles/proselint/LGBTTerms.yml +8 -8
  102. package/styles/proselint/Needless.yml +5 -5
  103. package/styles/proselint/RASSyndrome.yml +3 -3
  104. package/styles/proselint/Typography.yml +1 -1
  105. package/styles/proselint/Uncomparables.yml +5 -5
  106. package/styles/proselint/meta.json +1 -3
  107. package/testdriver/acceptance/remember.yaml +1 -1
  108. package/testdriver/behavior/secrets.yaml +7 -0
  109. /package/{upload-docs-to-openai.js → docs/_scripts/upload-docs-to-openai.js} +0 -0
package/agent/events.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { EventEmitter2 } = require("eventemitter2");
2
+ const { censorSensitiveDataDeep } = require("./lib/censorship");
2
3
 
3
- // Factory function to create a new emitter instance
4
+ // Factory function to create a new emitter instance with censoring middleware
4
5
  const createEmitter = () => {
5
6
  const emitter = new EventEmitter2({
6
7
  wildcard: true,
@@ -11,6 +12,15 @@ const createEmitter = () => {
11
12
  verboseMemoryLeak: false,
12
13
  ignoreErrors: false,
13
14
  });
15
+
16
+ // Override emit to censor sensitive data before emitting
17
+ const originalEmit = emitter.emit.bind(emitter);
18
+ emitter.emit = function (event, ...args) {
19
+ // Censor all arguments passed to emit
20
+ const censoredArgs = args.map(censorSensitiveDataDeep);
21
+ return originalEmit(event, ...censoredArgs);
22
+ };
23
+
14
24
  return emitter;
15
25
  };
16
26
 
@@ -33,7 +43,6 @@ const events = {
33
43
  vm: {
34
44
  show: "vm:show",
35
45
  },
36
- narration: "narration",
37
46
  status: "status",
38
47
  log: {
39
48
  markdown: {
@@ -45,6 +54,7 @@ const events = {
45
54
  log: "log:log",
46
55
  warn: "log:warn",
47
56
  debug: "log:debug",
57
+ narration: "log:narration",
48
58
  },
49
59
  command: {
50
60
  start: "command:start",
package/agent/index.js CHANGED
@@ -158,7 +158,7 @@ class TestDriverAgent extends EventEmitter2 {
158
158
  // single function to handle all program exits
159
159
  // allows us to save the current state, run lifecycle hooks, and track analytics
160
160
  async exit(failed = true, shouldSave = false, shouldRunPostrun = false) {
161
- this.emitter.emit(events.log.log, theme.dim("exiting..."), true);
161
+ this.emitter.emit(events.log.narration, theme.dim("exiting..."), true);
162
162
 
163
163
  // Clean up redraw interval
164
164
  if (this.redraw && this.redraw.cleanup) {
@@ -290,7 +290,7 @@ class TestDriverAgent extends EventEmitter2 {
290
290
  image = null;
291
291
  }
292
292
 
293
- this.emitter.emit(events.log.log, theme.dim("thinking..."), true);
293
+ this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
294
294
 
295
295
  const streamId = `error-${Date.now()}`;
296
296
  this.emitter.emit(events.log.markdown.start, streamId);
@@ -332,14 +332,14 @@ class TestDriverAgent extends EventEmitter2 {
332
332
 
333
333
  if (this.checkCount >= this.checkLimit) {
334
334
  this.emitter.emit(
335
- events.log.log,
335
+ events.log.narration,
336
336
  theme.red("Exploratory loop detected. Exiting."),
337
337
  );
338
338
  await this.summarize("Check loop detected.");
339
339
  return await this.exit(true);
340
340
  }
341
341
 
342
- this.emitter.emit(events.log.log, theme.dim("checking..."));
342
+ this.emitter.emit(events.log.narration, theme.dim("checking..."));
343
343
 
344
344
  // check asks the ai if the task is complete
345
345
  let thisScreenshot = await this.system.captureScreenBase64(1, false, true);
@@ -583,8 +583,6 @@ class TestDriverAgent extends EventEmitter2 {
583
583
  "check thinks more needs to be done",
584
584
  );
585
585
 
586
- this.emitter.emit(events.log.log, theme.dim("not done yet!"));
587
-
588
586
  return await this.aiExecute(response, validateAndLoop);
589
587
  } else {
590
588
  this.emitter.emit(events.log.debug, "seems complete, returning");
@@ -719,7 +717,7 @@ class TestDriverAgent extends EventEmitter2 {
719
717
  }
720
718
  }
721
719
 
722
- this.emitter.emit(events.log.log, theme.dim("thinking..."), true);
720
+ this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
723
721
 
724
722
  let response = `\`\`\`yaml
725
723
  commands:
@@ -747,7 +745,7 @@ commands:
747
745
 
748
746
  this.tasks.push(currentTask);
749
747
 
750
- this.emitter.emit(events.log.log, theme.dim("thinking..."), true);
748
+ this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
751
749
 
752
750
  this.lastScreenshot = await this.system.captureScreenBase64();
753
751
 
@@ -789,7 +787,7 @@ commands:
789
787
  async generate(type, count, baseYaml, skipYaml = false) {
790
788
  this.emitter.emit(events.log.debug, "generate called, %s", type);
791
789
 
792
- this.emitter.emit(events.log.log, theme.dim("thinking..."), true);
790
+ this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
793
791
 
794
792
  if (baseYaml && !skipYaml) {
795
793
  await this.runLifecycle("prerun");
@@ -866,7 +864,7 @@ commands:
866
864
 
867
865
  // this is the functinoality for "undo"
868
866
  async popFromHistory(fullStep) {
869
- this.emitter.emit(events.log.log, theme.dim("undoing..."), true);
867
+ this.emitter.emit(events.log.narration, theme.dim("undoing..."), true);
870
868
 
871
869
  if (this.executionHistory.length) {
872
870
  if (fullStep) {
@@ -946,12 +944,16 @@ ${yml}
946
944
  async summarize(error = null) {
947
945
  this.analytics.track("summarize");
948
946
 
949
- this.emitter.emit(events.log.log, theme.dim("reviewing test..."), true);
947
+ this.emitter.emit(
948
+ events.log.narration,
949
+ theme.dim("reviewing test..."),
950
+ true,
951
+ );
950
952
 
951
953
  // let text = prompts.summarize(tasks, error);
952
954
  let image = await this.system.captureScreenBase64();
953
955
 
954
- this.emitter.emit(events.log.log, theme.dim("summarizing..."), true);
956
+ this.emitter.emit(events.log.narration, theme.dim("summarizing..."), true);
955
957
 
956
958
  const streamId = `summarize-${Date.now()}`;
957
959
  this.emitter.emit(events.log.markdown.start, streamId);
@@ -1124,7 +1126,7 @@ ${regression}
1124
1126
  timestamp: fileStartTime,
1125
1127
  });
1126
1128
 
1127
- this.emitter.emit(events.log.log, theme.cyan(`running ${file}...`));
1129
+ this.emitter.emit(events.log.narration, theme.cyan(`running ${file}...`));
1128
1130
 
1129
1131
  let ymlObj = await this.loadYML(file);
1130
1132
 
@@ -1390,7 +1392,7 @@ ${regression}
1390
1392
  const mtime = new Date(stats.mtime);
1391
1393
  const now = new Date();
1392
1394
  const diffMinutes = (now - mtime) / (1000 * 60);
1393
- if (diffMinutes < 30) {
1395
+ if (diffMinutes < 10) {
1394
1396
  const fileContent = fs.readFileSync(lastSandboxFile, "utf-8").trim();
1395
1397
 
1396
1398
  // Parse sandbox info (supports both old format and new format)
@@ -1463,8 +1465,8 @@ ${regression}
1463
1465
  // If instance already exists, do not build environment again
1464
1466
  if (this.instance) {
1465
1467
  this.emitter.emit(
1466
- events.log.log,
1467
- theme.dim("- sandbox instance already exists, skipping launch."),
1468
+ events.log.narration,
1469
+ theme.dim("sandbox instance already exists, skipping launch."),
1468
1470
  );
1469
1471
  return;
1470
1472
  }
@@ -1488,7 +1490,7 @@ ${regression}
1488
1490
  if (!this.config.CI) {
1489
1491
  this.emitter.emit(
1490
1492
  events.log.log,
1491
- theme.dim("-- `new` flag detected, will create a new sandbox"),
1493
+ theme.dim("--`new` flag detected, will create a new sandbox"),
1492
1494
  );
1493
1495
  }
1494
1496
  }
@@ -1501,14 +1503,14 @@ ${regression}
1501
1503
  // Set sandbox ID for reconnection (only if not creating new and recent ID exists)
1502
1504
  if (!createNew && recentId) {
1503
1505
  this.emitter.emit(
1504
- events.log.log,
1505
- theme.dim(`- using recent sandbox: ${recentId}`),
1506
+ events.log.narration,
1507
+ theme.dim(`using recent sandbox: ${recentId}`),
1506
1508
  );
1507
1509
  this.sandboxId = recentId;
1508
1510
  } else if (!createNew) {
1509
1511
  this.emitter.emit(
1510
- events.log.log,
1511
- theme.dim(`- no recent sandbox found, creating a new one.`),
1512
+ events.log.narration,
1513
+ theme.dim(`no recent sandbox found, creating a new one.`),
1512
1514
  );
1513
1515
  }
1514
1516
 
@@ -1516,8 +1518,8 @@ ${regression}
1516
1518
  if (this.sandboxId && !this.config.CI && !createNew) {
1517
1519
  // Attempt to connect to known instance
1518
1520
  this.emitter.emit(
1519
- events.log.log,
1520
- theme.dim(`- connecting to sandbox ${this.sandboxId}...`),
1521
+ events.log.narration,
1522
+ theme.dim(`connecting to sandbox ${this.sandboxId}...`),
1521
1523
  );
1522
1524
 
1523
1525
  try {
@@ -1539,17 +1541,16 @@ ${regression}
1539
1541
  }
1540
1542
  }
1541
1543
 
1542
- this.emitter.emit(events.log.log, theme.dim(`- creating new sandbox...`));
1543
1544
  this.emitter.emit(
1544
- events.log.log,
1545
- theme.dim(` (this can take between 10 - 240 seconds)`),
1545
+ events.log.narration,
1546
+ theme.dim(`creating new sandbox (can take up to 2 minutes)...`),
1546
1547
  );
1547
1548
  // We don't have resiliency/retries baked in, so let's at least give it 1 attempt
1548
1549
  // to see if that fixes the issue.
1549
1550
  let newSandbox = await this.createNewSandbox().catch(() => {
1550
1551
  this.emitter.emit(
1551
- events.log.log,
1552
- theme.dim(` (double-checking sandbox availability)`),
1552
+ events.log.narration,
1553
+ theme.dim(`double-checking sandbox availability`),
1553
1554
  );
1554
1555
 
1555
1556
  return this.createNewSandbox();
@@ -1576,7 +1577,7 @@ ${regression}
1576
1577
  if (!debuggerStarted) {
1577
1578
  debuggerStarted = true; // Prevent multiple starts, especially when running test in parallel
1578
1579
  this.emitter.emit(
1579
- events.log.log,
1580
+ events.log.narration,
1580
1581
  theme.green(`Starting debugger server...`),
1581
1582
  );
1582
1583
  debuggerProcess = await createDebuggerProcess(
@@ -1697,8 +1698,8 @@ ${regression}
1697
1698
 
1698
1699
  async connectToSandboxService() {
1699
1700
  this.emitter.emit(
1700
- events.log.log,
1701
- theme.gray(`- establishing connection...`),
1701
+ events.log.narration,
1702
+ theme.dim(`establishing connection...`),
1702
1703
  );
1703
1704
  let ableToBoot = await this.sandbox.boot(this.config.TD_API_ROOT);
1704
1705
 
@@ -1710,7 +1711,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
1710
1711
  );
1711
1712
  }
1712
1713
 
1713
- this.emitter.emit(events.log.log, theme.gray(`- authenticating...`));
1714
+ this.emitter.emit(events.log.narration, theme.dim(`authenticating...`));
1714
1715
  let ableToAuth = await this.sandbox.auth(this.config.TD_API_KEY);
1715
1716
 
1716
1717
  if (!ableToAuth) {
@@ -1723,7 +1724,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
1723
1724
  }
1724
1725
 
1725
1726
  async connectToSandboxDirect(sandboxId, persist = false) {
1726
- this.emitter.emit(events.log.log, theme.gray(`- connecting...`));
1727
+ this.emitter.emit(events.log.narration, theme.dim(`connecting...`));
1727
1728
  let instance = await this.sandbox.connect(sandboxId, persist);
1728
1729
  return instance;
1729
1730
  }
@@ -0,0 +1,70 @@
1
+ // Shared censorship functionality for sensitive data
2
+ // Uses @npmcli/redact for common patterns (URLs, auth headers, etc.)
3
+ // Plus custom logic for environment variables and interpolation vars
4
+ const { redactLog } = require("@npmcli/redact");
5
+
6
+ let interpolationVars = JSON.parse(process.env.TD_INTERPOLATION_VARS || "{}");
7
+
8
+ // Handle local `TD_*` variables
9
+ for (const [key, value] of Object.entries(process.env)) {
10
+ if (key.startsWith("TD_") && key !== "TD_INTERPOLATION_VARS") {
11
+ interpolationVars[key] = value;
12
+ }
13
+ }
14
+
15
+ // Function to censor sensitive data in strings using both npmcli/redact and custom logic
16
+ const censorSensitiveData = (message) => {
17
+ if (typeof message !== "string") {
18
+ return message;
19
+ }
20
+
21
+ // First, use npmcli/redact for common patterns:
22
+ // - URLs with credentials (https://user:pass@host)
23
+ // - Authorization headers
24
+ // - Common secret patterns in logs
25
+ let result = redactLog(message);
26
+
27
+ // Then apply our custom interpolation variable redaction
28
+ // This catches application-specific secrets from TD_* env vars
29
+ for (let value of Object.values(interpolationVars)) {
30
+ // Avoid replacing vars that are 0 or 1 characters
31
+ if (value && value.length >= 2) {
32
+ result = result.replaceAll(value, "****");
33
+ }
34
+ }
35
+
36
+ return result;
37
+ };
38
+
39
+ // Function to censor sensitive data in any value (recursive for objects/arrays)
40
+ const censorSensitiveDataDeep = (value) => {
41
+ if (typeof value === "string") {
42
+ return censorSensitiveData(value);
43
+ } else if (Array.isArray(value)) {
44
+ return value.map(censorSensitiveDataDeep);
45
+ } else if (value && typeof value === "object") {
46
+ const result = {};
47
+ for (const [key, val] of Object.entries(value)) {
48
+ result[key] = censorSensitiveDataDeep(val);
49
+ }
50
+ return result;
51
+ }
52
+ return value;
53
+ };
54
+
55
+ // Function to update interpolation variables (for runtime updates)
56
+ const updateInterpolationVars = (newVars) => {
57
+ interpolationVars = { ...interpolationVars, ...newVars };
58
+ };
59
+
60
+ // Function to get current interpolation variables (for debugging)
61
+ const getInterpolationVars = () => {
62
+ return { ...interpolationVars };
63
+ };
64
+
65
+ module.exports = {
66
+ censorSensitiveData,
67
+ censorSensitiveDataDeep,
68
+ updateInterpolationVars,
69
+ getInterpolationVars,
70
+ };
@@ -80,20 +80,20 @@ commands:
80
80
  // this will actually interpret the command and execute it
81
81
  switch (object.command) {
82
82
  case "type":
83
- emitter.emit(events.narration, `typing ${object.text}`);
83
+ emitter.emit(events.log.narration, `typing ${object.text}`);
84
84
  emitter.emit(events.log.log, generator.jsonToManual(object));
85
85
  response = await commands.type(object.text, object.delay);
86
86
  break;
87
87
  case "press-keys":
88
88
  emitter.emit(
89
- events.narration,
89
+ events.log.narration,
90
90
  `pressing keys ${object.keys.join(",")}`,
91
91
  );
92
92
  emitter.emit(events.log.log, generator.jsonToManual(object));
93
93
  response = await commands["press-keys"](object.keys);
94
94
  break;
95
95
  case "scroll":
96
- emitter.emit(events.narration, `scrolling ${object.direction}`);
96
+ emitter.emit(events.log.narration, `scrolling ${object.direction}`);
97
97
  emitter.emit(events.log.log, generator.jsonToManual(object));
98
98
  response = await commands.scroll(
99
99
  object.direction,
@@ -102,17 +102,20 @@ commands:
102
102
  );
103
103
  break;
104
104
  case "wait":
105
- emitter.emit(events.narration, `waiting ${object.timeout} seconds`);
105
+ emitter.emit(
106
+ events.log.narration,
107
+ `waiting ${object.timeout} seconds`,
108
+ );
106
109
  emitter.emit(events.log.log, generator.jsonToManual(object));
107
110
  response = await commands.wait(object.timeout);
108
111
  break;
109
112
  case "click":
110
- emitter.emit(events.narration, `${object.action}`);
113
+ emitter.emit(events.log.narration, `${object.action}`);
111
114
  emitter.emit(events.log.log, generator.jsonToManual(object));
112
115
  response = await commands["click"](object.x, object.y, object.action);
113
116
  break;
114
117
  case "hover":
115
- emitter.emit(events.narration, `moving mouse`);
118
+ emitter.emit(events.log.narration, `moving mouse`);
116
119
  emitter.emit(events.log.log, generator.jsonToManual(object));
117
120
  response = await commands["hover"](object.x, object.y);
118
121
  break;
@@ -121,7 +124,10 @@ commands:
121
124
  response = await commands["drag"](object.x, object.y);
122
125
  break;
123
126
  case "hover-text":
124
- emitter.emit(events.narration, `searching for ${object.description}`);
127
+ emitter.emit(
128
+ events.log.narration,
129
+ `searching for ${object.description}`,
130
+ );
125
131
  emitter.emit(events.log.log, generator.jsonToManual(object));
126
132
  response = await commands["hover-text"](
127
133
  object.text,
@@ -132,7 +138,7 @@ commands:
132
138
  break;
133
139
  case "hover-image":
134
140
  emitter.emit(
135
- events.narration,
141
+ events.log.narration,
136
142
  `searching for image of ${object.description}`,
137
143
  );
138
144
  emitter.emit(events.log.log, generator.jsonToManual(object));
@@ -144,13 +150,16 @@ commands:
144
150
  case "match-image":
145
151
  emitter.emit(events.log.log, generator.jsonToManual(object));
146
152
  emitter.emit(
147
- events.narration,
153
+ events.log.narration,
148
154
  `${object.action} image ${object.path}`,
149
155
  );
150
156
  response = await commands["match-image"](object.path, object.action);
151
157
  break;
152
158
  case "wait-for-image":
153
- emitter.emit(events.narration, `waiting for ${object.description}`);
159
+ emitter.emit(
160
+ events.log.narration,
161
+ `waiting for ${object.description}`,
162
+ );
154
163
  emitter.emit(events.log.log, generator.jsonToManual(object));
155
164
  response = await commands["wait-for-image"](
156
165
  object.description,
@@ -158,7 +167,7 @@ commands:
158
167
  );
159
168
  break;
160
169
  case "wait-for-text":
161
- emitter.emit(events.narration, `waiting for ${object.text}`);
170
+ emitter.emit(events.log.narration, `waiting for ${object.text}`);
162
171
  emitter.emit(events.log.log, generator.jsonToManual(object));
163
172
  copy.text = "*****";
164
173
  response = await commands["wait-for-text"](
@@ -168,7 +177,7 @@ commands:
168
177
  );
169
178
  break;
170
179
  case "scroll-until-text":
171
- emitter.emit(events.narration, `scrolling until ${object.text}`);
180
+ emitter.emit(events.log.narration, `scrolling until ${object.text}`);
172
181
  emitter.emit(events.log.log, generator.jsonToManual(object));
173
182
  copy.text = "*****";
174
183
  response = await commands["scroll-until-text"](
@@ -181,7 +190,7 @@ commands:
181
190
  break;
182
191
  case "scroll-until-image": {
183
192
  const needle = object.description || object.path;
184
- emitter.emit(events.narration, `scrolling until ${needle}`);
193
+ emitter.emit(events.log.narration, `scrolling until ${needle}`);
185
194
  emitter.emit(events.log.log, generator.jsonToManual(object));
186
195
  response = await commands["scroll-until-image"](
187
196
  object.description,
@@ -193,7 +202,7 @@ commands:
193
202
  break;
194
203
  }
195
204
  case "focus-application":
196
- emitter.emit(events.narration, `focusing ${object.name}`);
205
+ emitter.emit(events.log.narration, `focusing ${object.name}`);
197
206
  emitter.emit(events.log.log, generator.jsonToManual(object));
198
207
  response = await commands["focus-application"](object.name);
199
208
  break;
@@ -205,12 +214,12 @@ commands:
205
214
  break;
206
215
  }
207
216
  case "assert":
208
- emitter.emit(events.narration, `asserting ${object.expect}`);
217
+ emitter.emit(events.log.narration, `asserting ${object.expect}`);
209
218
  emitter.emit(events.log.log, generator.jsonToManual(object));
210
219
  response = await commands.assert(object.expect, object.async);
211
220
  break;
212
221
  case "exec":
213
- emitter.emit(events.narration, `exec`);
222
+ emitter.emit(events.log.narration, `exec`);
214
223
  emitter.emit(
215
224
  events.log.log,
216
225
  generator.jsonToManual({
@@ -190,7 +190,7 @@ const createCommands = (
190
190
  }
191
191
  };
192
192
 
193
- emitter.emit(events.log.log, `thinking...`);
193
+ emitter.emit(events.log.narration, `thinking...`);
194
194
 
195
195
  if (async) {
196
196
  await sdk
@@ -276,7 +276,7 @@ const createCommands = (
276
276
  }
277
277
 
278
278
  emitter.emit(
279
- "log:debug",
279
+ events.log.narration,
280
280
  theme.dim(`${action} ${button} clicking at ${x}, ${y}...`),
281
281
  true,
282
282
  );
@@ -347,7 +347,7 @@ const createCommands = (
347
347
 
348
348
  description = description ? description.toString() : null;
349
349
 
350
- emitter.emit(events.log.log, theme.dim("thinking..."), true);
350
+ emitter.emit(events.log.narration, theme.dim("thinking..."), true);
351
351
 
352
352
  let response = await sdk.req(
353
353
  "hover/text",
@@ -375,7 +375,7 @@ const createCommands = (
375
375
  // uses our api to find all images on screen
376
376
  "hover-image": async (description, action = "click") => {
377
377
  // take a screenshot
378
- emitter.emit(events.log.log, theme.dim("thinking..."), true);
378
+ emitter.emit(events.log.narration, theme.dim("thinking..."), true);
379
379
 
380
380
  let response = await sdk.req(
381
381
  "hover/image",
@@ -446,7 +446,7 @@ const createCommands = (
446
446
  },
447
447
  "wait-for-image": async (description, timeout = 10000) => {
448
448
  emitter.emit(
449
- events.log.log,
449
+ events.log.narration,
450
450
  theme.dim(
451
451
  `waiting for an image matching description "${description}"...`,
452
452
  ),
@@ -467,7 +467,7 @@ const createCommands = (
467
467
  durationPassed = new Date().getTime() - startTime;
468
468
  if (!passed) {
469
469
  emitter.emit(
470
- events.log.log,
470
+ events.log.narration,
471
471
  theme.dim(
472
472
  `${niceSeconds(durationPassed)} seconds have passed without finding an image matching the description "${description}"`,
473
473
  ),
@@ -479,7 +479,7 @@ const createCommands = (
479
479
 
480
480
  if (passed) {
481
481
  emitter.emit(
482
- events.log.log,
482
+ events.log.narration,
483
483
  theme.dim(
484
484
  `An image matching the description "${description}" found!`,
485
485
  ),
@@ -496,7 +496,7 @@ const createCommands = (
496
496
  await redraw.start();
497
497
 
498
498
  emitter.emit(
499
- events.log.log,
499
+ events.log.narration,
500
500
  theme.dim(`waiting for text: "${text}"...`),
501
501
  true,
502
502
  );
@@ -525,7 +525,7 @@ const createCommands = (
525
525
  durationPassed = new Date().getTime() - startTime;
526
526
  if (!passed) {
527
527
  emitter.emit(
528
- events.log.log,
528
+ events.log.narration,
529
529
  theme.dim(
530
530
  `${niceSeconds(durationPassed)} seconds have passed without finding "${text}"`,
531
531
  ),
@@ -536,7 +536,7 @@ const createCommands = (
536
536
  }
537
537
 
538
538
  if (passed) {
539
- emitter.emit(events.log.log, theme.dim(`"${text}" found!`), true);
539
+ emitter.emit(events.log.narration, theme.dim(`"${text}" found!`), true);
540
540
  return;
541
541
  } else {
542
542
  throw new MatchError(
@@ -554,7 +554,7 @@ const createCommands = (
554
554
  await redraw.start();
555
555
 
556
556
  emitter.emit(
557
- events.log.log,
557
+ events.log.narration,
558
558
  theme.dim(`scrolling for text: "${text}"...`),
559
559
  true,
560
560
  );
@@ -598,7 +598,7 @@ const createCommands = (
598
598
  passed = response.data;
599
599
  if (!passed) {
600
600
  emitter.emit(
601
- events.log.log,
601
+ events.log.narration,
602
602
  theme.dim(
603
603
  `scrolling ${direction} ${incrementDistance}px. ${scrollDistance + incrementDistance}/${maxDistance}px scrolled...`,
604
604
  ),
@@ -610,7 +610,7 @@ const createCommands = (
610
610
  }
611
611
 
612
612
  if (passed) {
613
- emitter.emit(events.log.log, theme.dim(`"${text}" found!`), true);
613
+ emitter.emit(events.log.narration, theme.dim(`"${text}" found!`), true);
614
614
  return;
615
615
  } else {
616
616
  throw new MatchError(
@@ -638,7 +638,7 @@ const createCommands = (
638
638
  }
639
639
 
640
640
  emitter.emit(
641
- events.log.log,
641
+ events.log.narration,
642
642
  theme.dim(`scrolling for an image matching "${needle}"...`),
643
643
  true,
644
644
  );
@@ -665,7 +665,7 @@ const createCommands = (
665
665
 
666
666
  if (!passed) {
667
667
  emitter.emit(
668
- events.log.log,
668
+ events.log.narration,
669
669
  theme.dim(`scrolling ${direction} ${incrementDistance} pixels...`),
670
670
  true,
671
671
  );
@@ -675,7 +675,11 @@ const createCommands = (
675
675
  }
676
676
 
677
677
  if (passed) {
678
- emitter.emit(events.log.log, theme.dim(`"${needle}" found!`), true);
678
+ emitter.emit(
679
+ events.log.narration,
680
+ theme.dim(`"${needle}" found!`),
681
+ true,
682
+ );
679
683
  return;
680
684
  } else {
681
685
  throw new CommandError(
@@ -705,7 +709,7 @@ const createCommands = (
705
709
  return await assert(assertion, true, async);
706
710
  },
707
711
  exec: async (language, code, timeout, silent = false) => {
708
- emitter.emit(events.log.log, theme.dim(`calling exec...`), true);
712
+ emitter.emit(events.log.narration, theme.dim(`calling exec...`), true);
709
713
 
710
714
  emitter.emit(events.log.log, code);
711
715
 
@@ -738,10 +742,10 @@ const createCommands = (
738
742
  return result.out?.stdout?.trim();
739
743
  }
740
744
  } else if (language == "js") {
741
- emitter.emit(events.log.log, theme.dim(`running js...`), true);
745
+ emitter.emit(events.log.narration, theme.dim(`running js...`), true);
742
746
 
743
747
  emitter.emit(
744
- events.log.log,
748
+ events.log.narration,
745
749
  theme.dim(`running value of \`${plat}\` in local JS vm...`),
746
750
  true,
747
751
  );
@@ -670,6 +670,8 @@
670
670
  // Handle window resize to recalculate scale (throttled)
671
671
  window.addEventListener("resize", throttle(resizeOverlay, 100));
672
672
 
673
+ setInterval(resizeOverlay, 1000);
674
+
673
675
  // Handle window blur/focus for screen locking
674
676
  window.addEventListener("blur", () => {
675
677
  showInteractionOverlay();