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,467 @@
1
+ /**
2
+ * Inspect Service Module
3
+ * Manages inspect overlay state, region detection, and AI context integration
4
+ */
5
+
6
+ const { screen } = require('electron');
7
+ const visualAwareness = require('./visual-awareness');
8
+ const inspectTypes = require('../shared/inspect-types');
9
+
10
+ // ===== STATE =====
11
+ let inspectMode = false;
12
+ let currentRegions = [];
13
+ let windowContext = null;
14
+ let actionTraces = [];
15
+ let selectedRegionId = null;
16
+
17
+ const MAX_ACTION_TRACES = 100;
18
+
19
+ // ===== INSPECT MODE MANAGEMENT =====
20
+
21
+ /**
22
+ * Enable or disable inspect mode
23
+ * @param {boolean} enabled - Whether inspect mode should be enabled
24
+ */
25
+ function setInspectMode(enabled) {
26
+ inspectMode = enabled;
27
+ if (!enabled) {
28
+ // Clear all state when disabling inspect mode
29
+ clearRegions();
30
+ }
31
+ return inspectMode;
32
+ }
33
+
34
+ /**
35
+ * Check if inspect mode is active
36
+ * @returns {boolean}
37
+ */
38
+ function isInspectModeActive() {
39
+ return inspectMode;
40
+ }
41
+
42
+ // ===== REGION MANAGEMENT =====
43
+
44
+ /**
45
+ * Update inspect regions from various sources
46
+ * @param {Object[]} rawRegions - Raw region data from detection
47
+ * @param {string} source - Source of detection (accessibility, ocr, heuristic)
48
+ * @returns {Object[]} Processed regions
49
+ */
50
+ function updateRegions(rawRegions, source = 'unknown') {
51
+ if (!Array.isArray(rawRegions)) return [...currentRegions];
52
+
53
+ // Convert raw regions to inspect regions
54
+ // Note: Accessibility API coordinates are already in screen space,
55
+ // so no DPI scaling is needed here. Scale factor is stored in
56
+ // windowContext for AI reference.
57
+ const newRegions = rawRegions
58
+ .filter(r => r && (r.bounds || (r.x !== undefined && r.y !== undefined)))
59
+ .map(r => {
60
+ const bounds = r.bounds || { x: r.x, y: r.y, width: r.width || 0, height: r.height || 0 };
61
+
62
+ return inspectTypes.createInspectRegion({
63
+ ...r,
64
+ bounds: {
65
+ x: Math.round(bounds.x || bounds.X || 0),
66
+ y: Math.round(bounds.y || bounds.Y || 0),
67
+ width: Math.round(bounds.width || bounds.Width || 0),
68
+ height: Math.round(bounds.height || bounds.Height || 0)
69
+ },
70
+ source,
71
+ confidence: r.confidence || calculateConfidence(r, source)
72
+ });
73
+ })
74
+ .filter(r => r.bounds.width > 0 && r.bounds.height > 0);
75
+
76
+ // Merge with existing regions (prefer newer, dedupe by overlap)
77
+ currentRegions = mergeRegions(currentRegions, newRegions);
78
+
79
+ return [...currentRegions];
80
+ }
81
+
82
+ /**
83
+ * Clear all regions
84
+ */
85
+ function clearRegions() {
86
+ currentRegions = [];
87
+ selectedRegionId = null;
88
+ }
89
+
90
+ /**
91
+ * Get current inspect regions
92
+ * @returns {Object[]} Copy of current regions array
93
+ */
94
+ function getRegions() {
95
+ // Return a shallow copy to prevent external mutations
96
+ return [...currentRegions];
97
+ }
98
+
99
+ /**
100
+ * Select a region by ID
101
+ * @param {string} regionId - ID of region to select
102
+ * @returns {Object|null} Selected region or null
103
+ */
104
+ function selectRegion(regionId) {
105
+ const region = currentRegions.find(r => r.id === regionId);
106
+ if (region) {
107
+ selectedRegionId = regionId;
108
+ }
109
+ return region;
110
+ }
111
+
112
+ /**
113
+ * Get currently selected region
114
+ * @returns {Object|null}
115
+ */
116
+ function getSelectedRegion() {
117
+ return currentRegions.find(r => r.id === selectedRegionId) || null;
118
+ }
119
+
120
+ /**
121
+ * Find region at a specific point
122
+ * @param {number} x - X coordinate
123
+ * @param {number} y - Y coordinate
124
+ * @returns {Object|null}
125
+ */
126
+ function findRegionAt(x, y) {
127
+ return inspectTypes.findRegionAtPoint(x, y, currentRegions);
128
+ }
129
+
130
+ // ===== WINDOW CONTEXT =====
131
+
132
+ /**
133
+ * Update window context from active window info
134
+ * @param {Object} windowInfo - Window information
135
+ */
136
+ async function updateWindowContext(windowInfo = null) {
137
+ if (!windowInfo) {
138
+ // Fetch from visual awareness
139
+ windowInfo = await visualAwareness.getActiveWindow();
140
+ }
141
+
142
+ if (windowInfo && !windowInfo.error) {
143
+ const scaleFactor = getScaleFactor();
144
+ windowContext = inspectTypes.createWindowContext({
145
+ ...windowInfo,
146
+ scaleFactor
147
+ });
148
+ }
149
+
150
+ return windowContext;
151
+ }
152
+
153
+ /**
154
+ * Get current window context
155
+ * @returns {Object|null}
156
+ */
157
+ function getWindowContext() {
158
+ return windowContext;
159
+ }
160
+
161
+ /**
162
+ * Get display scale factor
163
+ * @returns {number}
164
+ */
165
+ function getScaleFactor() {
166
+ try {
167
+ return screen.getPrimaryDisplay().scaleFactor || 1;
168
+ } catch (e) {
169
+ return 1;
170
+ }
171
+ }
172
+
173
+ // ===== ACTION TRACING =====
174
+
175
+ /**
176
+ * Record an action for tracing
177
+ * @param {Object} action - Action data
178
+ * @param {string} [targetId] - ID of target region
179
+ * @returns {Object} Action trace
180
+ */
181
+ function recordAction(action, targetId = null) {
182
+ const trace = inspectTypes.createActionTrace({
183
+ type: action.type,
184
+ targetId: targetId || action.targetId,
185
+ x: action.x || 0,
186
+ y: action.y || 0,
187
+ outcome: 'pending'
188
+ });
189
+
190
+ actionTraces.push(trace);
191
+
192
+ // Trim history
193
+ while (actionTraces.length > MAX_ACTION_TRACES) {
194
+ actionTraces.shift();
195
+ }
196
+
197
+ return trace;
198
+ }
199
+
200
+ /**
201
+ * Update action outcome
202
+ * @param {string} actionId - ID of action to update
203
+ * @param {string} outcome - New outcome (success, failed)
204
+ */
205
+ function updateActionOutcome(actionId, outcome) {
206
+ const trace = actionTraces.find(t => t.actionId === actionId);
207
+ if (trace) {
208
+ trace.outcome = outcome;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Get action traces
214
+ * @param {number} [limit] - Max traces to return
215
+ * @returns {Object[]}
216
+ */
217
+ function getActionTraces(limit = 10) {
218
+ return actionTraces.slice(-limit);
219
+ }
220
+
221
+ // ===== AI CONTEXT GENERATION =====
222
+
223
+ /**
224
+ * Generate AI context payload including inspect regions and window context
225
+ * @param {Object} options - Options for context generation
226
+ * @returns {Object} AI context payload
227
+ */
228
+ function generateAIContext(options = {}) {
229
+ const { maxRegions = 50, includeTraces = true } = options;
230
+
231
+ // Format regions for AI
232
+ const formattedRegions = currentRegions
233
+ .slice(0, maxRegions)
234
+ .map(r => inspectTypes.formatRegionForAI(r));
235
+
236
+ const context = {
237
+ inspectMode: inspectMode,
238
+ windowContext: windowContext ? {
239
+ appName: windowContext.appName,
240
+ windowTitle: windowContext.windowTitle,
241
+ bounds: windowContext.bounds,
242
+ scaleFactor: windowContext.scaleFactor
243
+ } : null,
244
+ regions: formattedRegions,
245
+ regionCount: currentRegions.length,
246
+ selectedRegion: getSelectedRegion() ? inspectTypes.formatRegionForAI(getSelectedRegion()) : null
247
+ };
248
+
249
+ if (includeTraces) {
250
+ context.recentActions = getActionTraces(5);
251
+ }
252
+
253
+ return context;
254
+ }
255
+
256
+ /**
257
+ * Generate inspect instructions for AI system prompt
258
+ * @returns {string}
259
+ */
260
+ function generateAIInstructions() {
261
+ if (!inspectMode || currentRegions.length === 0) {
262
+ return '';
263
+ }
264
+
265
+ return `
266
+ ## Inspect Mode Active
267
+
268
+ You have access to detected UI regions. Each region has:
269
+ - **id**: Unique identifier for targeting
270
+ - **label**: Human-readable name
271
+ - **role**: UI role (button, textbox, etc.)
272
+ - **center**: Click coordinates {x, y}
273
+ - **confidence**: Detection confidence (0-1)
274
+
275
+ **IMPORTANT**: When clicking detected regions:
276
+ 1. Use the region's center coordinates for highest accuracy
277
+ 2. If confidence < 0.7, verify with the user before clicking
278
+ 3. Reference regions by their label or id in your explanations
279
+
280
+ Current regions available: ${currentRegions.length}
281
+ `;
282
+ }
283
+
284
+ // ===== REGION DETECTION INTEGRATION =====
285
+
286
+ /**
287
+ * Detect regions from current screen using available methods
288
+ * @param {Object} options - Detection options
289
+ * @returns {Object} Detection results
290
+ */
291
+ async function detectRegions(options = {}) {
292
+ const results = {
293
+ regions: [],
294
+ sources: [],
295
+ timestamp: Date.now()
296
+ };
297
+
298
+ try {
299
+ // Try accessibility API first
300
+ const uiElements = await visualAwareness.detectUIElements({ depth: 3 });
301
+ if (uiElements.elements && uiElements.elements.length > 0) {
302
+ updateRegions(
303
+ uiElements.elements.map(e => ({
304
+ label: e.Name || e.ClassName || '',
305
+ role: e.ControlType?.replace('ControlType.', '') || 'element',
306
+ bounds: e.Bounds,
307
+ confidence: e.IsEnabled ? 0.9 : 0.6
308
+ })),
309
+ 'accessibility'
310
+ );
311
+ results.sources.push('accessibility');
312
+ }
313
+
314
+ // Update window context
315
+ await updateWindowContext();
316
+
317
+ // Return copy of regions to prevent external mutation
318
+ results.regions = [...currentRegions];
319
+ results.windowContext = windowContext;
320
+
321
+ } catch (error) {
322
+ console.error('[INSPECT] Region detection error:', error);
323
+ results.error = error.message;
324
+ }
325
+
326
+ return results;
327
+ }
328
+
329
+ // ===== HELPER FUNCTIONS =====
330
+
331
+ /**
332
+ * Calculate confidence based on source and properties
333
+ * @param {Object} region - Region data
334
+ * @param {string} source - Detection source
335
+ * @returns {number} Confidence 0-1
336
+ */
337
+ function calculateConfidence(region, source) {
338
+ let base = 0.5;
339
+
340
+ // Source-based confidence
341
+ if (source === 'accessibility') base = 0.85;
342
+ else if (source === 'ocr') base = 0.7;
343
+ else if (source === 'heuristic') base = 0.5;
344
+
345
+ // Boost for having label/text
346
+ if (region.label || region.Name) base = Math.min(1, base + 0.1);
347
+ if (region.text || region.Value) base = Math.min(1, base + 0.05);
348
+
349
+ // Boost for known roles
350
+ const knownRoles = ['button', 'textbox', 'checkbox', 'link', 'menuitem'];
351
+ const role = (region.role || region.ControlType || '').toLowerCase();
352
+ if (knownRoles.some(r => role.includes(r))) {
353
+ base = Math.min(1, base + 0.1);
354
+ }
355
+
356
+ return Math.round(base * 100) / 100;
357
+ }
358
+
359
+ /**
360
+ * Merge regions, preferring newer and deduping overlaps
361
+ * @param {Object[]} existing - Existing regions
362
+ * @param {Object[]} incoming - New regions
363
+ * @returns {Object[]} Merged regions
364
+ */
365
+ function mergeRegions(existing, incoming) {
366
+ const merged = [];
367
+ const usedExisting = new Set();
368
+ const addedIds = new Set();
369
+
370
+ // Add incoming regions, checking for overlaps with existing
371
+ for (const inc of incoming) {
372
+ let isDupe = false;
373
+ for (const ex of existing) {
374
+ if (usedExisting.has(ex.id)) continue; // Skip already processed existing regions
375
+
376
+ if (regionsOverlap(inc, ex, 0.8)) {
377
+ // Significant overlap - prefer higher confidence
378
+ const winner = inc.confidence >= ex.confidence ? inc : ex;
379
+ if (!addedIds.has(winner.id)) {
380
+ merged.push(winner);
381
+ addedIds.add(winner.id);
382
+ }
383
+ usedExisting.add(ex.id);
384
+ isDupe = true;
385
+ break;
386
+ }
387
+ }
388
+ if (!isDupe && !addedIds.has(inc.id)) {
389
+ merged.push(inc);
390
+ addedIds.add(inc.id);
391
+ }
392
+ }
393
+
394
+ // Add remaining existing regions not overlapping
395
+ for (const ex of existing) {
396
+ if (!usedExisting.has(ex.id) && !addedIds.has(ex.id)) {
397
+ const hasOverlap = incoming.some(inc => regionsOverlap(ex, inc, 0.5));
398
+ if (!hasOverlap) {
399
+ merged.push(ex);
400
+ addedIds.add(ex.id);
401
+ }
402
+ }
403
+ }
404
+
405
+ return merged;
406
+ }
407
+
408
+ /**
409
+ * Check if two regions significantly overlap
410
+ * @param {Object} r1 - First region
411
+ * @param {Object} r2 - Second region
412
+ * @param {number} threshold - Overlap threshold (0-1)
413
+ * @returns {boolean}
414
+ */
415
+ function regionsOverlap(r1, r2, threshold = 0.5) {
416
+ const b1 = r1.bounds;
417
+ const b2 = r2.bounds;
418
+
419
+ const x1 = Math.max(b1.x, b2.x);
420
+ const y1 = Math.max(b1.y, b2.y);
421
+ const x2 = Math.min(b1.x + b1.width, b2.x + b2.width);
422
+ const y2 = Math.min(b1.y + b1.height, b2.y + b2.height);
423
+
424
+ if (x2 <= x1 || y2 <= y1) return false;
425
+
426
+ const intersectArea = (x2 - x1) * (y2 - y1);
427
+ const r1Area = b1.width * b1.height;
428
+ const r2Area = b2.width * b2.height;
429
+ const minArea = Math.min(r1Area, r2Area);
430
+
431
+ // Handle zero area case
432
+ if (minArea <= 0) return false;
433
+
434
+ return intersectArea / minArea >= threshold;
435
+ }
436
+
437
+ // ===== EXPORTS =====
438
+ module.exports = {
439
+ // Mode management
440
+ setInspectMode,
441
+ isInspectModeActive,
442
+
443
+ // Region management
444
+ updateRegions,
445
+ clearRegions,
446
+ getRegions,
447
+ selectRegion,
448
+ getSelectedRegion,
449
+ findRegionAt,
450
+
451
+ // Window context
452
+ updateWindowContext,
453
+ getWindowContext,
454
+ getScaleFactor,
455
+
456
+ // Action tracing
457
+ recordAction,
458
+ updateActionOutcome,
459
+ getActionTraces,
460
+
461
+ // AI integration
462
+ generateAIContext,
463
+ generateAIInstructions,
464
+
465
+ // Detection
466
+ detectRegions
467
+ };