copilot-liku-cli 0.0.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 (71) hide show
  1. package/ARCHITECTURE.md +411 -0
  2. package/CONFIGURATION.md +302 -0
  3. package/CONTRIBUTING.md +225 -0
  4. package/ELECTRON_README.md +121 -0
  5. package/INSTALLATION.md +350 -0
  6. package/LICENSE.md +1 -0
  7. package/PROJECT_STATUS.md +229 -0
  8. package/QUICKSTART.md +255 -0
  9. package/README.md +167 -0
  10. package/TESTING.md +274 -0
  11. package/package.json +61 -0
  12. package/scripts/start.js +30 -0
  13. package/src/assets/tray-icon.png +0 -0
  14. package/src/cli/commands/agent.js +327 -0
  15. package/src/cli/commands/click.js +108 -0
  16. package/src/cli/commands/drag.js +85 -0
  17. package/src/cli/commands/find.js +109 -0
  18. package/src/cli/commands/keys.js +132 -0
  19. package/src/cli/commands/mouse.js +79 -0
  20. package/src/cli/commands/repl.js +290 -0
  21. package/src/cli/commands/screenshot.js +72 -0
  22. package/src/cli/commands/scroll.js +74 -0
  23. package/src/cli/commands/start.js +67 -0
  24. package/src/cli/commands/type.js +57 -0
  25. package/src/cli/commands/wait.js +84 -0
  26. package/src/cli/commands/window.js +104 -0
  27. package/src/cli/liku.js +249 -0
  28. package/src/cli/util/output.js +174 -0
  29. package/src/main/agents/base-agent.js +410 -0
  30. package/src/main/agents/builder.js +484 -0
  31. package/src/main/agents/index.js +62 -0
  32. package/src/main/agents/orchestrator.js +362 -0
  33. package/src/main/agents/researcher.js +511 -0
  34. package/src/main/agents/state-manager.js +344 -0
  35. package/src/main/agents/supervisor.js +365 -0
  36. package/src/main/agents/verifier.js +452 -0
  37. package/src/main/ai-service.js +1633 -0
  38. package/src/main/index.js +2208 -0
  39. package/src/main/inspect-service.js +467 -0
  40. package/src/main/system-automation.js +1186 -0
  41. package/src/main/ui-automation/config.js +76 -0
  42. package/src/main/ui-automation/core/helpers.js +41 -0
  43. package/src/main/ui-automation/core/index.js +15 -0
  44. package/src/main/ui-automation/core/powershell.js +82 -0
  45. package/src/main/ui-automation/elements/finder.js +274 -0
  46. package/src/main/ui-automation/elements/index.js +14 -0
  47. package/src/main/ui-automation/elements/wait.js +66 -0
  48. package/src/main/ui-automation/index.js +164 -0
  49. package/src/main/ui-automation/interactions/element-click.js +211 -0
  50. package/src/main/ui-automation/interactions/high-level.js +230 -0
  51. package/src/main/ui-automation/interactions/index.js +47 -0
  52. package/src/main/ui-automation/keyboard/index.js +15 -0
  53. package/src/main/ui-automation/keyboard/input.js +179 -0
  54. package/src/main/ui-automation/mouse/click.js +186 -0
  55. package/src/main/ui-automation/mouse/drag.js +88 -0
  56. package/src/main/ui-automation/mouse/index.js +30 -0
  57. package/src/main/ui-automation/mouse/movement.js +51 -0
  58. package/src/main/ui-automation/mouse/scroll.js +116 -0
  59. package/src/main/ui-automation/screenshot.js +183 -0
  60. package/src/main/ui-automation/window/index.js +23 -0
  61. package/src/main/ui-automation/window/manager.js +305 -0
  62. package/src/main/utils/time.js +62 -0
  63. package/src/main/visual-awareness.js +597 -0
  64. package/src/renderer/chat/chat.js +671 -0
  65. package/src/renderer/chat/index.html +725 -0
  66. package/src/renderer/chat/preload.js +112 -0
  67. package/src/renderer/overlay/index.html +648 -0
  68. package/src/renderer/overlay/overlay.js +782 -0
  69. package/src/renderer/overlay/preload.js +90 -0
  70. package/src/shared/grid-math.js +82 -0
  71. package/src/shared/inspect-types.js +230 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * repl command - Interactive automation shell
