testdriverai 5.2.2 → 5.3.1

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 (107) hide show
  1. package/.github/workflows/test-install.yml +1 -1
  2. package/README.md +5 -11
  3. package/agent.js +135 -99
  4. package/docs/30x30.mdx +84 -0
  5. package/docs/action/browser.mdx +129 -0
  6. package/docs/action/os.mdx +157 -0
  7. package/docs/action/output.mdx +98 -0
  8. package/docs/action/performance.mdx +71 -0
  9. package/docs/action/prerun.mdx +80 -0
  10. package/docs/action/secrets.mdx +103 -0
  11. package/docs/action/setup.mdx +115 -0
  12. package/docs/bugs/jira.mdx +208 -0
  13. package/docs/cli/overview.mdx +65 -0
  14. package/docs/commands/assert.mdx +31 -0
  15. package/docs/commands/exec.mdx +42 -0
  16. package/docs/commands/focus-application.mdx +29 -0
  17. package/docs/commands/hover-image.mdx +32 -0
  18. package/docs/commands/hover-text.mdx +37 -0
  19. package/docs/commands/if.mdx +43 -0
  20. package/docs/commands/match-image.mdx +41 -0
  21. package/docs/commands/press-keys.mdx +30 -0
  22. package/docs/commands/run.mdx +30 -0
  23. package/docs/commands/scroll-until-image.mdx +33 -0
  24. package/docs/commands/scroll-until-text.mdx +37 -0
  25. package/docs/commands/scroll.mdx +33 -0
  26. package/docs/commands/type.mdx +29 -0
  27. package/docs/commands/wait-for-image.mdx +31 -0
  28. package/docs/commands/wait-for-text.mdx +35 -0
  29. package/docs/commands/wait.mdx +30 -0
  30. package/docs/docs.json +226 -0
  31. package/docs/exporting/playwright.mdx +159 -0
  32. package/docs/features/auto-healing.mdx +124 -0
  33. package/docs/features/cross-platform.mdx +106 -0
  34. package/docs/features/generation.mdx +180 -0
  35. package/docs/features/github.mdx +161 -0
  36. package/docs/features/parallel-testing.mdx +130 -0
  37. package/docs/features/reusable-snippets.mdx +124 -0
  38. package/docs/features/selectorless.mdx +62 -0
  39. package/docs/features/visual-assertions.mdx +123 -0
  40. package/docs/getting-started/ci.mdx +196 -0
  41. package/docs/getting-started/generating.mdx +210 -0
  42. package/docs/getting-started/running.mdx +67 -0
  43. package/docs/getting-started/setup.mdx +133 -0
  44. package/docs/getting-started/writing.mdx +99 -0
  45. package/docs/guide/assertions.mdx +195 -0
  46. package/docs/guide/authentication.mdx +150 -0
  47. package/docs/guide/code.mdx +169 -0
  48. package/docs/guide/locating.mdx +136 -0
  49. package/docs/guide/setup-teardown.mdx +161 -0
  50. package/docs/guide/variables.mdx +218 -0
  51. package/docs/guide/waiting.mdx +199 -0
  52. package/docs/importing/csv.mdx +196 -0
  53. package/docs/importing/gherkin.mdx +142 -0
  54. package/docs/importing/jira.mdx +172 -0
  55. package/docs/importing/testrail.mdx +161 -0
  56. package/docs/integrations/electron.mdx +152 -0
  57. package/docs/integrations/netlify.mdx +98 -0
  58. package/docs/integrations/vercel.mdx +177 -0
  59. package/docs/interactive/assert.mdx +51 -0
  60. package/docs/interactive/generate.mdx +41 -0
  61. package/docs/interactive/run.mdx +36 -0
  62. package/docs/interactive/save.mdx +53 -0
  63. package/docs/interactive/undo.mdx +47 -0
  64. package/docs/issues.mdx +9 -0
  65. package/docs/overview/comparison.mdx +82 -0
  66. package/docs/overview/faq.mdx +122 -0
  67. package/docs/overview/quickstart.mdx +66 -0
  68. package/docs/overview/what-is-testdriver.mdx +73 -0
  69. package/docs/quickstart.mdx +66 -0
  70. package/docs/reference/commands/scroll.mdx +0 -0
  71. package/docs/reference/interactive/assert.mdx +0 -0
  72. package/docs/security/action.mdx +62 -0
  73. package/docs/security/agent.mdx +62 -0
  74. package/docs/security/dashboard.mdx +0 -0
  75. package/docs/security/platform.mdx +54 -0
  76. package/docs/tutorials/advanced-test.mdx +79 -0
  77. package/docs/tutorials/basic-test.mdx +41 -0
  78. package/electron/icon.png +0 -0
  79. package/electron/overlay.html +7 -3
  80. package/electron/overlay.js +75 -15
  81. package/electron/tray-buffered.png +0 -0
  82. package/electron/tray.png +0 -0
  83. package/index.js +75 -34
  84. package/lib/commander.js +22 -1
  85. package/lib/commands.js +87 -19
  86. package/lib/config.js +10 -1
  87. package/lib/focus-application.js +30 -23
  88. package/lib/generator.js +58 -7
  89. package/lib/init.js +48 -19
  90. package/lib/ipc.js +50 -0
  91. package/lib/logger.js +19 -6
  92. package/lib/overlay.js +82 -36
  93. package/lib/parser.js +9 -7
  94. package/lib/resources/prerun.yaml +17 -0
  95. package/lib/sandbox.js +2 -3
  96. package/lib/sdk.js +0 -2
  97. package/lib/session.js +3 -1
  98. package/lib/speak.js +0 -2
  99. package/lib/subimage/opencv.js +0 -4
  100. package/lib/system.js +56 -39
  101. package/lib/upload-secrets.js +65 -0
  102. package/lib/validation.js +175 -0
  103. package/package.json +2 -1
  104. package/postinstall.js +0 -24
  105. package/lib/websockets.js +0 -85
  106. package/test.md +0 -8
  107. package/test.yml +0 -18
