copilot-liku-cli 0.0.3 → 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 +702 -113
  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 +876 -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
@@ -44,7 +44,6 @@
44
44
  display: flex;
45
45
  justify-content: space-between;
46
46
  align-items: center;
47
- -webkit-app-region: drag;
48
47
  -webkit-user-select: none;
49
48
  user-select: none;
50
49
  border-bottom: 1px solid var(--border-color);
@@ -68,7 +67,6 @@
68
67
 
69
68
  #titlebar-controls {
70
69
  display: flex;
71
- -webkit-app-region: no-drag;
72
70
  height: 100%;
73
71
  }
74
72
 
@@ -206,7 +204,7 @@
206
204
  align-items: center;
207
205
  padding: 6px 10px;
208
206
  background: var(--bg-tertiary);
209
- border-bottom: 1px solid var(--border-color);
207
+ border-bottom: 1px solid var(--border-color);
210
208
  font-size: 11px;
211
209
  }
212
210
 
@@ -223,7 +221,7 @@
223
221
  #provider-select,
224
222
  #model-select {
225
223
  background: var(--bg-secondary);
226
- border: 1px solid var(--border-color);
224
+ border: 1px solid var(--border-color);
227
225
  border-radius: 4px;
228
226
  color: var(--text-primary);
229
227
  padding: 3px 6px;
@@ -92,6 +92,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
92
92
 
93
93
  // Verify using verifier agent
94
94
  agentVerify: (params) => ipcRenderer.invoke('agent-verify', params),
95
+
96
+ // Produce music using producer agent
97
+ agentProduce: (params) => ipcRenderer.invoke('agent-produce', params),
95
98
 
96
99
  // Get agent system status
97
100
  agentStatus: () => ipcRenderer.invoke('agent-status'),
@@ -108,5 +111,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
108
111
  },
109
112
 
110
113
  // ===== STATE =====
111
- getState: () => ipcRenderer.invoke('get-state')
114
+ getState: () => ipcRenderer.invoke('get-state'),
115
+
116
+ // ===== DEBUG / SMOKE (guarded in main by LIKU_ENABLE_DEBUG_IPC) =====
117
+ debugToggleChat: () => ipcRenderer.invoke('debug-toggle-chat'),
118
+ debugWindowState: () => ipcRenderer.invoke('debug-window-state')
112
119
  });