3
+ * @module cli/commands/repl
4
+ */
5
+
6
+ const readline = require('readline');
7
+ const path = require('path');
8
+ const { success, error, info, warn, highlight, dim, bold } = require('../util/output');
9
+
10
+ const UI_MODULE = path.resolve(__dirname, '../../main/ui-automation');
11
+ let ui;
12
+
13
+ function loadUI() {
14
+ if (!ui) {
15
+ ui = require(UI_MODULE);
16
+ }
17
+ return ui;
18
+ }
19
+
20
+ /**
21
+ * Show REPL help
22
+ */
23
+ function showHelp() {
24
+ console.log(`
25
+ ${bold('Liku Interactive Shell')}
26
+ ${dim('Type commands to execute UI automation')}
27
+
28
+ ${highlight('Commands:')}
29
+ click <text|x,y> Click element or coordinates
30
+ find <text> Find elements matching text
31
+ type <text> Type text
32
+ keys <combo> Send key combo (ctrl+c, enter, etc.)
33
+ window [title] List or focus windows
34
+ mouse <x> <y> Move mouse
35
+ pos Show mouse position
36
+ screenshot [path] Take screenshot
37
+ scroll <up|down> [n] Scroll direction
38
+ wait <text> [ms] Wait for element
39
+ sleep <ms> Wait for milliseconds
40
+
41
+ ${highlight('Special:')}
42
+ help Show this help
43
+ clear Clear screen
44
+ exit, quit, q Exit REPL
45
+
46
+ ${highlight('Examples:')}
47
+ ${dim('> click "Submit"')}
48
+ ${dim('> find "Save" | Button')}
49
+ ${dim('> type "Hello World"')}
50
+ ${dim('> keys ctrl+s')}
51
+ ${dim('> window "Notepad"')}
52
+ `);
53
+ }
54
+
55
+ /**
56
+ * Parse and execute a REPL command
57
+ */
58
+ async function executeCommand(line) {
59
+ const parts = line.trim().split(/\s+/);
60
+ const cmd = parts[0]?.toLowerCase();
61
+ const args = parts.slice(1);
62
+
63
+ if (!cmd) return;
64
+
65
+ switch (cmd) {
66
+ case 'help':
67
+ case '?':
68
+ showHelp();
69
+ break;
70
+
71
+ case 'clear':
72
+ case 'cls':
73
+ console.clear();
74
+ break;
75
+
76
+ case 'exit':
77
+ case 'quit':
78
+ case 'q':
79
+ return 'exit';
80
+
81
+ case 'click': {
82
+ const target = args.join(' ');
83
+ if (!target) {
84
+ error('Usage: click <text|x,y>');
85
+ break;
86
+ }
87
+
88
+ const coordMatch = target.match(/^(\d+)[,\s]+(\d+)$/);
89
+ if (coordMatch) {
90
+ const x = parseInt(coordMatch[1], 10);
91
+ const y = parseInt(coordMatch[2], 10);
92
+ const result = await ui.clickAt(x, y);
93
+ result.success ? success(`Clicked at (${x}, ${y})`) : error('Click failed');
94
+ } else {
95
+ const result = await ui.click({ text: target });
96
+ result.success
97
+ ? success(`Clicked "${result.element?.name || target}"`)
98
+ : error(`Not found: "${target}"`);
99
+ }
100
+ break;
101
+ }
102
+
103
+ case 'find': {
104
+ const text = args.join(' ');
105
+ if (!text) {
106
+ error('Usage: find <text>');
107
+ break;
108
+ }
109
+
110
+ // Check for type filter: find "text" | Button
111
+ const filterMatch = text.match(/^(.+?)\s*\|\s*(\w+)$/);
112
+ const criteria = filterMatch
113
+ ? { text: filterMatch[1].trim(), controlType: filterMatch[2] }
114
+ : { text };
115
+
116
+ const result = await ui.findElements(criteria);
117
+ if (result.count === 0) {
118
+ info('No elements found');
119
+ } else {
120
+ console.log(`Found ${result.count} elements:`);
121
+ result.elements.slice(0, 10).forEach((el, i) => {
122
+ console.log(` ${i + 1}. ${el.name || '(unnamed)'} [${el.controlType}] @ ${el.bounds?.x},${el.bounds?.y}`);
123
+ });
124
+ if (result.count > 10) {
125
+ console.log(dim(` ... and ${result.count - 10} more`));
126
+ }
127
+ }
128
+ break;
129
+ }
130
+
131
+ case 'type': {
132
+ const text = args.join(' ');
133
+ if (!text) {
134
+ error('Usage: type <text>');
135
+ break;
136
+ }
137
+ const result = await ui.typeText(text);
138
+ result.success ? success(`Typed ${text.length} chars`) : error('Type failed');
139
+ break;
140
+ }
141
+
142
+ case 'keys':
143
+ case 'key': {
144
+ const combo = args.join(' ');
145
+ if (!combo) {
146
+ error('Usage: keys <combo>');
147
+ break;
148
+ }
149
+ // Simple conversion
150
+ const sendKeys = combo
151
+ .replace(/ctrl\+/gi, '^')
152
+ .replace(/alt\+/gi, '%')
153
+ .replace(/shift\+/gi, '+')
154
+ .replace(/enter/gi, '{ENTER}')
155
+ .replace(/tab/gi, '{TAB}')
156
+ .replace(/esc(ape)?/gi, '{ESC}');
157
+ const result = await ui.sendKeys(sendKeys);
158
+ result.success ? success(`Sent: ${combo}`) : error('Keys failed');
159
+ break;
160
+ }
161
+
162
+ case 'window':
163
+ case 'win': {
164
+ const title = args.join(' ');
165
+ if (title) {
166
+ const result = await ui.focusWindow({ title });
167
+ result.success
168
+ ? success(`Focused: ${result.window?.title || title}`)
169
+ : error(`Window not found: "${title}"`);
170
+ } else {
171
+ const windows = await ui.findWindows({});
172
+ console.log(`${windows.length} windows:`);
173
+ windows.slice(0, 15).forEach((w, i) => {
174
+ console.log(` ${i + 1}. ${w.title?.substring(0, 50) || '(untitled)'} [${w.processName}]`);
175
+ });
176
+ }
177
+ break;
178
+ }
179
+
180
+ case 'mouse':
181
+ case 'move': {
182
+ if (args.length < 2) {
183
+ error('Usage: mouse <x> <y>');
184
+ break;
185
+ }
186
+ const x = parseInt(args[0], 10);
187
+ const y = parseInt(args[1], 10);
188
+ const result = await ui.moveMouse(x, y);
189
+ result.success ? success(`Moved to (${x}, ${y})`) : error('Move failed');
190
+ break;
191
+ }
192
+
193
+ case 'pos':
194
+ case 'position': {
195
+ const pos = await ui.getMousePosition();
196
+ console.log(`Mouse: (${pos.x}, ${pos.y})`);
197
+ break;
198
+ }
199
+
200
+ case 'screenshot':
201
+ case 'ss': {
202
+ const savePath = args[0] || `screenshot_${Date.now()}.png`;
203
+ const result = await ui.screenshot({ path: savePath });
204
+ result.success ? success(`Saved: ${result.path}`) : error('Screenshot failed');
205
+ break;
206
+ }
207
+
208
+ case 'scroll': {
209
+ const dir = args[0]?.toLowerCase();
210
+ const amount = parseInt(args[1], 10) || 3;
211
+ if (!['up', 'down', 'left', 'right'].includes(dir)) {
212
+ error('Usage: scroll <up|down|left|right> [amount]');
213
+ break;
214
+ }
215
+ const fn = { up: 'scrollUp', down: 'scrollDown', left: 'scrollLeft', right: 'scrollRight' }[dir];
216
+ const result = await ui[fn](amount);
217
+ result.success ? success(`Scrolled ${dir}`) : error('Scroll failed');
218
+ break;
219
+ }
220
+
221
+ case 'wait': {
222
+ const text = args[0];
223
+ const timeout = parseInt(args[1], 10) || 5000;
224
+ if (!text) {
225
+ error('Usage: wait <text> [timeout]');
226
+ break;
227
+ }
228
+ info(`Waiting for "${text}"...`);
229
+ const result = await ui.waitForElement({ text }, { timeout });
230
+ result.success
231
+ ? success(`Found after ${result.elapsed}ms`)
232
+ : warn(`Not found within ${timeout}ms`);
233
+ break;
234
+ }
235
+
236
+ case 'sleep':
237
+ case 'delay': {
238
+ const ms = parseInt(args[0], 10) || 1000;
239
+ await ui.sleep(ms);
240
+ success(`Waited ${ms}ms`);
241
+ break;
242
+ }
243
+
244
+ default:
245
+ error(`Unknown command: ${cmd}`);
246
+ info('Type "help" for available commands');
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Run the REPL
252
+ */
253
+ async function run(args, options) {
254
+ loadUI();
255
+
256
+ console.log(`
257
+ ${bold('Liku Interactive Shell')} ${dim('v1.0')}
258
+ ${dim('Type "help" for commands, "exit" to quit')}
259
+ `);
260
+
261
+ const rl = readline.createInterface({
262
+ input: process.stdin,
263
+ output: process.stdout,
264
+ prompt: highlight('liku> '),
265
+ });
266
+
267
+ rl.prompt();
268
+
269
+ return new Promise((resolve) => {
270
+ rl.on('line', async (line) => {
271
+ try {
272
+ const result = await executeCommand(line);
273
+ if (result === 'exit') {
274
+ rl.close();
275
+ return;
276
+ }
277
+ } catch (err) {
278
+ error(err.message);
279
+ }
280
+ rl.prompt();
281
+ });
282
+
283
+ rl.on('close', () => {
284
+ console.log('\nGoodbye!');
285
+ resolve({ success: true });
286
+ });
287
+ });
288
+ }
289
+
290
+ module.exports = { run };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * screenshot command - Capture screenshot
3
+ * @module cli/commands/screenshot
4
+ */
5
+
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const { success, error, info } = require('../util/output');
9
+
10
+ const UI_MODULE = path.resolve(__dirname, '../../main/ui-automation');
11
+ let ui;
12
+
13
+ function loadUI() {
14
+ if (!ui) {
15
+ ui = require(UI_MODULE);
16
+ }
17
+ return ui;
18
+ }
19
+
20
+ /**
21
+ * Run the screenshot command
22
+ *
23
+ * Usage:
24
+ * liku screenshot # Save to temp with timestamp
25
+ * liku screenshot ./capture.png # Save to specific path
26
+ * liku screenshot --clipboard # Copy to clipboard (TODO)
27
+ */
28
+ async function run(args, options) {
29
+ loadUI();
30
+
31
+ // Determine output path
32
+ let outputPath = args[0];
33
+
34
+ if (!outputPath) {
35
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
36
+ outputPath = path.join(process.cwd(), `screenshot_${timestamp}.png`);
37
+ } else {
38
+ // Resolve relative paths
39
+ if (!path.isAbsolute(outputPath)) {
40
+ outputPath = path.resolve(process.cwd(), outputPath);
41
+ }
42
+ }
43
+
44
+ // Ensure directory exists
45
+ const dir = path.dirname(outputPath);
46
+ if (!fs.existsSync(dir)) {
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ }
49
+
50
+ if (!options.quiet) {
51
+ info('Capturing screenshot...');
52
+ }
53
+
54
+ const result = await ui.screenshot({ path: outputPath });
55
+
56
+ if (result.success) {
57
+ if (!options.quiet) {
58
+ success(`Screenshot saved: ${result.path}`);
59
+ }
60
+ return {
61
+ success: true,
62
+ path: result.path,
63
+ // Include base64 if JSON output requested
64
+ ...(options.json && result.base64 ? { base64: result.base64 } : {}),
65
+ };
66
+ } else {
67
+ error(`Screenshot failed: ${result.error || 'Unknown error'}`);
68
+ return { success: false, error: result.error };
69
+ }
70
+ }
71
+
72
+ module.exports = { run };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * scroll command - Scroll up or down
3
+ * @module cli/commands/scroll
4
+ */
5
+
6
+ const path = require('path');
7
+ const { success, error, info } = require('../util/output');
8
+
9
+ const UI_MODULE = path.resolve(__dirname, '../../main/ui-automation');
10
+ let ui;
11
+
12
+ function loadUI() {
13
+ if (!ui) {
14
+ ui = require(UI_MODULE);
15
+ }
16
+ return ui;
17
+ }
18
+
19
+ /**
20
+ * Run the scroll command
21
+ *
22
+ * Usage:
23
+ * liku scroll up
24
+ * liku scroll down 5
25
+ * liku scroll left 3
26
+ */
27
+ async function run(args, options) {
28
+ loadUI();
29
+
30
+ if (args.length === 0) {
31
+ error('Usage: liku scroll <up|down|left|right> [amount]');
32
+ return { success: false };
33
+ }
34
+
35
+ const direction = args[0].toLowerCase();
36
+ const amount = args[1] ? parseInt(args[1], 10) : 3;
37
+
38
+ if (!['up', 'down', 'left', 'right'].includes(direction)) {
39
+ error('Direction must be: up, down, left, or right');
40
+ return { success: false };
41
+ }
42
+
43
+ if (!options.quiet) {
44
+ info(`Scrolling ${direction} by ${amount}...`);
45
+ }
46
+
47
+ let result;
48
+ switch (direction) {
49
+ case 'up':
50
+ result = await ui.scrollUp(amount);
51
+ break;
52
+ case 'down':
53
+ result = await ui.scrollDown(amount);
54
+ break;
55
+ case 'left':
56
+ result = await ui.scrollLeft(amount);
57
+ break;
58
+ case 'right':
59
+ result = await ui.scrollRight(amount);
60
+ break;
61
+ }
62
+
63
+ if (result.success) {
64
+ if (!options.quiet) {
65
+ success(`Scrolled ${direction} by ${amount}`);
66
+ }
67
+ return { success: true, direction, amount };
68
+ } else {
69
+ error(`Scroll failed: ${result.error || 'Unknown error'}`);
70
+ return { success: false, error: result.error };
71
+ }
72
+ }
73
+
74
+ module.exports = { run };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * start command - Launch the Electron agent
3
+ * @module cli/commands/start
4
+ */
5
+
6
+ const { spawn } = require('child_process');
7
+ const path = require('path');
8
+ const { success, info, error } = require('../util/output');
9
+
10
+ const PROJECT_ROOT = path.resolve(__dirname, '../../..');
11
+
12
+ /**
13
+ * Run the start command
14
+ */
15
+ async function run(args, options) {
16
+ if (!options.quiet) {
17
+ info('Starting Copilot-Liku agent...');
18
+ }
19
+
20
+ return new Promise((resolve, reject) => {
21
+ // Copy environment and clear ELECTRON_RUN_AS_NODE
22
+ const env = { ...process.env };
23
+ delete env.ELECTRON_RUN_AS_NODE;
24
+
25
+ // Get electron path
26
+ let electronPath;
27
+ try {
28
+ electronPath = require('electron');
29
+ } catch (e) {
30
+ error('Electron not found. Run: npm install');
31
+ return reject(new Error('Electron not installed'));
32
+ }
33
+
34
+ const child = spawn(electronPath, ['.'], {
35
+ cwd: PROJECT_ROOT,
36
+ env,
37
+ stdio: options.quiet ? 'ignore' : 'inherit',
38
+ detached: options.background || false,
39
+ windowsHide: false,
40
+ });
41
+
42
+ if (options.background) {
43
+ // Detach and let it run
44
+ child.unref();
45
+ if (!options.quiet) {
46
+ success('Agent started in background');
47
+ }
48
+ resolve({ success: true, pid: child.pid });
49
+ } else {
50
+ // Wait for exit
51
+ child.on('exit', (code, signal) => {
52
+ if (signal) {
53
+ resolve({ success: true, signal });
54
+ } else {
55
+ resolve({ success: code === 0, code });
56
+ }
57
+ });
58
+
59
+ child.on('error', (err) => {
60
+ error(`Failed to start: ${err.message}`);
61
+ reject(err);
62
+ });
63
+ }
64
+ });
65
+ }
66
+
67
+ module.exports = { run };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * type command - Type text at cursor position
3
+ * @module cli/commands/type
4
+ */
5
+
6
+ const path = require('path');
7
+ const { success, error, info } = require('../util/output');
8
+
9
+ const UI_MODULE = path.resolve(__dirname, '../../main/ui-automation');
10
+ let ui;
11
+
12
+ function loadUI() {
13
+ if (!ui) {
14
+ ui = require(UI_MODULE);
15
+ }
16
+ return ui;
17
+ }
18
+
19
+ /**
20
+ * Run the type command
21
+ *
22
+ * Usage:
23
+ * liku type "Hello World"
24
+ * liku type "slow typing" --delay 100
25
+ */
26
+ async function run(args, options) {
27
+ if (args.length === 0) {
28
+ error('Usage: liku type <text> [--delay <ms>]');
29
+ return { success: false };
30
+ }
31
+
32
+ loadUI();
33
+ const text = args.join(' ');
34
+
35
+ if (!options.quiet) {
36
+ info(`Typing: "${text.substring(0, 30)}${text.length > 30 ? '...' : ''}"`);
37
+ }
38
+
39
+ const typeOptions = {};
40
+ if (options.delay) {
41
+ typeOptions.delay = parseInt(options.delay, 10);
42
+ }
43
+
44
+ const result = await ui.typeText(text, typeOptions);
45
+
46
+ if (result.success) {
47
+ if (!options.quiet) {
48
+ success(`Typed ${text.length} characters`);
49
+ }
50
+ return { success: true, length: text.length };
51
+ } else {
52
+ error(`Type failed: ${result.error || 'Unknown error'}`);
53
+ return { success: false, error: result.error };
54
+ }
55
+ }
56
+
57
+ module.exports = { run };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * wait command - Wait for element to appear
3
+ * @module cli/commands/wait
4
+ */
5
+
6
+ const path = require('path');
7
+ const { success, error, info, Spinner } = require('../util/output');
8
+
9
+ const UI_MODULE = path.resolve(__dirname, '../../main/ui-automation');
10
+ let ui;
11
+
12
+ function loadUI() {
13
+ if (!ui) {
14
+ ui = require(UI_MODULE);
15
+ }
16
+ return ui;
17
+ }
18
+
19
+ /**
20
+ * Run the wait command
21
+ *
22
+ * Usage:
23
+ * liku wait "Loading..." # Wait up to 10s for element
24
+ * liku wait "Submit" 5000 # Wait up to 5s
25
+ * liku wait "Dialog" --gone # Wait for element to disappear
26
+ */
27
+ async function run(args, options) {
28
+ loadUI();
29
+
30
+ if (args.length === 0) {
31
+ error('Usage: liku wait <text> [timeout] [--gone]');
32
+ return { success: false };
33
+ }
34
+
35
+ const searchText = args[0];
36
+ const timeout = args[1] ? parseInt(args[1], 10) : 10000;
37
+ const waitGone = options.gone || false;
38
+
39
+ const spinner = !options.quiet ? new Spinner(
40
+ waitGone
41
+ ? `Waiting for "${searchText}" to disappear`
42
+ : `Waiting for "${searchText}"`
43
+ ) : null;
44
+
45
+ spinner?.start();
46
+
47
+ const criteria = { text: searchText };
48
+
49
+ if (options.type) {
50
+ criteria.controlType = options.type;
51
+ }
52
+
53
+ const result = waitGone
54
+ ? await ui.waitForElementGone(criteria, timeout)
55
+ : await ui.waitForElement(criteria, { timeout });
56
+
57
+ spinner?.stop();
58
+
59
+ if (result.success) {
60
+ if (!options.quiet) {
61
+ success(
62
+ waitGone
63
+ ? `"${searchText}" disappeared after ${result.elapsed}ms`
64
+ : `Found "${searchText}" after ${result.elapsed}ms`
65
+ );
66
+ }
67
+ return {
68
+ success: true,
69
+ elapsed: result.elapsed,
70
+ element: result.element,
71
+ };
72
+ } else {
73
+ if (!options.quiet) {
74
+ error(
75
+ waitGone
76
+ ? `"${searchText}" did not disappear within ${timeout}ms`
77
+ : `"${searchText}" not found within ${timeout}ms`
78
+ );
79
+ }
80
+ return { success: false, elapsed: result.elapsed, timeout };
81
+ }
82
+ }
83
+
84
+ module.exports = { run };