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,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* click command - Click element by text or coordinates
|
|
3
|
+
* @module cli/commands/click
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { success, error, info, Spinner } = require('../util/output');
|
|
8
|
+
|
|
9
|
+
// Load UI automation module
|
|
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 click command
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* liku click "Button Text"
|
|
25
|
+
* liku click 500,300
|
|
26
|
+
* liku click "Submit" --double
|
|
27
|
+
* liku click "Menu" --right
|
|
28
|
+
*/
|
|
29
|
+
async function run(args, options) {
|
|
30
|
+
if (args.length === 0) {
|
|
31
|
+
error('Usage: liku click <text|x,y> [--double] [--right] [--wait <ms>]');
|
|
32
|
+
return { success: false };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
loadUI();
|
|
36
|
+
const target = args.join(' ');
|
|
37
|
+
|
|
38
|
+
// Check if target is coordinates (e.g., "500,300" or "500 300")
|
|
39
|
+
const coordMatch = target.match(/^(\d+)[,\s]+(\d+)$/);
|
|
40
|
+
|
|
41
|
+
if (coordMatch) {
|
|
42
|
+
// Click at coordinates
|
|
43
|
+
const x = parseInt(coordMatch[1], 10);
|
|
44
|
+
const y = parseInt(coordMatch[2], 10);
|
|
45
|
+
|
|
46
|
+
if (!options.quiet) {
|
|
47
|
+
info(`Clicking at (${x}, ${y})...`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const button = options.right ? 'right' : 'left';
|
|
51
|
+
const clickFn = options.double ? ui.doubleClickAt : ui.clickAt;
|
|
52
|
+
|
|
53
|
+
const result = await clickFn(x, y, { button });
|
|
54
|
+
|
|
55
|
+
if (result.success) {
|
|
56
|
+
if (!options.quiet) {
|
|
57
|
+
success(`Clicked at (${x}, ${y})`);
|
|
58
|
+
}
|
|
59
|
+
return { success: true, x, y, method: 'coordinates' };
|
|
60
|
+
} else {
|
|
61
|
+
error(`Click failed: ${result.error || 'Unknown error'}`);
|
|
62
|
+
return { success: false, error: result.error };
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// Click by text
|
|
66
|
+
const spinner = !options.quiet ? new Spinner(`Searching for "${target}"`) : null;
|
|
67
|
+
spinner?.start();
|
|
68
|
+
|
|
69
|
+
const criteria = { text: target };
|
|
70
|
+
|
|
71
|
+
// Add control type filter if specified
|
|
72
|
+
if (options.type) {
|
|
73
|
+
criteria.controlType = options.type;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Add window filter if specified
|
|
77
|
+
if (options.window) {
|
|
78
|
+
criteria.windowTitle = options.window;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const clickOptions = {
|
|
82
|
+
button: options.right ? 'right' : 'left',
|
|
83
|
+
doubleClick: options.double || false,
|
|
84
|
+
waitTimeout: options.wait ? parseInt(options.wait, 10) : 0,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const result = await ui.click(criteria, clickOptions);
|
|
88
|
+
|
|
89
|
+
spinner?.stop();
|
|
90
|
+
|
|
91
|
+
if (result.success) {
|
|
92
|
+
if (!options.quiet) {
|
|
93
|
+
const element = result.element;
|
|
94
|
+
success(`Clicked "${element?.name || target}" (${element?.controlType || 'unknown'})`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
success: true,
|
|
98
|
+
element: result.element,
|
|
99
|
+
method: 'text',
|
|
100
|
+
};
|
|
101
|
+
} else {
|
|
102
|
+
error(`Element not found: "${target}"`);
|
|
103
|
+
return { success: false, error: result.error || 'Element not found' };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { run };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* drag command - Drag from one point to another
|
|
3
|
+
* @module cli/commands/drag
|
|
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 drag command
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* liku drag 100 100 500 300
|
|
24
|
+
* liku drag 100,100 to 500,300
|
|
25
|
+
*/
|
|
26
|
+
async function run(args, options) {
|
|
27
|
+
loadUI();
|
|
28
|
+
|
|
29
|
+
// Parse coordinates
|
|
30
|
+
let fromX, fromY, toX, toY;
|
|
31
|
+
|
|
32
|
+
// Remove "to" keyword if present
|
|
33
|
+
const cleanArgs = args.filter(a => a.toLowerCase() !== 'to');
|
|
34
|
+
|
|
35
|
+
if (cleanArgs.length === 2) {
|
|
36
|
+
// Format: "100,100" "500,300"
|
|
37
|
+
const from = cleanArgs[0].split(',');
|
|
38
|
+
const to = cleanArgs[1].split(',');
|
|
39
|
+
fromX = parseInt(from[0], 10);
|
|
40
|
+
fromY = parseInt(from[1], 10);
|
|
41
|
+
toX = parseInt(to[0], 10);
|
|
42
|
+
toY = parseInt(to[1], 10);
|
|
43
|
+
} else if (cleanArgs.length >= 4) {
|
|
44
|
+
// Format: "100 100 500 300"
|
|
45
|
+
fromX = parseInt(cleanArgs[0], 10);
|
|
46
|
+
fromY = parseInt(cleanArgs[1], 10);
|
|
47
|
+
toX = parseInt(cleanArgs[2], 10);
|
|
48
|
+
toY = parseInt(cleanArgs[3], 10);
|
|
49
|
+
} else {
|
|
50
|
+
error('Usage: liku drag <fromX> <fromY> <toX> <toY>');
|
|
51
|
+
info('Example: liku drag 100 100 500 300');
|
|
52
|
+
return { success: false };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if ([fromX, fromY, toX, toY].some(isNaN)) {
|
|
56
|
+
error('Invalid coordinates. Use numbers.');
|
|
57
|
+
return { success: false };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!options.quiet) {
|
|
61
|
+
info(`Dragging from (${fromX}, ${fromY}) to (${toX}, ${toY})...`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const dragOptions = {};
|
|
65
|
+
if (options.steps) {
|
|
66
|
+
dragOptions.steps = parseInt(options.steps, 10);
|
|
67
|
+
}
|
|
68
|
+
if (options.delay) {
|
|
69
|
+
dragOptions.stepDelay = parseInt(options.delay, 10);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await ui.drag(fromX, fromY, toX, toY, dragOptions);
|
|
73
|
+
|
|
74
|
+
if (result.success) {
|
|
75
|
+
if (!options.quiet) {
|
|
76
|
+
success(`Dragged from (${fromX}, ${fromY}) to (${toX}, ${toY})`);
|
|
77
|
+
}
|
|
78
|
+
return { success: true, from: { x: fromX, y: fromY }, to: { x: toX, y: toY } };
|
|
79
|
+
} else {
|
|
80
|
+
error(`Drag failed: ${result.error || 'Unknown error'}`);
|
|
81
|
+
return { success: false, error: result.error };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { run };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* find command - Find UI elements matching criteria
|
|
3
|
+
* @module cli/commands/find
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { success, error, info, table, dim, highlight } = 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 find command
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* liku find "Button Text"
|
|
24
|
+
* liku find "Save" --type Button
|
|
25
|
+
* liku find "*" --type Edit --window "Notepad"
|
|
26
|
+
*/
|
|
27
|
+
async function run(args, options) {
|
|
28
|
+
if (args.length === 0) {
|
|
29
|
+
error('Usage: liku find <text> [--type <ControlType>] [--window <title>]');
|
|
30
|
+
return { success: false };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
loadUI();
|
|
34
|
+
const searchText = args.join(' ');
|
|
35
|
+
|
|
36
|
+
if (!options.quiet) {
|
|
37
|
+
info(`Searching for elements matching "${searchText}"...`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const criteria = {};
|
|
41
|
+
|
|
42
|
+
// Handle wildcard search (find all of a type)
|
|
43
|
+
if (searchText !== '*') {
|
|
44
|
+
criteria.text = searchText;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (options.type) {
|
|
48
|
+
criteria.controlType = options.type;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (options.window) {
|
|
52
|
+
criteria.windowTitle = options.window;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (options.id) {
|
|
56
|
+
criteria.automationId = options.id;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (options.class) {
|
|
60
|
+
criteria.className = options.class;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result = await ui.findElements(criteria);
|
|
64
|
+
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
error(`Search failed: ${result.error}`);
|
|
67
|
+
return { success: false, error: result.error };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (result.count === 0) {
|
|
71
|
+
if (!options.quiet) {
|
|
72
|
+
info('No elements found matching criteria');
|
|
73
|
+
}
|
|
74
|
+
return { success: true, elements: [], count: 0 };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!options.quiet && !options.json) {
|
|
78
|
+
success(`Found ${result.count} element(s):\n`);
|
|
79
|
+
|
|
80
|
+
// Display as table
|
|
81
|
+
const rows = result.elements.map((el, i) => [
|
|
82
|
+
i + 1,
|
|
83
|
+
el.name?.substring(0, 40) || dim('(unnamed)'),
|
|
84
|
+
el.controlType || '-',
|
|
85
|
+
el.bounds ? `${el.bounds.x},${el.bounds.y}` : '-',
|
|
86
|
+
el.bounds ? `${el.bounds.width}x${el.bounds.height}` : '-',
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
table(rows, ['#', 'Name', 'Type', 'Position', 'Size']);
|
|
90
|
+
|
|
91
|
+
// Show automation IDs if verbose
|
|
92
|
+
if (options.verbose) {
|
|
93
|
+
console.log('\n' + dim('Automation IDs:'));
|
|
94
|
+
result.elements.forEach((el, i) => {
|
|
95
|
+
if (el.automationId) {
|
|
96
|
+
console.log(` ${i + 1}. ${highlight(el.automationId)}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
success: true,
|
|
104
|
+
elements: result.elements,
|
|
105
|
+
count: result.count,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { run };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keys command - Send keyboard shortcuts
|
|
3
|
+
* @module cli/commands/keys
|
|
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
|
+
* Convert human-readable key combo to SendKeys format
|
|
21
|
+
*
|
|
22
|
+
* Examples:
|
|
23
|
+
* ctrl+c → ^c
|
|
24
|
+
* ctrl+shift+s → ^+s
|
|
25
|
+
* alt+f4 → %{F4}
|
|
26
|
+
* enter → {ENTER}
|
|
27
|
+
*/
|
|
28
|
+
function toSendKeys(combo) {
|
|
29
|
+
// Already in SendKeys format
|
|
30
|
+
if (combo.includes('{') || combo.includes('^') || combo.includes('%') || combo.includes('+')) {
|
|
31
|
+
return combo;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const parts = combo.toLowerCase().split(/[+\-]/);
|
|
35
|
+
let modifiers = '';
|
|
36
|
+
let key = '';
|
|
37
|
+
|
|
38
|
+
for (const part of parts) {
|
|
39
|
+
const trimmed = part.trim();
|
|
40
|
+
switch (trimmed) {
|
|
41
|
+
case 'ctrl':
|
|
42
|
+
case 'control':
|
|
43
|
+
modifiers += '^';
|
|
44
|
+
break;
|
|
45
|
+
case 'alt':
|
|
46
|
+
modifiers += '%';
|
|
47
|
+
break;
|
|
48
|
+
case 'shift':
|
|
49
|
+
modifiers += '+';
|
|
50
|
+
break;
|
|
51
|
+
case 'win':
|
|
52
|
+
case 'windows':
|
|
53
|
+
case 'meta':
|
|
54
|
+
// Windows key - use PowerShell workaround
|
|
55
|
+
modifiers += '#';
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
key = trimmed;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Special keys
|
|
63
|
+
const specialKeys = {
|
|
64
|
+
'enter': '{ENTER}',
|
|
65
|
+
'return': '{ENTER}',
|
|
66
|
+
'tab': '{TAB}',
|
|
67
|
+
'esc': '{ESC}',
|
|
68
|
+
'escape': '{ESC}',
|
|
69
|
+
'space': ' ',
|
|
70
|
+
'backspace': '{BACKSPACE}',
|
|
71
|
+
'delete': '{DELETE}',
|
|
72
|
+
'del': '{DELETE}',
|
|
73
|
+
'insert': '{INSERT}',
|
|
74
|
+
'ins': '{INSERT}',
|
|
75
|
+
'home': '{HOME}',
|
|
76
|
+
'end': '{END}',
|
|
77
|
+
'pageup': '{PGUP}',
|
|
78
|
+
'pgup': '{PGUP}',
|
|
79
|
+
'pagedown': '{PGDN}',
|
|
80
|
+
'pgdn': '{PGDN}',
|
|
81
|
+
'up': '{UP}',
|
|
82
|
+
'down': '{DOWN}',
|
|
83
|
+
'left': '{LEFT}',
|
|
84
|
+
'right': '{RIGHT}',
|
|
85
|
+
'f1': '{F1}', 'f2': '{F2}', 'f3': '{F3}', 'f4': '{F4}',
|
|
86
|
+
'f5': '{F5}', 'f6': '{F6}', 'f7': '{F7}', 'f8': '{F8}',
|
|
87
|
+
'f9': '{F9}', 'f10': '{F10}', 'f11': '{F11}', 'f12': '{F12}',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const finalKey = specialKeys[key] || key;
|
|
91
|
+
|
|
92
|
+
return modifiers + finalKey;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Run the keys command
|
|
97
|
+
*
|
|
98
|
+
* Usage:
|
|
99
|
+
* liku keys ctrl+c
|
|
100
|
+
* liku keys ctrl+shift+s
|
|
101
|
+
* liku keys enter
|
|
102
|
+
* liku keys "^c" (raw SendKeys format)
|
|
103
|
+
*/
|
|
104
|
+
async function run(args, options) {
|
|
105
|
+
if (args.length === 0) {
|
|
106
|
+
error('Usage: liku keys <combo>');
|
|
107
|
+
info('Examples: ctrl+c, ctrl+shift+s, alt+f4, enter, tab');
|
|
108
|
+
return { success: false };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
loadUI();
|
|
112
|
+
const combo = args.join(' ');
|
|
113
|
+
const sendKeysFormat = toSendKeys(combo);
|
|
114
|
+
|
|
115
|
+
if (!options.quiet) {
|
|
116
|
+
info(`Sending keys: ${combo} (${sendKeysFormat})`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = await ui.sendKeys(sendKeysFormat);
|
|
120
|
+
|
|
121
|
+
if (result.success) {
|
|
122
|
+
if (!options.quiet) {
|
|
123
|
+
success(`Sent: ${combo}`);
|
|
124
|
+
}
|
|
125
|
+
return { success: true, keys: sendKeysFormat };
|
|
126
|
+
} else {
|
|
127
|
+
error(`Keys failed: ${result.error || 'Unknown error'}`);
|
|
128
|
+
return { success: false, error: result.error };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { run };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mouse command - Move mouse to coordinates
|
|
3
|
+
* @module cli/commands/mouse
|
|
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 mouse command
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* liku mouse 500 300 # Move to coordinates
|
|
24
|
+
* liku mouse --pos # Show current position
|
|
25
|
+
*/
|
|
26
|
+
async function run(args, options) {
|
|
27
|
+
loadUI();
|
|
28
|
+
|
|
29
|
+
// Show current position
|
|
30
|
+
if (options.pos || options.position || args.length === 0) {
|
|
31
|
+
const pos = await ui.getMousePosition();
|
|
32
|
+
|
|
33
|
+
if (!options.quiet && !options.json) {
|
|
34
|
+
console.log(`Mouse position: (${pos.x}, ${pos.y})`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { success: true, x: pos.x, y: pos.y };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Parse coordinates
|
|
41
|
+
let x, y;
|
|
42
|
+
|
|
43
|
+
if (args.length === 1 && args[0].includes(',')) {
|
|
44
|
+
// Format: "500,300"
|
|
45
|
+
const parts = args[0].split(',');
|
|
46
|
+
x = parseInt(parts[0], 10);
|
|
47
|
+
y = parseInt(parts[1], 10);
|
|
48
|
+
} else if (args.length >= 2) {
|
|
49
|
+
// Format: "500 300"
|
|
50
|
+
x = parseInt(args[0], 10);
|
|
51
|
+
y = parseInt(args[1], 10);
|
|
52
|
+
} else {
|
|
53
|
+
error('Usage: liku mouse <x> <y>');
|
|
54
|
+
return { success: false };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isNaN(x) || isNaN(y)) {
|
|
58
|
+
error('Invalid coordinates. Use numbers like: liku mouse 500 300');
|
|
59
|
+
return { success: false };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!options.quiet) {
|
|
63
|
+
info(`Moving mouse to (${x}, ${y})...`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = await ui.moveMouse(x, y);
|
|
67
|
+
|
|
68
|
+
if (result.success) {
|
|
69
|
+
if (!options.quiet) {
|
|
70
|
+
success(`Mouse moved to (${x}, ${y})`);
|
|
71
|
+
}
|
|
72
|
+
return { success: true, x, y };
|
|
73
|
+
} else {
|
|
74
|
+
error(`Move failed: ${result.error || 'Unknown error'}`);
|
|
75
|
+
return { success: false, error: result.error };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { run };
|