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.
- package/ARCHITECTURE.md +411 -0
- package/CONFIGURATION.md +302 -0
- package/CONTRIBUTING.md +225 -0
- package/ELECTRON_README.md +121 -0
- package/INSTALLATION.md +350 -0
- package/LICENSE.md +1 -0
- package/PROJECT_STATUS.md +229 -0
- package/QUICKSTART.md +255 -0
- package/README.md +167 -0
- package/TESTING.md +274 -0
- package/package.json +61 -0
- package/scripts/start.js +30 -0
- package/src/assets/tray-icon.png +0 -0
- package/src/cli/commands/agent.js +327 -0
- package/src/cli/commands/click.js +108 -0
- package/src/cli/commands/drag.js +85 -0
- package/src/cli/commands/find.js +109 -0
- package/src/cli/commands/keys.js +132 -0
- package/src/cli/commands/mouse.js +79 -0
- package/src/cli/commands/repl.js +290 -0
- package/src/cli/commands/screenshot.js +72 -0
- package/src/cli/commands/scroll.js +74 -0
- package/src/cli/commands/start.js +67 -0
- package/src/cli/commands/type.js +57 -0
- package/src/cli/commands/wait.js +84 -0
- package/src/cli/commands/window.js +104 -0
- package/src/cli/liku.js +249 -0
- package/src/cli/util/output.js +174 -0
- package/src/main/agents/base-agent.js +410 -0
- package/src/main/agents/builder.js +484 -0
- package/src/main/agents/index.js +62 -0
- package/src/main/agents/orchestrator.js +362 -0
- package/src/main/agents/researcher.js +511 -0
- package/src/main/agents/state-manager.js +344 -0
- package/src/main/agents/supervisor.js +365 -0
- package/src/main/agents/verifier.js +452 -0
- package/src/main/ai-service.js +1633 -0
- package/src/main/index.js +2208 -0
- package/src/main/inspect-service.js +467 -0
- package/src/main/system-automation.js +1186 -0
- package/src/main/ui-automation/config.js +76 -0
- package/src/main/ui-automation/core/helpers.js +41 -0
- package/src/main/ui-automation/core/index.js +15 -0
- package/src/main/ui-automation/core/powershell.js +82 -0
- package/src/main/ui-automation/elements/finder.js +274 -0
- package/src/main/ui-automation/elements/index.js +14 -0
- package/src/main/ui-automation/elements/wait.js +66 -0
- package/src/main/ui-automation/index.js +164 -0
- package/src/main/ui-automation/interactions/element-click.js +211 -0
- package/src/main/ui-automation/interactions/high-level.js +230 -0
- package/src/main/ui-automation/interactions/index.js +47 -0
- package/src/main/ui-automation/keyboard/index.js +15 -0
- package/src/main/ui-automation/keyboard/input.js +179 -0
- package/src/main/ui-automation/mouse/click.js +186 -0
- package/src/main/ui-automation/mouse/drag.js +88 -0
- package/src/main/ui-automation/mouse/index.js +30 -0
- package/src/main/ui-automation/mouse/movement.js +51 -0
- package/src/main/ui-automation/mouse/scroll.js +116 -0
- package/src/main/ui-automation/screenshot.js +183 -0
- package/src/main/ui-automation/window/index.js +23 -0
- package/src/main/ui-automation/window/manager.js +305 -0
- package/src/main/utils/time.js +62 -0
- package/src/main/visual-awareness.js +597 -0
- package/src/renderer/chat/chat.js +671 -0
- package/src/renderer/chat/index.html +725 -0
- package/src/renderer/chat/preload.js +112 -0
- package/src/renderer/overlay/index.html +648 -0
- package/src/renderer/overlay/overlay.js +782 -0
- package/src/renderer/overlay/preload.js +90 -0
- package/src/shared/grid-math.js +82 -0
- 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 };
|