@@ -1,25 +1,56 @@
1
- const ipc = require("node-ipc").default;
2
- const { app, screen, BrowserWindow } = require("electron");
1
+ const { default: nodeIPC } = require("node-ipc");
2
+ const { app: electronApp, remote, screen, BrowserWindow, Tray, Menu } = require("electron");
3
3
  const { eventsArray } = require("../lib/events.js");
4
4
  const config = require("../lib/config.js");
5
+ const path = require('path');
5
6
 
6
- ipc.config.id = "testdriverai_overlay";
7
- ipc.config.retry = 1500;
7
+ let tray = null;
8
+
9
+ const app = electronApp || remote;
10
+ if (!app) {
11
+ console.log("No app");
12
+ process.exit(1);
13
+ }
14
+
15
+ // Seems like the direct process id is not the electron process id
16
+ // so we use the parent process id
17
+ const rendererId = process.env.TD_OVERLAY_ID ?? process.ppid;
18
+
19
+ const ipc = new nodeIPC.IPC();
20
+ ipc.config.id = `testdriverai_overlay_${rendererId}`;
21
+ ipc.config.retry = 0;
8
22
  ipc.config.silent = true;
9
23
 
10
24
  app.whenReady().then(() => {
25
+
26
+ // Path to tray icon (must be .ico on Windows, .png on Mac/Linux)
27
+ const iconPath = path.join(__dirname, 'tray.png');
28
+
29
+ tray = new Tray(iconPath);
30
+
31
+ const contextMenu = Menu.buildFromTemplate([
32
+ {
33
+ label: 'Quit',
34
+ click: () => {
35
+ app.quit();
36
+ },
37
+ },
38
+ ]);
39
+
40
+ tray.setToolTip('TestDriver.ai');
41
+ tray.setContextMenu(contextMenu);
42
+
11
43
  app.dock?.hide();
12
44
 
13
45
  let windowOptions;
14
46
 
15
47
  if (config.TD_VM) {
16
-
17
48
  windowOptions = {
18
- width: 1024,
19
- height: 768,
49
+ width: config.TD_VM_RESOLUTION[0],
50
+ height: config.TD_VM_RESOLUTION[1],
20
51
  closable: true,
21
52
  resizable: true,
22
- alwaysOnTop: true,
53
+ // alwaysOnTop: true,
23
54
  show: false,
24
55
  webPreferences: {
25
56
  nodeIntegration: true,
@@ -27,9 +58,7 @@ app.whenReady().then(() => {
27
58
  },
28
59
  autoHideMenuBar: true,
29
60
  };
30
-
31
61
  } else {
32
-
33
62
  windowOptions = {
34
63
  ...screen.getPrimaryDisplay().bounds,
35
64
  closable: true,
@@ -49,10 +78,9 @@ app.whenReady().then(() => {
49
78
  autoHideMenuBar: true,
50
79
  };
51
80
 
52
- if (process.platform !== 'darwin') {
81
+ if (process.platform !== "darwin") {
53
82
  windowOptions.fullscreen = true;
54
83
  }
55
-
56
84
  }
57
85
 
58
86
  const window = new BrowserWindow(windowOptions);
@@ -64,13 +92,13 @@ app.whenReady().then(() => {
64
92
  visibleOnFullScreen: true,
65
93
  });
66
94
  } else {
67
- window.setContentSize(1024, 768);
95
+ window.setContentSize(config.TD_VM_RESOLUTION[0], config.TD_VM_RESOLUTION[1]);
68
96
  window.setBackgroundColor('#000')
69
97
  }
70
-
98
+
71
99
  window.loadFile("overlay.html");
72
100
 
73
- window.once('ready-to-show', () => {
101
+ window.once("ready-to-show", () => {
74
102
  // window.showInactive();
75
103
  });
76
104
 
@@ -78,6 +106,7 @@ app.whenReady().then(() => {
78
106
  // window.webContents.openDevTools();
79
107
 
80
108
  ipc.serve(() => {
109
+ console.log("Serving IPC");
81
110
  for (const event of eventsArray) {
82
111
  ipc.server.on(event, (data) => {
83
112
  if (event === "show-window") {
@@ -88,8 +117,39 @@ app.whenReady().then(() => {
88
117
  });
89
118
  }
90
119
  });
120
+
121
+ // We do this because node-ipc doesn't prevent new servers from using the same id
122
+ // so we need to timeout if no clients connect after 5 minutes to avoid keeping older
123
+ // overlay processes alive
124
+ const timeout = setTimeout(
125
+ () => {
126
+ console.log("No connected clients for 5 minutes");
127
+ process.exit(0);
128
+ },
129
+ 1000 * 60 * 5,
130
+ );
131
+
132
+ ipc.server.on("connect", () => {
133
+ console.log("Client connected");
134
+ clearTimeout(timeout);
135
+ });
136
+
91
137
  ipc.server.on("socket.disconnected", function () {
138
+ // We exit because we want the renderer process to be single use
139
+ // and not stay alive if the cli gets disconnected
140
+ console.log("Client disconnected");
92
141
  process.exit();
93
142
  });
143
+
144
+ ipc.server.on("error", () => {
145
+ console.log("Server error");
146
+ process.exit(1);
147
+ });
148
+
149
+ ipc.server.on("destroy", () => {
150
+ console.log("Server destroyed");
151
+ process.exit(1);
152
+ });
153
+
94
154
  ipc.server.start();
95
155
  });
Binary file
Binary file
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  const config = require("./lib/config.js");
3
- const system = require("./lib/system.js");
3
+ // We need to initialize the IPC server quickly
4
+ require("./lib/ipc.js");
4
5
  const { emitter, events } = require("./lib/events.js");
5
6
  const { logger } = require("./lib/logger.js");
6
7
 
@@ -8,42 +9,82 @@ if (process.argv[2] === "--help" || process.argv[2] === "-h") {
8
9
  console.log("Command: testdriverai [init, run, edit] [yaml filepath]");
9
10
  process.exit(0);
10
11
  }
12
+ if (process.argv[2] === "--renderer") {
13
+ const {
14
+ // connectToOverlay,
15
+ createOverlayProcess,
16
+ } = require("./lib/overlay.js");
17
+ // const id = config.TD_OVERLAY_ID;
18
+ const id = process.argv[3] ?? config.TD_OVERLAY_ID;
19
+ if (!id) {
20
+ logger.error("Renderer ID is not set");
21
+ process.exit(1);
22
+ }
23
+ (async () => {
24
+ try {
25
+ if (!id) {
26
+ throw new Error("Renderer ID is not set");
27
+ }
28
+ const electronProcess = await createOverlayProcess({
29
+ id,
30
+ detached: true,
31
+ });
32
+ logger.info(`Started renderer, process ID: ${electronProcess.pid}`);
33
+ process.exit(0);
34
+ } catch (err) {
35
+ logger.error("%s", err);
36
+ process.exit(1);
37
+ }
38
+ })();
39
+ } else {
40
+ (async () => {
41
+ if (!config.TD_OVERLAY) {
42
+ let agent = require("./agent.js");
43
+ await agent.start();
44
+ } else {
45
+ // Intercept all stdout and stderr calls (works with console as well)
46
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
47
+ process.stdout.write = (...args) => {
48
+ const [data, encoding] = args;
49
+ emitter.emit(
50
+ events.terminal.stdout,
51
+ data.toString(typeof encoding === "string" ? encoding : undefined),
52
+ );
53
+ originalStdoutWrite(...args);
54
+ };
11
55
 
12
- (async () => {
56
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
57
+ process.stderr.write = (...args) => {
58
+ const [data, encoding] = args;
59
+ emitter.emit(
60
+ events.terminal.stderr,
61
+ data.toString(typeof encoding === "string" ? encoding : undefined),
62
+ );
63
+ originalStderrWrite(...args);
64
+ };
13
65
 
14
- if (!config.TD_OVERLAY) {
15
- let agent = require("./agent.js");
16
- agent.start();
17
- } else {
18
- // Intercept all stdout and stderr calls (works with console as well)
19
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
20
- process.stdout.write = (...args) => {
21
- const [data, encoding] = args;
22
- emitter.emit(
23
- events.terminal.stdout,
24
- data.toString(typeof encoding === "string" ? encoding : undefined),
25
- );
26
- originalStdoutWrite(...args);
27
- };
66
+ const {
67
+ connectToOverlay,
68
+ createOverlayProcess,
69
+ } = require("./lib/overlay.js");
28
70
 
29
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
30
- process.stderr.write = (...args) => {
31
- const [data, encoding] = args;
32
- emitter.emit(
33
- events.terminal.stderr,
34
- data.toString(typeof encoding === "string" ? encoding : undefined),
35
- );
36
- originalStderrWrite(...args);
37
- };
71
+ try {
72
+ let id = config.TD_OVERLAY_ID;
73
+ if (!id) {
74
+ const electronProcess = await createOverlayProcess();
75
+ electronProcess.on("exit", (code) => {
76
+ logger.info(`Renderer process exited with code ${code}`);
77
+ process.exit(code);
78
+ });
79
+ id = electronProcess.pid;
80
+ }
38
81
 
39
- require("./lib/overlay.js")
40
- .electronProcessPromise.then(async () => {
41
- let agent = require("./agent.js");
42
- agent.start();
43
- })
44
- .catch((err) => {
82
+ await connectToOverlay(id);
83
+ await require("./agent.js").start();
84
+ } catch (err) {
45
85
  logger.error("%s", err);
46
86
  process.exit(1);
47
- });
48
- }
49
- })();
87
+ }
88
+ }
89
+ })();
90
+ }
package/lib/commander.js CHANGED
@@ -9,6 +9,7 @@ const analytics = require("./analytics");
9
9
  const marky = require("marky");
10
10
  const sdk = require("./sdk");
11
11
  const outputs = require("./outputs");
12
+ const {server} = require("./ipc");
12
13
 
13
14
  // replace all occurances of ${OUTPUT.ls} with outputs.get("ls") in every possible property of the `object`
14
15
  // this is a recursive function that will go through all the properties of the object
@@ -69,18 +70,21 @@ commands:
69
70
  switch (object.command) {
70
71
  case "type":
71
72
  speak(`typing ${object.text}`);
73
+ server.broadcast("status", `typing ${object.text}`);
72
74
  logger.info(generator.jsonToManual(object));
73
75
  notify(generator.jsonToManual(object, false));
74
76
  response = await commands.type(object.text, object.delay);
75
77
  break;
76
78
  case "press-keys":
77
79
  speak(`pressing keys ${object.keys.join(",")}`);
80
+ server.broadcast("status", `pressing keys ${object.keys.join(",")}`);
78
81
  logger.info(generator.jsonToManual(object));
79
82
  notify(generator.jsonToManual(object, false));
80
83
  response = await commands["press-keys"](object.keys);
81
84
  break;
82
85
  case "scroll":
83
86
  speak(`scrolling ${object.direction}`);
87
+ server.broadcast("status", `scrolling ${object.direction}`);
84
88
  logger.info(generator.jsonToManual(object));
85
89
  notify(generator.jsonToManual(object, false));
86
90
  response = await commands.scroll(
@@ -91,12 +95,14 @@ commands:
91
95
  break;
92
96
  case "wait":
93
97
  speak(`waiting ${object.timeout} seconds`);
98
+ server.broadcast("status", `waiting ${object.timeout} seconds`);
94
99
  logger.info(generator.jsonToManual(object));
95
100
  notify(generator.jsonToManual(object, false));
96
101
  response = await commands.wait(object.timeout);
97
102
  break;
98
103
  case "click":
99
104
  speak(`${object.action}`);
105
+ server.broadcast("status", `${object.action}`);
100
106
  logger.info(generator.jsonToManual(object));
101
107
  notify(generator.jsonToManual(object, false));
102
108
  response = await commands["click"](
@@ -107,12 +113,14 @@ commands:
107
113
  break;
108
114
  case "hover":
109
115
  speak(`moving mouse`);
116
+ server.broadcast("status", `moving mouse`);
110
117
  logger.info(generator.jsonToManual(object));
111
118
  notify(generator.jsonToManual(object, false));
112
119
  response = await commands["hover"](object.x, object.y);
113
120
  break;
114
121
  case "hover-text":
115
122
  speak(`searching for ${object.description}`);
123
+ server.broadcast("status", `searching for ${object.description}`);
116
124
  logger.info(generator.jsonToManual(object));
117
125
  notify(generator.jsonToManual(object, false));
118
126
  response = await commands["hover-text"](
@@ -124,6 +132,7 @@ commands:
124
132
  break;
125
133
  case "hover-image":
126
134
  speak(`searching for image of ${object.description}`);
135
+ server.broadcast("status", `searching for image of ${object.description}`);
127
136
  logger.info(generator.jsonToManual(object));
128
137
  response = await commands["hover-image"](
129
138
  object.description,
@@ -132,6 +141,7 @@ commands:
132
141
  break;
133
142
  case "match-image":
134
143
  logger.info(generator.jsonToManual(object));
144
+ server.broadcast("status", `${object.action} image ${object.path}`);
135
145
  notify(generator.jsonToManual(object, false));
136
146
  response = await commands["match-image"](
137
147
  object.path,
@@ -140,6 +150,7 @@ commands:
140
150
  break;
141
151
  case "wait-for-image":
142
152
  speak(`waiting for ${object.description}`);
153
+ server.broadcast("status", `waiting for ${object.description}`);
143
154
  logger.info(generator.jsonToManual(object));
144
155
  notify(generator.jsonToManual(object, false));
145
156
  response = await commands["wait-for-image"](
@@ -149,6 +160,7 @@ commands:
149
160
  break;
150
161
  case "wait-for-text":
151
162
  speak(`waiting for ${object.text}`);
163
+ server.broadcast("status", `waiting for ${object.text}`);
152
164
  logger.info(generator.jsonToManual(object));
153
165
  copy.text = "*****";
154
166
  notify(generator.jsonToManual(copy, false));
@@ -160,6 +172,7 @@ commands:
160
172
  break;
161
173
  case "scroll-until-text":
162
174
  speak(`scrolling until ${object.text}`);
175
+ server.broadcast("status", `scrolling until ${object.text}`);
163
176
  logger.info(generator.jsonToManual(object));
164
177
  copy.text = "*****";
165
178
  notify(generator.jsonToManual(copy, false));
@@ -173,6 +186,7 @@ commands:
173
186
  break;
174
187
  case "scroll-until-image":
175
188
  speak(`scrolling until ${object.description}`);
189
+ server.broadcast("status", `scrolling until ${object.description}`);
176
190
  logger.info(generator.jsonToManual(object));
177
191
  notify(generator.jsonToManual(object, false));
178
192
  response = await commands["scroll-until-image"](
@@ -184,6 +198,7 @@ commands:
184
198
  break;
185
199
  case "focus-application":
186
200
  speak(`focusing ${object.name}`);
201
+ server.broadcast("status", `focusing ${object.name}`);
187
202
  logger.info(generator.jsonToManual(object));
188
203
  notify(generator.jsonToManual(object, false));
189
204
  response = await commands["focus-application"](object.name);
@@ -195,16 +210,22 @@ commands:
195
210
  break;
196
211
  case "assert":
197
212
  speak(`asserting ${object.expect}`);
213
+ server.broadcast("status", `asserting ${object.expect}`);
198
214
  logger.info(generator.jsonToManual(object));
199
215
  notify(generator.jsonToManual(object, false));
200
216
  response = await commands.assert(object.expect, object.async);
201
217
  break;
202
218
  case "exec":
219
+
203
220
  speak(`exec`);
221
+ server.broadcast("status", `exec`);
204
222
  logger.info(generator.jsonToManual(object));
205
223
  notify(generator.jsonToManual(object, false));
206
- response = await commands.exec(object.js);
224
+
225
+ response = await commands.exec(object.lang, object.mac, object.windows, object.linux);
226
+
207
227
  outputs.set(object.output, response);
228
+
208
229
  break;
209
230
  default:
210
231
  throw new Error(`Command not found: ${object.command}`);
package/lib/commands.js CHANGED
@@ -2,6 +2,7 @@
2
2
  const sdk = require("./sdk");
3
3
  const vm = require('vm');
4
4
  const chalk = require("chalk");
5
+ const server = require("./ipc").server;
5
6
  const {
6
7
  captureScreenBase64,
7
8
  captureScreenPNG,
@@ -20,6 +21,8 @@ const cliProgress = require("cli-progress");
20
21
  const redraw = require("./redraw");
21
22
  const sandbox = require("./sandbox.js");
22
23
  const config = require("./config.js");
24
+ const util = require('util');
25
+ const exec = util.promisify(require('child_process').exec);
23
26
  let robot;
24
27
 
25
28
  let keymap;
@@ -49,7 +52,7 @@ class AiError extends Error {
49
52
  }
50
53
  }
51
54
 
52
- const commandOrControl = process.platform === "darwin" ? "command" : "control";
55
+ const commandOrControl = platform() === "darwin" ? "command" : "control";
53
56
 
54
57
  const findImageOnScreen = async (relativePath, haystack, restrictToWindow) => {
55
58
  // move the file from filePath to `testdriver/screenshots`
@@ -154,6 +157,7 @@ const assert = async (assertion, shouldThrow = false, async = false) => {
154
157
  // take a screenshot
155
158
  logger.info("");
156
159
  logger.info(chalk.dim("thinking..."), true);
160
+ server.broadcast("status", `thinking...`);
157
161
  logger.info("");
158
162
 
159
163
  if (async) {
@@ -222,7 +226,7 @@ const click = async (x, y, action = "click") => {
222
226
  let button = 'left';
223
227
  let double = false;
224
228
 
225
- if (action === "right-click" && process.platform !== "darwin" ) {
229
+ if (action === "right-click" && platform !== "darwin" ) {
226
230
  button = "right";
227
231
  }
228
232
  if (action === "double-click") {
@@ -239,7 +243,7 @@ const click = async (x, y, action = "click") => {
239
243
 
240
244
  await delay(1000); // wait for the mouse to move
241
245
 
242
- if (!config.TD_VM && process.platform === "darwin" && action === "right-click") {
246
+ if (!config.TD_VM && platform()== "darwin" && action === "right-click") {
243
247
  robot.keyToggle('control', 'down', 'control');
244
248
  await delay(250);
245
249
  }
@@ -274,7 +278,7 @@ const click = async (x, y, action = "click") => {
274
278
  emitter.emit(events.mouseClick, { x, y, button, click });
275
279
  }
276
280
 
277
- if (!config.TD_VM && process.platform === "darwin" && action === "right-click") {
281
+ if (!config.TD_VM && platform()== "darwin" && action === "right-click") {
278
282
  await delay(250);
279
283
  robot.keyToggle('control', 'up', 'control');
280
284
  }
@@ -434,7 +438,7 @@ let commands = {
434
438
  // remove any modifier keys from the inputKeys array and put them in a new array
435
439
  inputKeys.forEach((key) => {
436
440
  // change command to control on windows
437
- if (key === "command" && process.platform === "win32") {
441
+ if (key === "command" && platform()== "win32") {
438
442
  key = "control";
439
443
  }
440
444
 
@@ -718,25 +722,89 @@ let commands = {
718
722
  assert: async (assertion, async = false) => {
719
723
  return await assert(assertion, true, async);
720
724
  },
721
- exec: async (nodejs_code) => {
725
+ exec: async (language, mac_code, windows_code, linux_code) => {
722
726
 
723
- // must be assigned to `result`
724
- // do not overwrite `result`
725
- // must install locally via npm install
727
+ logger.info(chalk.dim(`calling exec...`), true,);
726
728
 
727
- const context = vm.createContext({ require, console, fs, process });
728
- const script = new vm.Script(`
729
- (async () => {
730
- ${nodejs_code}
731
- })();
732
- `);
729
+ let plat = platform();
730
+
731
+ const scriptCode = plat == "linux" ? linux_code : plat == "windows" ? windows_code : plat == "mac" ? mac_code : (() => { throw new AiError(`Unsupported plat: ${plat}`); })();
732
+
733
+ if (!scriptCode) {
734
+ logger.warn(`No code provided for ${plat}`);
735
+ return;
736
+ }
737
+
738
+ if (language == 'shell') {
739
+
740
+ logger.info(chalk.dim(`running in shell...`), true,);
741
+
742
+ if (plat == "linux") {
743
+
744
+ if (config.TD_VM) {
745
+
746
+ logger.info(chalk.dim(`sending value of \`linux\` to vm...`), true,);
747
+
748
+ return await sandbox.send({
749
+ type: "commands.run",
750
+ command: linux_code,
751
+ });
752
+
753
+ } else {
754
+
755
+ if (!linux_code) {
756
+ throw new AiError(`No code provided for linux`, true);
757
+ }
733
758
 
734
- await script.runInContext(context);
759
+ logger.info(chalk.dim(`running value of \`${plat}\` on this machine...`), true);
760
+ return await exec(mac_code, { cwd: cwd() });
761
+ }
762
+ } else if (plat == "windows") {
763
+ logger.info(chalk.dim(`running value of \`${plat}\` on this machine...`), true);
764
+ return await exec(windows_code, { cwd: cwd() });
765
+ } else if (plat == "mac") {
766
+ logger.info(chalk.dim(`running value of \`${plat}\` on this machine...`), true);
767
+ return await exec(mac_code, { cwd: cwd() });
768
+
769
+ }
770
+
771
+ } else if (language == 'js') {
772
+
773
+ logger.info(chalk.dim(`running js...`), true,);
774
+
775
+ // must be assigned to `result`
776
+ // do not overwrite `result`
777
+ // must install locally via npm install
778
+
779
+ if (config.TD_VM) {
780
+ logger.info(chalk.dim(`running value of \`linux\` on vm...`), true);
781
+ return await sandbox.send({
782
+ type: "js.run",
783
+ js: linux_code,
784
+ });
785
+ } else {
786
+
787
+ logger.info(chalk.dim(`running value of \`${plat}\` in local JS vm...`), true);
735
788
 
736
- // wait for context.result to resolve
737
- const stepResult = await context.result;
789
+ const context = vm.createContext({ require, console, fs, process });
738
790
 
739
- return stepResult;
791
+ const script = new vm.Script(`
792
+ (async () => {
793
+ ${scriptCode}
794
+ })();
795
+ `);
796
+
797
+ await script.runInContext(context);
798
+
799
+ // wait for context.result to resolve
800
+ const stepResult = await context.result;
801
+
802
+ return stepResult;
803
+ }
804
+
805
+ } else {
806
+ throw new AiError(`Language not supported: ${language}`);
807
+ }
740
808
 
741
809
  }
742
810
  };
package/lib/config.js CHANGED
@@ -32,11 +32,20 @@ const config = {
32
32
  TD_PROFILE: false,
33
33
  TD_OVERLAY: true,
34
34
  TD_SECRET: null,
35
- TD_VM: false
35
+ TD_VM: false,
36
+ TD_OVERLAY_ID: null,
37
+ TD_VM_RESOLUTION: [1024, 768]
36
38
  };
37
39
 
38
40
  // Find all env vars starting with TD_
39
41
  for (let key in process.env) {
42
+ if (key == "TD_VM_RESOLUTION") {
43
+ config[key] = process.env[key]
44
+ .split("x")
45
+ .map((x) => parseInt(x.trim()));
46
+ continue;
47
+ }
48
+
40
49
  if (key.startsWith("TD_")) {
41
50
  config[key] = parseValue(process.env[key]);
42
51
  }
@@ -12,20 +12,20 @@ if (!config.TD_VM) {
12
12
  robot = require("robotjs");
13
13
  }
14
14
 
15
- async function focusVSCode() {
15
+ // async function focusVSCode() {
16
16
 
17
- try {
17
+ // try {
18
18
 
19
- if (platform() == "mac") {
20
- return await execSync('open -a "Visual Studio Code"');
21
- } else {
22
- return await execSync('"C:\\Program Files\\Microsoft VS Code\\Code.exe"');
23
- }
24
- } catch (error) {
25
- logger.error(error);
26
- }
19
+ // if (platform() == "mac") {
20
+ // return await execSync('open -a "Visual Studio Code"');
21
+ // } else {
22
+ // return await execSync('"C:\\Program Files\\Microsoft VS Code\\Code.exe"');
23
+ // }
24
+ // } catch (error) {
25
+ // logger.error(error);
26
+ // }
27
27
 
28
- }
28
+ // }
29
29
 
30
30
  // apple script that focuses on a window
31
31
  const appleScriptSetFrontmost = (windowName) => `
@@ -52,19 +52,26 @@ const runPwsh = (appName, method) => {
52
52
  };
53
53
 
54
54
  async function focusApplication(appName) {
55
- if (!config.TD_VM) {
56
- try {
57
- if (platform() == "mac") {
58
- return await execSync(`osascript -e '${appleScriptSetFrontmost(appName)}'`);
59
- } else if (platform() == "linux") {
60
- // TODO: This needs fixing
61
- return;
62
- } else if (platform() == "windows") {
63
- return runPwsh(appName, "Focus");
64
- }
65
- } catch (error) {
66
- logger.error(error);
55
+ if (config.TD_VM) {
56
+ let result = await sandbox.send({type: "commands.run", command: `/home/user/scripts/control_window.sh "${appName}" Focus`});
57
+ if (result.type == "error") {
58
+ logger.error(result.error.result.stdout);
67
59
  }
60
+
61
+ return;
62
+ }
63
+
64
+ try {
65
+ if (platform() == "mac") {
66
+ return await execSync(`osascript -e '${appleScriptSetFrontmost(appName)}'`);
67
+ } else if (platform() == "linux") {
68
+ // TODO: This needs fixing
69
+ return;
70
+ } else if (platform() == "windows") {
71
+ return runPwsh(appName, "Focus");
72
+ }
73
+ } catch (error) {
74
+ logger.error(error);
68
75
  }
69
76
  }
70
77