copilot-liku-cli 0.0.4 → 0.0.8

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 (46) hide show
  1. package/QUICKSTART.md +24 -0
  2. package/README.md +85 -33
  3. package/package.json +23 -14
  4. package/scripts/postinstall.js +63 -0
  5. package/src/cli/commands/window.js +66 -0
  6. package/src/main/agents/base-agent.js +15 -7
  7. package/src/main/agents/builder.js +211 -0
  8. package/src/main/agents/index.js +7 -4
  9. package/src/main/agents/orchestrator.js +13 -0
  10. package/src/main/agents/producer.js +891 -0
  11. package/src/main/agents/researcher.js +78 -0
  12. package/src/main/agents/state-manager.js +134 -2
  13. package/src/main/agents/verifier.js +201 -0
  14. package/src/main/ai-service.js +349 -35
  15. package/src/main/index.js +680 -110
  16. package/src/main/inspect-service.js +24 -1
  17. package/src/main/python-bridge.js +395 -0
  18. package/src/main/system-automation.js +849 -131
  19. package/src/main/ui-automation/core/ui-provider.js +99 -0
  20. package/src/main/ui-automation/core/uia-host.js +214 -0
  21. package/src/main/ui-automation/index.js +30 -0
  22. package/src/main/ui-automation/interactions/element-click.js +6 -6
  23. package/src/main/ui-automation/interactions/high-level.js +28 -6
  24. package/src/main/ui-automation/interactions/index.js +21 -0
  25. package/src/main/ui-automation/interactions/pattern-actions.js +236 -0
  26. package/src/main/ui-automation/window/index.js +6 -0
  27. package/src/main/ui-automation/window/manager.js +173 -26
  28. package/src/main/ui-watcher.js +401 -58
  29. package/src/main/visual-awareness.js +18 -1
  30. package/src/native/windows-uia/Program.cs +89 -0
  31. package/src/native/windows-uia/build.ps1 +24 -0
  32. package/src/native/windows-uia-dotnet/Program.cs +920 -0
  33. package/src/native/windows-uia-dotnet/WindowsUIA.csproj +11 -0
  34. package/src/native/windows-uia-dotnet/build.ps1 +24 -0
  35. package/src/renderer/chat/chat.js +915 -671
  36. package/src/renderer/chat/index.html +2 -4
  37. package/src/renderer/chat/preload.js +8 -1
  38. package/src/renderer/overlay/overlay.js +157 -8
  39. package/src/renderer/overlay/preload.js +4 -0
  40. package/src/shared/inspect-types.js +82 -6
  41. package/ARCHITECTURE.md +0 -411
  42. package/CONFIGURATION.md +0 -302
  43. package/CONTRIBUTING.md +0 -225
  44. package/ELECTRON_README.md +0 -121
  45. package/PROJECT_STATUS.md +0 -229
  46. package/TESTING.md +0 -274