@@ -23,6 +23,7 @@ let state = {
23
23
  // Inspect mode state
24
24
  inspectMode: false,
25
25
  inspectRegions: [],
26
+ actionableRegions: [], // New: AI-detected regions for overlay
26
27
  hoveredRegion: null,
27
28
  selectedRegionId: null,
28
29
  // Live UI mirror state
@@ -74,8 +75,15 @@ function draw() {
74
75
  if (currentMode !== 'selection') return;
75
76
 
76
77
  // 1. Draw Coarse Grid (Always visible in selection)
77
- ctx.fillStyle = 'rgba(0, 122, 255, 0.85)';
78
- ctx.strokeStyle = 'rgba(255, 255, 255, 0.95)';
78
+ // ADAPTIVE GRID: If we have actionable regions, fade the grid significantly to reduce clutter
79
+ // This prioritizes the "Live UI" view as requested
80
+ const hasRegions = state.actionableRegions && state.actionableRegions.length > 0;
81
+ const gridOpacity = hasRegions ? 0.15 : 0.85;
82
+ const dotOpacity = hasRegions ? 0.15 : 0.85;
83
+ const labelOpacity = hasRegions ? 0.1 : 0.7;
84
+
85
+ ctx.fillStyle = `rgba(0, 122, 255, ${dotOpacity})`;
86
+ ctx.strokeStyle = `rgba(255, 255, 255, ${dotOpacity + 0.1})`;
79
87
  ctx.lineWidth = 2;
80
88
 
81
89
  // Font for labels
@@ -98,7 +106,7 @@ function draw() {
98
106
  // Draw Dot
99
107
  ctx.beginPath();
100
108
  ctx.arc(x, y, 6, 0, Math.PI * 2);
101
- ctx.fillStyle = 'rgba(0, 122, 255, 0.85)';
109
+ ctx.fillStyle = `rgba(0, 122, 255, ${dotOpacity})`;
102
110
  ctx.fill();
103
111
  ctx.stroke();
104
112
 
@@ -108,12 +116,12 @@ function draw() {
108
116
  const bgW = metrics.width + 10;
109
117
  const bgH = 16;
110
118
 
111
- // Label Background
112
- ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
119
+ // Label Background (Fainter if regions present)
120
+ ctx.fillStyle = `rgba(0, 0, 0, ${labelOpacity})`;
113
121
  ctx.fillRect(x - bgW / 2, y - 20 - bgH, bgW, bgH);
114
122
 
115
123
  // Label Text
116
- ctx.fillStyle = 'white';
124
+ ctx.fillStyle = `rgba(255, 255, 255, ${hasRegions ? 0.4 : 1.0})`;
117
125
  ctx.fillText(label, x, y - 24);
118
126
  }
119
127
  }
@@ -156,6 +164,11 @@ function draw() {
156
164
  if (zoomLevel < 2) {
157
165
  drawLocalFineGrid();
158
166
  }
167
+
168
+ // 4. Draw Actionable Regions (AI Vision)
169
+ if (state.actionableRegions && state.actionableRegions.length > 0) {
170
+ drawActionableRegions();
171
+ }
159
172
  }
160
173
 
161
174
  // Resize handler
@@ -279,6 +292,111 @@ function drawLocalFineGrid() {
279
292
  }
280
293
  }
281
294
 
295
+ function drawActionableRegions() {
296
+ const { actionableRegions, hoveredRegion } = state;
297
+ if (!actionableRegions) return;
298
+
299
+ // Style for regions
300
+ ctx.lineWidth = 1;
301
+ ctx.textBaseline = 'top';
302
+
303
+ // Smart De-Cluttering:
304
+ // If we have > 50 regions, we might want to skip drawing containers that fully enclose other regions?
305
+ // For now, relies on the improved visual style to handle density.
306
+
307
+ actionableRegions.forEach((region, index) => {
308
+ const { bounds, label, type, id } = region;
309
+ if (!bounds || bounds.width <= 0 || bounds.height <= 0) return;
310
+
311
+ const x = bounds.x;
312
+ const y = bounds.y;
313
+ const w = bounds.width;
314
+ const h = bounds.height;
315
+
316
+ // Check hover state (from mousemove or DOM interaction)
317
+ const isHovered = hoveredRegion && hoveredRegion.id === id;
318
+
319
+ // 1. Determine Style based on interactivity and state
320
+ const isPrimaryAction = ['Button', 'Hyperlink', 'MenuItem', 'TabItem', 'CheckBox'].includes(type);
321
+ const isInput = ['Edit', 'ComboBox', 'Document'].includes(type);
322
+
323
+ let strokeColor, fillColor, textColor, bgAlpha;
324
+
325
+ if (isHovered) {
326
+ strokeColor = 'rgba(255, 255, 0, 1.0)'; // Bright Yellow highlight
327
+ fillColor = 'rgba(255, 255, 0, 0.1)';
328
+ textColor = '#ffff00';
329
+ bgAlpha = 0.95;
330
+ ctx.lineWidth = 2;
331
+ } else if (isPrimaryAction) {
332
+ strokeColor = 'rgba(0, 255, 255, 0.7)'; // Cyan for clickable
333
+ fillColor = 'rgba(0, 255, 255, 0.02)';
334
+ textColor = '#00ffff';
335
+ bgAlpha = 0.8;
336
+ ctx.lineWidth = 1;
337
+ } else if (isInput) {
338
+ strokeColor = 'rgba(0, 255, 100, 0.6)'; // Greenish for inputs
339
+ fillColor = 'rgba(0, 255, 100, 0.02)';
340
+ textColor = '#00ff66';
341
+ bgAlpha = 0.7;
342
+ ctx.lineWidth = 1;
343
+ } else {
344
+ // Containers/Text - Subtle
345
+ strokeColor = 'rgba(100, 180, 255, 0.3)';
346
+ fillColor = 'transparent';
347
+ textColor = 'rgba(200, 220, 255, 0.8)';
348
+ bgAlpha = 0.6;
349
+ ctx.lineWidth = 0.5;
350
+ }
351
+
352
+ // 2. Draw Outline
353
+ ctx.strokeStyle = strokeColor;
354
+ ctx.fillStyle = fillColor;
355
+
356
+ // Use dashed lines for containers/large areas to reduce noise
357
+ if (!isHovered && w > 300 && h > 300) {
358
+ ctx.setLineDash([4, 4]);
359
+ } else {
360
+ ctx.setLineDash([]);
361
+ }
362
+
363
+ ctx.strokeRect(x, y, w, h);
364
+ ctx.fillRect(x, y, w, h);
365
+ ctx.setLineDash([]); // Reset
366
+
367
+ // 3. Draw Label
368
+ // CLARITY FIX: Only show Index by default. Show full Name only on Hover.
369
+ // This keeps the screen clean while allowing "Confident Communication" via ID reference.
370
+ const idx = index + 1;
371
+ let labelText = `#${idx}`;
372
+
373
+ if (isHovered && label) {
374
+ // Show full info on hover
375
+ labelText = `${label} (${type})`;
376
+ ctx.font = 'bold 12px "SF Mono", "Monaco", monospace'; // Larger font on hover
377
+ } else {
378
+ ctx.font = 'bold 10px "SF Mono", "Monaco", monospace';
379
+ }
380
+
381
+ const textMetrics = ctx.measureText(labelText);
382
+ const textW = textMetrics.width + 6;
383
+ const textH = isHovered ? 16 : 12;
384
+
385
+ // Label Position: Interior Top-Left
386
+ // Ensure it doesn't go off-screen
387
+ let lx = x;
388
+ let ly = y;
389
+
390
+ // Label Background
391
+ ctx.fillStyle = `rgba(0, 20, 20, ${bgAlpha})`;
392
+ ctx.fillRect(lx, ly, textW, textH);
393
+
394
+ // Label Text
395
+ ctx.fillStyle = textColor;
396
+ ctx.fillText(labelText, lx + 3, ly + 1);
397
+ });
398
+ }
399
+
282
400
  // ===== INPUT HANDLING =====
283
401
 
284
402
  // Visual Feedback Helper
@@ -306,6 +424,26 @@ document.addEventListener('mousemove', (e) => {
306
424
 
307
425
  if (state.currentMode === 'selection') {
308
426
  requestDraw();
427
+
428
+ // 0. HIT TESTING for Actionable Regions
429
+ const { actionableRegions } = state;
430
+ if (actionableRegions && actionableRegions.length > 0) {
431
+ const hit = actionableRegions
432
+ .filter(r =>
433
+ e.clientX >= r.bounds.x &&
434
+ e.clientX <= (r.bounds.x + r.bounds.width) &&
435
+ e.clientY >= r.bounds.y &&
436
+ e.clientY <= (r.bounds.y + r.bounds.height)
437
+ )
438
+ // Sort by area ascending so we pick the smallest/most specific one
439
+ .sort((a,b) => (a.bounds.width * a.bounds.height) - (b.bounds.width * b.bounds.height))[0];
440
+
441
+ if (state.hoveredRegion !== (hit || null)) {
442
+ state.hoveredRegion = hit || null;
443
+ requestDraw();
444
+ }
445
+ }
446
+
309
447
  // Virtual Interaction Logic
310
448
  // Find nearest grid point
311
449
  const spacing = state.zoomLevel >= 2 ? FINE_SPACING : COARSE_SPACING;
@@ -475,7 +613,9 @@ if (window.electronAPI) {
475
613
  }
476
614
 
477
615
  function handleCommand(data) {
478
- console.log('Command:', data.action);
616
+ if (data.action !== 'update-inspect-regions') {
617
+ console.log('Command:', data.action);
618
+ }
479
619
  switch (data.action) {
480
620
  case 'toggle-fine':
481
621
  state.zoomLevel = state.zoomLevel >= 2 ? 1 : 2;
@@ -524,7 +664,16 @@ function handleCommand(data) {
524
664
  break;
525
665
  case 'update-inspect-regions':
526
666
  if (data.regions) {
527
- updateInspectRegions(data.regions);
667
+ // Reuse inspection capability for AI vision visualization
668
+ // Check if we should use DOM (legacy/inspect) or Canvas (performance)
669
+ // For high-frequency updates, we'll store in actionableRegions and use canvas
670
+ state.actionableRegions = data.regions;
671
+ requestDraw();
672
+
673
+ // If actual inspect mode is active, also update DOM for interaction
674
+ if (state.inspectMode) {
675
+ updateInspectRegions(data.regions);
676
+ }
528
677
  }
529
678
  break;
530
679
  case 'clear-inspect-regions':
@@ -67,6 +67,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
67
67
  // Get current state
68
68
  getState: () => ipcRenderer.invoke('get-state'),
69
69
 
70
+ // Debug / smoke controls (guarded in main by LIKU_ENABLE_DEBUG_IPC)
71
+ debugToggleChat: () => ipcRenderer.invoke('debug-toggle-chat'),
72
+ debugWindowState: () => ipcRenderer.invoke('debug-window-state'),
73
+
70
74
  // Grid math helpers (inlined above)
71
75
  getGridConstants: () => gridConstants,
72
76
  labelToScreenCoordinates: (label) => labelToScreenCoordinates(label),
@@ -3,6 +3,22 @@
3
3
  * Shared type definitions for inspect regions, window context, and action traces
4
4
  */
5
5
 
6
+ /**
7
+ * Visual Frame Data Contract
8
+ * Standardized schema for any captured visual context (full screen, ROI, window, element)
9
+ * @typedef {Object} VisualFrame
10
+ * @property {string} dataURL - Base64 data URL of the image
11
+ * @property {number} width - Image width in pixels
12
+ * @property {number} height - Image height in pixels
13
+ * @property {number} timestamp - Capture timestamp (ms)
14
+ * @property {number} [originX] - X offset of the captured region on screen (0 for full screen)
15
+ * @property {number} [originY] - Y offset of the captured region on screen (0 for full screen)
16
+ * @property {string} coordinateSpace - Always 'screen-physical' for UIA/input compatibility
17
+ * @property {string} scope - 'screen' | 'region' | 'window' | 'element'
18
+ * @property {string} [sourceId] - Display/window source identifier
19
+ * @property {string} [sourceName] - Human-readable source name
20
+ */
21
+
6
22
  /**
7
23
  * Inspect Region Data Contract
8
24
  * Represents an actionable region on screen detected through various sources
@@ -15,6 +31,9 @@
15
31
  * @property {number} confidence - Detection confidence 0-1
16
32
  * @property {string} source - Detection source (accessibility, ocr, heuristic)
17
33
  * @property {number} timestamp - When this region was detected
34
+ * @property {Object} [clickPoint] - Preferred click point {x, y} from UIA TryGetClickablePoint
35
+ * @property {number[]|null} [runtimeId] - UIA RuntimeId for stable session-scoped element identity
36
+ * @property {string} coordinateSpace - Coordinate space (default 'screen-physical')
18
37
  */
19
38
 
20
39
  /**
@@ -42,6 +61,26 @@
42
61
  * @property {string} outcome - Result (success, failed, pending)
43
62
  */
44
63
 
64
+ /**
65
+ * Create a VisualFrame from capture data
66
+ * @param {Object} params - Capture parameters
67
+ * @returns {VisualFrame}
68
+ */
69
+ function createVisualFrame(params) {
70
+ return {
71
+ dataURL: params.dataURL || '',
72
+ width: params.width || 0,
73
+ height: params.height || 0,
74
+ timestamp: params.timestamp || Date.now(),
75
+ originX: params.originX ?? params.x ?? 0,
76
+ originY: params.originY ?? params.y ?? 0,
77
+ coordinateSpace: 'screen-physical',
78
+ scope: params.scope || params.type || 'screen',
79
+ sourceId: params.sourceId || null,
80
+ sourceName: params.sourceName || null
81
+ };
82
+ }
83
+
45
84
  /**
46
85
  * Create a new inspect region object
47
86
  * @param {Object} params - Region parameters
@@ -61,7 +100,10 @@ function createInspectRegion(params) {
61
100
  role: params.role || params.controlType || 'unknown',
62
101
  confidence: typeof params.confidence === 'number' ? params.confidence : 0.5,
63
102
  source: params.source || 'unknown',
64
- timestamp: params.timestamp || Date.now()
103
+ timestamp: params.timestamp || Date.now(),
104
+ clickPoint: params.clickPoint || null,
105
+ runtimeId: params.runtimeId || null,
106
+ coordinateSpace: params.coordinateSpace || 'screen-physical'
65
107
  };
66
108
  }
67
109
 
@@ -203,21 +245,54 @@ function findRegionAtPoint(x, y, regions) {
203
245
  * @returns {Object} AI-friendly format
204
246
  */
205
247
  function formatRegionForAI(region) {
248
+ const center = region.clickPoint
249
+ ? { x: region.clickPoint.x, y: region.clickPoint.y }
250
+ : {
251
+ x: Math.round(region.bounds.x + region.bounds.width / 2),
252
+ y: Math.round(region.bounds.y + region.bounds.height / 2)
253
+ };
206
254
  return {
207
255
  id: region.id,
208
256
  label: region.label,
209
257
  text: region.text,
210
258
  role: region.role,
211
259
  confidence: region.confidence,
212
- center: {
213
- x: Math.round(region.bounds.x + region.bounds.width / 2),
214
- y: Math.round(region.bounds.y + region.bounds.height / 2)
215
- },
260
+ center,
216
261
  bounds: region.bounds
217
262
  };
218
263
  }
219
264
 
265
+ /**
266
+ * Resolve a region target from the regions array
267
+ * Supports targetRegionId (stable) or targetRegionIndex (display order)
268
+ * @param {Object} target - { targetRegionId?, targetRegionIndex? }
269
+ * @param {InspectRegion[]} regions - Current regions array
270
+ * @returns {{ region: InspectRegion, clickX: number, clickY: number } | null}
271
+ */
272
+ function resolveRegionTarget(target, regions) {
273
+ if (!target || !regions || regions.length === 0) return null;
274
+
275
+ let region = null;
276
+ if (target.targetRegionId) {
277
+ region = regions.find(r => r.id === target.targetRegionId);
278
+ } else if (typeof target.targetRegionIndex === 'number') {
279
+ region = regions[target.targetRegionIndex];
280
+ }
281
+ if (!region) return null;
282
+
283
+ // Prefer clickPoint from UIA, fall back to bounds center
284
+ const clickX = region.clickPoint
285
+ ? region.clickPoint.x
286
+ : Math.round(region.bounds.x + region.bounds.width / 2);
287
+ const clickY = region.clickPoint
288
+ ? region.clickPoint.y
289
+ : Math.round(region.bounds.y + region.bounds.height / 2);
290
+
291
+ return { region, clickX, clickY };
292
+ }
293
+
220
294
  module.exports = {
295
+ createVisualFrame,
221
296
  createInspectRegion,
222
297
  createWindowContext,
223
298
  createActionTrace,
@@ -226,5 +301,6 @@ module.exports = {
226
301
  isPointInRegion,
227
302
  findClosestRegion,
228
303
  findRegionAtPoint,
229
- formatRegionForAI
304
+ formatRegionForAI,
305
+ resolveRegionTarget
230
306
  };