@@ -0,0 +1,99 @@
1
+ const { spawn } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * @typedef {Object} Bounds
7
+ * @property {number} x
8
+ * @property {number} y
9
+ * @property {number} width
10
+ * @property {number} height
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} UIElement
15
+ * @property {string} id
16
+ * @property {string} name
17
+ * @property {string} role
18
+ * @property {Bounds} bounds
19
+ * @property {boolean} isClickable
20
+ * @property {boolean} isFocusable
21
+ * @property {UIElement[]} children
22
+ */
23
+
24
+ class UIProvider {
25
+ constructor() {
26
+ const binDir = path.join(__dirname, '..', '..', '..', '..', 'bin');
27
+ const candidates = [
28
+ path.join(binDir, 'WindowsUIA.exe'),
29
+ path.join(binDir, 'windows-uia.exe')
30
+ ];
31
+ this.binaryPath = candidates.find(filePath => fs.existsSync(filePath)) || candidates[0];
32
+ }
33
+
34
+ /**
35
+ * Fetches the UI tree from the native binary.
36
+ * @returns {Promise<UIElement>}
37
+ */
38
+ async getUITree() {
39
+ return new Promise((resolve, reject) => {
40
+ if (!fs.existsSync(this.binaryPath)) {
41
+ return reject(new Error('UIAutomation binary not found. Build it with: powershell -ExecutionPolicy Bypass -File src/native/windows-uia/build.ps1'));
42
+ }
43
+
44
+ const child = spawn(this.binaryPath);
45
+ let output = '';
46
+ let errorOutput = '';
47
+
48
+ child.stdout.on('data', (data) => {
49
+ output += data.toString();
50
+ });
51
+
52
+ child.stderr.on('data', (data) => {
53
+ errorOutput += data.toString();
54
+ });
55
+
56
+ child.on('close', (code) => {
57
+ if (code !== 0) {
58
+ return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
59
+ }
60
+
61
+ try {
62
+ const parsed = JSON.parse(output);
63
+ const uiTree = this.parseNode(parsed);
64
+ resolve(uiTree);
65
+ } catch (err) {
66
+ reject(new Error(`Failed to parse JSON output: ${err.message}`));
67
+ }
68
+ });
69
+
70
+ child.on('error', (err) => {
71
+ reject(new Error(`Failed to start subprocess: ${err.message}`));
72
+ });
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Parses the OS-specific JSON node into a unified UIElement.
78
+ * @param {Object} node
79
+ * @returns {UIElement}
80
+ */
81
+ parseNode(node) {
82
+ return {
83
+ id: node.id || '',
84
+ name: node.name || '',
85
+ role: node.role || '',
86
+ bounds: {
87
+ x: node.bounds?.x || 0,
88
+ y: node.bounds?.y || 0,
89
+ width: node.bounds?.width || 0,
90
+ height: node.bounds?.height || 0
91
+ },
92
+ isClickable: !!node.isClickable,
93
+ isFocusable: !!node.isFocusable,
94
+ children: (node.children || []).map(child => this.parseNode(child))
95
+ };
96
+ }
97
+ }
98
+
99
+ module.exports = { UIProvider };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Persistent .NET UIA host — spawns WindowsUIA.exe once, communicates
3
+ * via newline-delimited JSON (JSONL) over stdin/stdout.
4
+ *
5
+ * Protocol:
6
+ * stdin → {"cmd":"elementFromPoint","x":500,"y":300}
7
+ * stdout ← {"ok":true,"cmd":"elementFromPoint","element":{…}}
8
+ *
9
+ * Supported commands: getTree, elementFromPoint, exit.
10
+ */
11
+
12
+ const { spawn } = require('child_process');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { EventEmitter } = require('events');
16
+
17
+ const STARTUP_TIMEOUT_MS = 5000;
18
+ const REQUEST_TIMEOUT_MS = 8000;
19
+
20
+ class UIAHost extends EventEmitter {
21
+ constructor() {
22
+ super();
23
+ const binDir = path.join(__dirname, '..', '..', '..', '..', 'bin');
24
+ this._binaryPath = path.join(binDir, 'WindowsUIA.exe');
25
+ this._proc = null;
26
+ this._buffer = '';
27
+ this._pending = null; // { resolve, reject, timer }
28
+ this._alive = false;
29
+ }
30
+
31
+ /** Ensure the host process is running. Idempotent. */
32
+ async start() {
33
+ if (this._alive && this._proc && !this._proc.killed) return;
34
+
35
+ if (!fs.existsSync(this._binaryPath)) {
36
+ throw new Error(
37
+ `UIA host binary not found at ${this._binaryPath}. ` +
38
+ 'Build with: powershell -ExecutionPolicy Bypass -File src/native/windows-uia-dotnet/build.ps1'
39
+ );
40
+ }
41
+
42
+ this._proc = spawn(this._binaryPath, [], {
43
+ stdio: ['pipe', 'pipe', 'pipe'],
44
+ windowsHide: true
45
+ });
46
+
47
+ this._buffer = '';
48
+ this._alive = true;
49
+
50
+ this._proc.stdout.on('data', (chunk) => this._onData(chunk));
51
+ this._proc.stderr.on('data', (chunk) => {
52
+ this.emit('stderr', chunk.toString());
53
+ });
54
+ this._proc.on('exit', (code) => {
55
+ this._alive = false;
56
+ this._rejectPending(new Error(`UIA host exited with code ${code}`));
57
+ this.emit('exit', code);
58
+ });
59
+ this._proc.on('error', (err) => {
60
+ this._alive = false;
61
+ this._rejectPending(err);
62
+ this.emit('error', err);
63
+ });
64
+ }
65
+
66
+ /** Send a command and await the JSON response. */
67
+ async send(cmd) {
68
+ await this.start();
69
+
70
+ if (this._pending) {
71
+ throw new Error('UIAHost: concurrent request not supported (previous call still pending)');
72
+ }
73
+
74
+ return new Promise((resolve, reject) => {
75
+ const timer = setTimeout(() => {
76
+ this._pending = null;
77
+ reject(new Error(`UIAHost: command "${cmd.cmd}" timed out after ${REQUEST_TIMEOUT_MS}ms`));
78
+ }, REQUEST_TIMEOUT_MS);
79
+
80
+ this._pending = { resolve, reject, timer };
81
+
82
+ const line = JSON.stringify(cmd) + '\n';
83
+ this._proc.stdin.write(line);
84
+ });
85
+ }
86
+
87
+ /** Convenience: elementFromPoint(x, y) → rich element payload */
88
+ async elementFromPoint(x, y) {
89
+ const resp = await this.send({ cmd: 'elementFromPoint', x, y });
90
+ if (!resp.ok) throw new Error(resp.error || 'elementFromPoint failed');
91
+ return resp.element;
92
+ }
93
+
94
+ /** Convenience: getTree() → foreground window tree */
95
+ async getTree() {
96
+ const resp = await this.send({ cmd: 'getTree' });
97
+ if (!resp.ok) throw new Error(resp.error || 'getTree failed');
98
+ return resp.tree;
99
+ }
100
+
101
+ /** Set value on element at (x,y) using ValuePattern. */
102
+ async setValue(x, y, value) {
103
+ const resp = await this.send({ cmd: 'setValue', x, y, value });
104
+ if (!resp.ok) throw new Error(resp.error || 'setValue failed');
105
+ return resp;
106
+ }
107
+
108
+ /** Scroll element at (x,y) using ScrollPattern. direction: up|down|left|right. amount: percent (0-100) or -1 for small increment. */
109
+ async scroll(x, y, direction = 'down', amount = -1) {
110
+ const resp = await this.send({ cmd: 'scroll', x, y, direction, amount });
111
+ if (!resp.ok) throw new Error(resp.error || 'scroll failed');
112
+ return resp;
113
+ }
114
+
115
+ /** Expand/collapse element at (x,y). action: expand|collapse|toggle. */
116
+ async expandCollapse(x, y, action = 'toggle') {
117
+ const resp = await this.send({ cmd: 'expandCollapse', x, y, action });
118
+ if (!resp.ok) throw new Error(resp.error || 'expandCollapse failed');
119
+ return resp;
120
+ }
121
+
122
+ /** Get text from element at (x,y) using TextPattern → ValuePattern → Name fallback. */
123
+ async getText(x, y) {
124
+ const resp = await this.send({ cmd: 'getText', x, y });
125
+ if (!resp.ok) throw new Error(resp.error || 'getText failed');
126
+ return resp;
127
+ }
128
+
129
+ /** Subscribe to UIA events (focus, structure, property). Returns initial snapshot. */
130
+ async subscribeEvents() {
131
+ const resp = await this.send({ cmd: 'subscribeEvents' });
132
+ if (!resp.ok) throw new Error(resp.error || 'subscribeEvents failed');
133
+ return resp;
134
+ }
135
+
136
+ /** Unsubscribe from all UIA events. */
137
+ async unsubscribeEvents() {
138
+ const resp = await this.send({ cmd: 'unsubscribeEvents' });
139
+ if (!resp.ok) throw new Error(resp.error || 'unsubscribeEvents failed');
140
+ return resp;
141
+ }
142
+
143
+ /** Gracefully shut down the host process. */
144
+ async stop() {
145
+ if (!this._alive || !this._proc) return;
146
+ try {
147
+ await this.send({ cmd: 'exit' });
148
+ } catch { /* ignore */ }
149
+ this._alive = false;
150
+ if (this._proc && !this._proc.killed) {
151
+ this._proc.kill();
152
+ }
153
+ this._proc = null;
154
+ }
155
+
156
+ get isAlive() {
157
+ return this._alive;
158
+ }
159
+
160
+ // ── internal ─────────────────────────────────────────────────────────
161
+
162
+ _onData(chunk) {
163
+ this._buffer += chunk.toString();
164
+ let nl;
165
+ while ((nl = this._buffer.indexOf('\n')) !== -1) {
166
+ const line = this._buffer.slice(0, nl).trim();
167
+ this._buffer = this._buffer.slice(nl + 1);
168
+ if (!line) continue;
169
+ try {
170
+ const json = JSON.parse(line);
171
+ // Phase 4: route unsolicited event messages before pending resolution
172
+ if (json.type === 'event') {
173
+ this.emit('uia-event', json);
174
+ continue;
175
+ }
176
+ this._resolvePending(json);
177
+ } catch (e) {
178
+ this.emit('parseError', line, e);
179
+ }
180
+ }
181
+ }
182
+
183
+ _resolvePending(json) {
184
+ if (!this._pending) return;
185
+ const { resolve, timer } = this._pending;
186
+ clearTimeout(timer);
187
+ this._pending = null;
188
+ resolve(json);
189
+ }
190
+
191
+ _rejectPending(err) {
192
+ if (!this._pending) return;
193
+ const { reject, timer } = this._pending;
194
+ clearTimeout(timer);
195
+ this._pending = null;
196
+ reject(err);
197
+ }
198
+ }
199
+
200
+ // Singleton for shared use
201
+ let _shared = null;
202
+
203
+ /**
204
+ * Get or create the shared UIAHost instance.
205
+ * @returns {UIAHost}
206
+ */
207
+ function getSharedUIAHost() {
208
+ if (!_shared) {
209
+ _shared = new UIAHost();
210
+ }
211
+ return _shared;
212
+ }
213
+
214
+ module.exports = { UIAHost, getSharedUIAHost };
@@ -28,6 +28,8 @@ const { CONFIG, CONTROL_TYPES } = require('./config');
28
28
 
29
29
  // Core utilities
30
30
  const { sleep, debug, log, executePowerShellScript } = require('./core');
31
+ const { UIProvider } = require('./core/ui-provider');
32
+ const { UIAHost, getSharedUIAHost } = require('./core/uia-host');
31
33
 
32
34
  // Element operations
33
35
  const {
@@ -64,7 +66,10 @@ const {
64
66
  const {
65
67
  getActiveWindow,
66
68
  findWindows,
69
+ resolveWindowTarget,
67
70
  focusWindow,
71
+ bringWindowToFront,
72
+ sendWindowToBack,
68
73
  minimizeWindow,
69
74
  maximizeWindow,
70
75
  restoreWindow,
@@ -87,6 +92,15 @@ const {
87
92
  waitAndClick,
88
93
  clickAndWaitFor,
89
94
  selectFromDropdown,
95
+ // Pattern-based interactions (Phase 3)
96
+ normalizePatternName,
97
+ hasPattern,
98
+ setElementValue,
99
+ scrollElement,
100
+ expandElement,
101
+ collapseElement,
102
+ toggleExpandCollapse,
103
+ getElementText,
90
104
  } = require('./interactions');
91
105
 
92
106
  // Screenshot
@@ -106,6 +120,9 @@ module.exports = {
106
120
  debug,
107
121
  log,
108
122
  executePowerShellScript,
123
+ UIProvider,
124
+ UIAHost,
125
+ getSharedUIAHost,
109
126
 
110
127
  // Element operations
111
128
  findElements,
@@ -135,7 +152,10 @@ module.exports = {
135
152
  // Window operations
136
153
  getActiveWindow,
137
154
  findWindows,
155
+ resolveWindowTarget,
138
156
  focusWindow,
157
+ bringWindowToFront,
158
+ sendWindowToBack,
139
159
  minimizeWindow,
140
160
  maximizeWindow,
141
161
  restoreWindow,
@@ -157,6 +177,16 @@ module.exports = {
157
177
  clickAndWaitFor,
158
178
  selectFromDropdown,
159
179
 
180
+ // Pattern-based interactions (Phase 3)
181
+ normalizePatternName,
182
+ hasPattern,
183
+ setElementValue,
184
+ scrollElement,
185
+ expandElement,
186
+ collapseElement,
187
+ toggleExpandCollapse,
188
+ getElementText,
189
+
160
190
  // Screenshot
161
191
  screenshot,
162
192
  screenshotActiveWindow,
@@ -50,10 +50,10 @@ async function click(criteria, options = {}) {
50
50
  return { success: false, element: null, error: findResult?.error || 'Element not found' };
51
51
  }
52
52
 
53
- // Calculate center point
53
+ // Calculate click point — prefer UIA clickPoint over bounds-center
54
54
  const bounds = element.bounds;
55
- const x = bounds.x + bounds.width / 2;
56
- const y = bounds.y + bounds.height / 2;
55
+ const x = element.clickPoint?.x ?? (bounds.x + bounds.width / 2);
56
+ const y = element.clickPoint?.y ?? (bounds.y + bounds.height / 2);
57
57
 
58
58
  // Focus window if needed
59
59
  if (focusWindow && element.windowHwnd) {
@@ -132,11 +132,11 @@ async function clickElement(element, options = {}) {
132
132
  }
133
133
 
134
134
  const bounds = element.bounds;
135
- const centerX = bounds.x + bounds.width / 2;
136
- const centerY = bounds.y + bounds.height / 2;
135
+ const centerX = element.clickPoint?.x ?? (bounds.x + bounds.width / 2);
136
+ const centerY = element.clickPoint?.y ?? (bounds.y + bounds.height / 2);
137
137
 
138
138
  // Strategy 1: Try Invoke pattern for buttons
139
- if (useInvoke && element.patterns?.includes('InvokePatternIdentifiers.Pattern')) {
139
+ if (useInvoke && (element.patterns?.includes('InvokePatternIdentifiers.Pattern') || element.patterns?.includes('Invoke'))) {
140
140
  log(`Attempting Invoke pattern for "${element.name}"`);
141
141
  const invokeResult = await invokeElement(element);
142
142
  if (invokeResult.success) {
@@ -7,6 +7,7 @@
7
7
 
8
8
  const { findElement, findElements, waitForElement } = require('../elements');
9
9
  const { click, clickByText } = require('./element-click');
10
+ const { setElementValue, expandElement } = require('./pattern-actions');
10
11
  const { typeText, sendKeys } = require('../keyboard');
11
12
  const { focusWindow, findWindows } = require('../window');
12
13
  const { log, sleep } = require('../core/helpers');
@@ -21,9 +22,18 @@ const { log, sleep } = require('../core/helpers');
21
22
  * @returns {Promise<{success: boolean}>}
22
23
  */
23
24
  async function fillField(criteria, text, options = {}) {
24
- const { clear = true } = options;
25
+ const { clear = true, preferPattern = true } = options;
26
+
27
+ // Strategy 1: Try ValuePattern (fast, no focus/click needed)
28
+ if (preferPattern) {
29
+ const patternResult = await setElementValue(criteria, text);
30
+ if (patternResult.success) {
31
+ log(`fillField: ValuePattern succeeded for "${text.slice(0, 30)}"`);
32
+ return { success: true, method: 'ValuePattern' };
33
+ }
34
+ }
25
35
 
26
- // Click the field
36
+ // Strategy 2: Click + type (fallback)
27
37
  const clickResult = await click(criteria);
28
38
  if (!clickResult.success) {
29
39
  return { success: false };
@@ -39,7 +49,7 @@ async function fillField(criteria, text, options = {}) {
39
49
 
40
50
  // Type text
41
51
  const typeResult = await typeText(text);
42
- return { success: typeResult.success };
52
+ return { success: typeResult.success, method: 'sendKeys' };
43
53
  }
44
54
 
45
55
  /**
@@ -52,9 +62,21 @@ async function fillField(criteria, text, options = {}) {
52
62
  * @returns {Promise<{success: boolean}>}
53
63
  */
54
64
  async function selectDropdownItem(dropdownCriteria, itemCriteria, options = {}) {
55
- const { itemWait = 1000 } = options;
65
+ const { itemWait = 1000, preferPattern = true } = options;
66
+
67
+ // Strategy 1: Try ExpandCollapsePattern to open
68
+ if (preferPattern) {
69
+ const expandResult = await expandElement(dropdownCriteria);
70
+ if (expandResult.success) {
71
+ log(`selectDropdownItem: ExpandCollapsePattern expanded (${expandResult.stateBefore} → ${expandResult.stateAfter})`);
72
+ await sleep(itemWait);
73
+ const itemQuery = typeof itemCriteria === 'string' ? { text: itemCriteria } : itemCriteria;
74
+ const itemResult = await click(itemQuery);
75
+ return { success: itemResult.success, method: 'ExpandCollapsePattern' };
76
+ }
77
+ }
56
78
 
57
- // Click dropdown to open
79
+ // Strategy 2: Click to open (fallback)
58
80
  const openResult = await click(dropdownCriteria);
59
81
  if (!openResult.success) {
60
82
  log('selectDropdownItem: Failed to open dropdown', 'warn');
@@ -69,7 +91,7 @@ async function selectDropdownItem(dropdownCriteria, itemCriteria, options = {})
69
91
  : itemCriteria;
70
92
 
71
93
  const itemResult = await click(itemQuery);
72
- return { success: itemResult.success };
94
+ return { success: itemResult.success, method: 'click' };
73
95
  }
74
96
 
75
97
  /**
@@ -25,6 +25,17 @@ const {
25
25
  selectFromDropdown,
26
26
  } = require('./high-level');
27
27
 
28
+ const {
29
+ normalizePatternName,
30
+ hasPattern,
31
+ setElementValue,
32
+ scrollElement,
33
+ expandElement,
34
+ collapseElement,
35
+ toggleExpandCollapse,
36
+ getElementText,
37
+ } = require('./pattern-actions');
38
+
28
39
  module.exports = {
29
40
  // Element clicks
30
41
  click,
@@ -44,4 +55,14 @@ module.exports = {
44
55
  waitAndClick,
45
56
  clickAndWaitFor,
46
57
  selectFromDropdown,
58
+
59
+ // Pattern-based interactions (Phase 3)
60
+ normalizePatternName,
61
+ hasPattern,
62
+ setElementValue,
63
+ scrollElement,
64
+ expandElement,
65
+ collapseElement,
66
+ toggleExpandCollapse,
67
+ getElementText,
47
68
  };