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,183 @@
1
+ /**
2
+ * Screenshot Module
3
+ *
4
+ * Capture screenshots of screen, windows, or regions.
5
+ * @module ui-automation/screenshot
6
+ */
7
+
8
+ const { executePowerShellScript } = require('./core/powershell');
9
+ const { log } = require('./core/helpers');
10
+ const path = require('path');
11
+ const os = require('os');
12
+
13
+ /**
14
+ * Take a screenshot
15
+ *
16
+ * @param {Object} [options] - Screenshot options
17
+ * @param {string} [options.path] - Save path (auto-generated if omitted)
18
+ * @param {Object} [options.region] - Region to capture {x, y, width, height}
19
+ * @param {number} [options.windowHwnd] - Capture specific window by handle
20
+ * @param {string} [options.format='png'] - Image format (png, jpg, bmp)
21
+ * @returns {Promise<{success: boolean, path: string|null, base64: string|null}>}
22
+ */
23
+ async function screenshot(options = {}) {
24
+ const {
25
+ path: savePath,
26
+ region,
27
+ windowHwnd,
28
+ format = 'png',
29
+ } = options;
30
+
31
+ // Generate path if not provided
32
+ const outputPath = savePath || path.join(
33
+ os.tmpdir(),
34
+ `screenshot_${Date.now()}.${format}`
35
+ );
36
+
37
+ // Build PowerShell script based on capture type
38
+ let captureScript;
39
+
40
+ if (windowHwnd) {
41
+ // Capture specific window
42
+ captureScript = `
43
+ Add-Type @'
44
+ using System;
45
+ using System.Drawing;
46
+ using System.Runtime.InteropServices;
47
+
48
+ public class WindowCapture {
49
+ [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
50
+ [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr hWnd, IntPtr hDC, int flags);
51
+
52
+ [StructLayout(LayoutKind.Sequential)]
53
+ public struct RECT { public int Left, Top, Right, Bottom; }
54
+
55
+ public static Bitmap Capture(IntPtr hwnd) {
56
+ RECT rect;
57
+ GetWindowRect(hwnd, out rect);
58
+ int w = rect.Right - rect.Left;
59
+ int h = rect.Bottom - rect.Top;
60
+ if (w <= 0 || h <= 0) return null;
61
+
62
+ var bmp = new Bitmap(w, h);
63
+ using (var g = Graphics.FromImage(bmp)) {
64
+ IntPtr hdc = g.GetHdc();
65
+ PrintWindow(hwnd, hdc, 2);
66
+ g.ReleaseHdc(hdc);
67
+ }
68
+ return bmp;
69
+ }
70
+ }
71
+ '@
72
+
73
+ Add-Type -AssemblyName System.Drawing
74
+ $bmp = [WindowCapture]::Capture([IntPtr]::new(${windowHwnd}))
75
+ `;
76
+ } else if (region) {
77
+ // Capture region
78
+ captureScript = `
79
+ Add-Type -AssemblyName System.Drawing
80
+ $bmp = New-Object System.Drawing.Bitmap(${region.width}, ${region.height})
81
+ $g = [System.Drawing.Graphics]::FromImage($bmp)
82
+ $g.CopyFromScreen(${region.x}, ${region.y}, 0, 0, $bmp.Size)
83
+ $g.Dispose()
84
+ `;
85
+ } else {
86
+ // Capture full screen
87
+ captureScript = `
88
+ Add-Type -AssemblyName System.Windows.Forms
89
+ Add-Type -AssemblyName System.Drawing
90
+
91
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
92
+ $bmp = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
93
+ $g = [System.Drawing.Graphics]::FromImage($bmp)
94
+ $g.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size)
95
+ $g.Dispose()
96
+ `;
97
+ }
98
+
99
+ // Add save and output
100
+ const formatMap = { png: 'Png', jpg: 'Jpeg', bmp: 'Bmp' };
101
+ const imageFormat = formatMap[format.toLowerCase()] || 'Png';
102
+
103
+ const psScript = `
104
+ ${captureScript}
105
+ if ($bmp -eq $null) {
106
+ Write-Output "capture_failed"
107
+ exit
108
+ }
109
+
110
+ $path = '${outputPath.replace(/\\/g, '\\\\').replace(/'/g, "''")}'
111
+ $bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::${imageFormat})
112
+ $bmp.Dispose()
113
+
114
+ # Output base64 for convenience
115
+ $bytes = [System.IO.File]::ReadAllBytes($path)
116
+ $base64 = [System.Convert]::ToBase64String($bytes)
117
+ Write-Output "SCREENSHOT_PATH:$path"
118
+ Write-Output "SCREENSHOT_BASE64:$base64"
119
+ `;
120
+
121
+ try {
122
+ const result = await executePowerShellScript(psScript);
123
+
124
+ if (result.stdout.includes('capture_failed')) {
125
+ log('Screenshot capture failed', 'error');
126
+ return { success: false, path: null, base64: null };
127
+ }
128
+
129
+ const pathMatch = result.stdout.match(/SCREENSHOT_PATH:(.+)/);
130
+ const base64Match = result.stdout.match(/SCREENSHOT_BASE64:(.+)/);
131
+
132
+ const screenshotPath = pathMatch ? pathMatch[1].trim() : outputPath;
133
+ const base64 = base64Match ? base64Match[1].trim() : null;
134
+
135
+ log(`Screenshot saved to: ${screenshotPath}`);
136
+
137
+ return { success: true, path: screenshotPath, base64 };
138
+ } catch (err) {
139
+ log(`Screenshot error: ${err.message}`, 'error');
140
+ return { success: false, path: null, base64: null };
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Take screenshot of active window
146
+ *
147
+ * @param {Object} [options] - Screenshot options
148
+ * @returns {Promise<{success: boolean, path: string|null}>}
149
+ */
150
+ async function screenshotActiveWindow(options = {}) {
151
+ const { getActiveWindow } = require('./window');
152
+ const activeWindow = await getActiveWindow();
153
+
154
+ if (!activeWindow) {
155
+ return { success: false, path: null, base64: null };
156
+ }
157
+
158
+ return screenshot({ ...options, windowHwnd: activeWindow.hwnd });
159
+ }
160
+
161
+ /**
162
+ * Take screenshot of element
163
+ *
164
+ * @param {Object} criteria - Element search criteria
165
+ * @param {Object} [options] - Screenshot options
166
+ * @returns {Promise<{success: boolean, path: string|null}>}
167
+ */
168
+ async function screenshotElement(criteria, options = {}) {
169
+ const { findElement } = require('./elements');
170
+ const element = await findElement(criteria);
171
+
172
+ if (!element || !element.bounds) {
173
+ return { success: false, path: null, base64: null };
174
+ }
175
+
176
+ return screenshot({ ...options, region: element.bounds });
177
+ }
178
+
179
+ module.exports = {
180
+ screenshot,
181
+ screenshotActiveWindow,
182
+ screenshotElement,
183
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Window Management Module
3
+ *
4
+ * @module ui-automation/window
5
+ */
6
+
7
+ const {
8
+ getActiveWindow,
9
+ findWindows,
10
+ focusWindow,
11
+ minimizeWindow,
12
+ maximizeWindow,
13
+ restoreWindow,
14
+ } = require('./manager');
15
+
16
+ module.exports = {
17
+ getActiveWindow,
18
+ findWindows,
19
+ focusWindow,
20
+ minimizeWindow,
21
+ maximizeWindow,
22
+ restoreWindow,
23
+ };
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Window Management Module
3
+ *
4
+ * Find, focus, and interact with windows.
5
+ * @module ui-automation/window
6
+ */
7
+
8
+ const { executePowerShellScript } = require('../core/powershell');
9
+ const { log, sleep } = require('../core/helpers');
10
+
11
+ /**
12
+ * Get the active (foreground) window info
13
+ *
14
+ * @returns {Promise<{hwnd: number, title: string, processName: string, className: string, bounds: Object} | null>}
15
+ */
16
+ async function getActiveWindow() {
17
+ const psScript = `
18
+ Add-Type @'
19
+ using System;
20
+ using System.Runtime.InteropServices;
21
+ using System.Text;
22
+
23
+ public class WinAPI {
24
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
25
+ [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
26
+ [DllImport("user32.dll")] public static extern int GetClassName(IntPtr hWnd, StringBuilder name, int count);
27
+ [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint pid);
28
+ [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
29
+
30
+ [StructLayout(LayoutKind.Sequential)]
31
+ public struct RECT { public int Left, Top, Right, Bottom; }
32
+ }
33
+ '@
34
+
35
+ $hwnd = [WinAPI]::GetForegroundWindow()
36
+ if ($hwnd -eq [IntPtr]::Zero) { Write-Output "null"; exit }
37
+
38
+ $titleSB = New-Object System.Text.StringBuilder 256
39
+ $classSB = New-Object System.Text.StringBuilder 256
40
+ [void][WinAPI]::GetWindowText($hwnd, $titleSB, 256)
41
+ [void][WinAPI]::GetClassName($hwnd, $classSB, 256)
42
+
43
+ $procId = 0
44
+ [void][WinAPI]::GetWindowThreadProcessId($hwnd, [ref]$procId)
45
+ $proc = Get-Process -Id $procId -ErrorAction SilentlyContinue
46
+
47
+ $rect = New-Object WinAPI+RECT
48
+ [void][WinAPI]::GetWindowRect($hwnd, [ref]$rect)
49
+
50
+ @{
51
+ hwnd = $hwnd.ToInt64()
52
+ title = $titleSB.ToString()
53
+ className = $classSB.ToString()
54
+ processName = if ($proc) { $proc.ProcessName } else { "" }
55
+ bounds = @{ x = $rect.Left; y = $rect.Top; width = $rect.Right - $rect.Left; height = $rect.Bottom - $rect.Top }
56
+ } | ConvertTo-Json -Compress
57
+ `;
58
+
59
+ try {
60
+ const result = await executePowerShellScript(psScript);
61
+ if (result.stdout.trim() === 'null') return null;
62
+ const data = JSON.parse(result.stdout.trim());
63
+ log(`Active window: "${data.title}" (${data.processName})`);
64
+ return data;
65
+ } catch (err) {
66
+ log(`getActiveWindow error: ${err.message}`, 'error');
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Find windows matching criteria
73
+ *
74
+ * @param {Object} [criteria] - Search criteria
75
+ * @param {string} [criteria.title] - Window title contains
76
+ * @param {string} [criteria.processName] - Process name equals
77
+ * @param {string} [criteria.className] - Window class contains
78
+ * @returns {Promise<Array<{hwnd: number, title: string, processName: string, className: string, bounds: Object}>>}
79
+ */
80
+ async function findWindows(criteria = {}) {
81
+ const { title, processName, className } = criteria;
82
+
83
+ const psScript = `
84
+ Add-Type @'
85
+ using System;
86
+ using System.Collections.Generic;
87
+ using System.Runtime.InteropServices;
88
+ using System.Text;
89
+
90
+ public class WindowFinder {
91
+ [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc cb, IntPtr lParam);
92
+ [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
93
+ [DllImport("user32.dll")] public static extern int GetClassName(IntPtr hWnd, StringBuilder name, int count);
94
+ [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint pid);
95
+ [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
96
+ [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
97
+
98
+ [StructLayout(LayoutKind.Sequential)]
99
+ public struct RECT { public int Left, Top, Right, Bottom; }
100
+
101
+ public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
102
+
103
+ public static List<IntPtr> windows = new List<IntPtr>();
104
+
105
+ public static void Find() {
106
+ windows.Clear();
107
+ EnumWindows((h, l) => { if (IsWindowVisible(h)) windows.Add(h); return true; }, IntPtr.Zero);
108
+ }
109
+ }
110
+ '@
111
+
112
+ [WindowFinder]::Find()
113
+ $results = @()
114
+
115
+ foreach ($hwnd in [WindowFinder]::windows) {
116
+ $titleSB = New-Object System.Text.StringBuilder 256
117
+ $classSB = New-Object System.Text.StringBuilder 256
118
+ [void][WindowFinder]::GetWindowText($hwnd, $titleSB, 256)
119
+ [void][WindowFinder]::GetClassName($hwnd, $classSB, 256)
120
+
121
+ $t = $titleSB.ToString()
122
+ $c = $classSB.ToString()
123
+ if ([string]::IsNullOrEmpty($t)) { continue }
124
+
125
+ ${title ? `if (-not $t.ToLower().Contains('${title.toLowerCase().replace(/'/g, "''")}')) { continue }` : ''}
126
+ ${className ? `if (-not $c.ToLower().Contains('${className.toLowerCase().replace(/'/g, "''")}')) { continue }` : ''}
127
+
128
+ $procId = 0
129
+ [void][WindowFinder]::GetWindowThreadProcessId($hwnd, [ref]$procId)
130
+ $proc = Get-Process -Id $procId -ErrorAction SilentlyContinue
131
+ $pn = if ($proc) { $proc.ProcessName } else { "" }
132
+
133
+ ${processName ? `if ($pn -ne '${processName.replace(/'/g, "''")}') { continue }` : ''}
134
+
135
+ $rect = New-Object WindowFinder+RECT
136
+ [void][WindowFinder]::GetWindowRect($hwnd, [ref]$rect)
137
+
138
+ $results += @{
139
+ hwnd = $hwnd.ToInt64()
140
+ title = $t
141
+ className = $c
142
+ processName = $pn
143
+ bounds = @{ x = $rect.Left; y = $rect.Top; width = $rect.Right - $rect.Left; height = $rect.Bottom - $rect.Top }
144
+ }
145
+ }
146
+
147
+ $results | ConvertTo-Json -Compress
148
+ `;
149
+
150
+ try {
151
+ const result = await executePowerShellScript(psScript);
152
+ const output = result.stdout.trim();
153
+ if (!output || output === 'null') return [];
154
+ const data = JSON.parse(output);
155
+ const windows = Array.isArray(data) ? data : [data];
156
+ log(`Found ${windows.length} windows matching criteria`);
157
+ return windows;
158
+ } catch (err) {
159
+ log(`findWindows error: ${err.message}`, 'error');
160
+ return [];
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Focus a window (bring to foreground)
166
+ *
167
+ * @param {number|string|Object} target - Window handle, title substring, or criteria object
168
+ * @returns {Promise<{success: boolean, window: Object|null}>}
169
+ */
170
+ async function focusWindow(target) {
171
+ let hwnd = null;
172
+ let windowInfo = null;
173
+
174
+ if (typeof target === 'number') {
175
+ hwnd = target;
176
+ } else if (typeof target === 'string') {
177
+ const windows = await findWindows({ title: target });
178
+ if (windows.length > 0) {
179
+ hwnd = windows[0].hwnd;
180
+ windowInfo = windows[0];
181
+ }
182
+ } else if (typeof target === 'object' && target.hwnd) {
183
+ hwnd = target.hwnd;
184
+ windowInfo = target;
185
+ } else if (typeof target === 'object') {
186
+ const windows = await findWindows(target);
187
+ if (windows.length > 0) {
188
+ hwnd = windows[0].hwnd;
189
+ windowInfo = windows[0];
190
+ }
191
+ }
192
+
193
+ if (!hwnd) {
194
+ log(`focusWindow: No window found for target`, 'warn');
195
+ return { success: false, window: null };
196
+ }
197
+
198
+ const psScript = `
199
+ Add-Type @'
200
+ using System;
201
+ using System.Runtime.InteropServices;
202
+
203
+ public class FocusHelper {
204
+ [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
205
+ [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int cmd);
206
+ [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr hWnd);
207
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
208
+ }
209
+ '@
210
+
211
+ $hwnd = [IntPtr]::new(${hwnd})
212
+ [FocusHelper]::ShowWindow($hwnd, 9) # SW_RESTORE
213
+ Start-Sleep -Milliseconds 50
214
+ [FocusHelper]::BringWindowToTop($hwnd)
215
+ [FocusHelper]::SetForegroundWindow($hwnd)
216
+ Start-Sleep -Milliseconds 100
217
+
218
+ $fg = [FocusHelper]::GetForegroundWindow()
219
+ if ($fg -eq $hwnd) { "focused" } else { "failed" }
220
+ `;
221
+
222
+ const result = await executePowerShellScript(psScript);
223
+ const success = result.stdout.includes('focused');
224
+ log(`focusWindow hwnd=${hwnd} - ${success ? 'success' : 'failed'}`);
225
+
226
+ return { success, window: windowInfo };
227
+ }
228
+
229
+ /**
230
+ * Minimize a window
231
+ *
232
+ * @param {number} hwnd - Window handle
233
+ * @returns {Promise<{success: boolean}>}
234
+ */
235
+ async function minimizeWindow(hwnd) {
236
+ const psScript = `
237
+ Add-Type @'
238
+ using System;
239
+ using System.Runtime.InteropServices;
240
+ public class MinHelper {
241
+ [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int cmd);
242
+ }
243
+ '@
244
+ [MinHelper]::ShowWindow([IntPtr]::new(${hwnd}), 6) # SW_MINIMIZE
245
+ 'minimized'
246
+ `;
247
+
248
+ const result = await executePowerShellScript(psScript);
249
+ return { success: result.stdout.includes('minimized') };
250
+ }
251
+
252
+ /**
253
+ * Maximize a window
254
+ *
255
+ * @param {number} hwnd - Window handle
256
+ * @returns {Promise<{success: boolean}>}
257
+ */
258
+ async function maximizeWindow(hwnd) {
259
+ const psScript = `
260
+ Add-Type @'
261
+ using System;
262
+ using System.Runtime.InteropServices;
263
+ public class MaxHelper {
264
+ [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int cmd);
265
+ }
266
+ '@
267
+ [MaxHelper]::ShowWindow([IntPtr]::new(${hwnd}), 3) # SW_MAXIMIZE
268
+ 'maximized'
269
+ `;
270
+
271
+ const result = await executePowerShellScript(psScript);
272
+ return { success: result.stdout.includes('maximized') };
273
+ }
274
+
275
+ /**
276
+ * Restore a window to normal state
277
+ *
278
+ * @param {number} hwnd - Window handle
279
+ * @returns {Promise<{success: boolean}>}
280
+ */
281
+ async function restoreWindow(hwnd) {
282
+ const psScript = `
283
+ Add-Type @'
284
+ using System;
285
+ using System.Runtime.InteropServices;
286
+ public class RestoreHelper {
287
+ [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int cmd);
288
+ }
289
+ '@
290
+ [RestoreHelper]::ShowWindow([IntPtr]::new(${hwnd}), 9) # SW_RESTORE
291
+ 'restored'
292
+ `;
293
+
294
+ const result = await executePowerShellScript(psScript);
295
+ return { success: result.stdout.includes('restored') };
296
+ }
297
+
298
+ module.exports = {
299
+ getActiveWindow,
300
+ findWindows,
301
+ focusWindow,
302
+ minimizeWindow,
303
+ maximizeWindow,
304
+ restoreWindow,
305
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Centralized time utilities for consistent timestamp handling
3
+ */
4
+
5
+ const TIME_FORMAT = {
6
+ ISO: 'iso',
7
+ FILENAME_SAFE: 'filename',
8
+ DISPLAY: 'display'
9
+ };
10
+
11
+ function nowIso() {
12
+ return new Date().toISOString();
13
+ }
14
+
15
+ function nowFilenameSafe() {
16
+ return new Date().toISOString().replace(/[:.]/g, '-');
17
+ }
18
+
19
+ function nowDisplay() {
20
+ return new Date().toLocaleString();
21
+ }
22
+
23
+ function formatTimestamp(date, format = TIME_FORMAT.ISO) {
24
+ const d = date instanceof Date ? date : new Date(date);
25
+
26
+ switch (format) {
27
+ case TIME_FORMAT.FILENAME_SAFE:
28
+ return d.toISOString().replace(/[:.]/g, '-');
29
+ case TIME_FORMAT.DISPLAY:
30
+ return d.toLocaleString();
31
+ case TIME_FORMAT.ISO:
32
+ default:
33
+ return d.toISOString();
34
+ }
35
+ }
36
+
37
+ function parseTimestamp(timestamp) {
38
+ return new Date(timestamp);
39
+ }
40
+
41
+ function timeSince(timestamp) {
42
+ const ms = Date.now() - new Date(timestamp).getTime();
43
+ const seconds = Math.floor(ms / 1000);
44
+ const minutes = Math.floor(seconds / 60);
45
+ const hours = Math.floor(minutes / 60);
46
+ const days = Math.floor(hours / 24);
47
+
48
+ if (days > 0) return `${days}d ago`;
49
+ if (hours > 0) return `${hours}h ago`;
50
+ if (minutes > 0) return `${minutes}m ago`;
51
+ return `${seconds}s ago`;
52
+ }
53
+
54
+ module.exports = {
55
+ TIME_FORMAT,
56
+ nowIso,
57
+ nowFilenameSafe,
58
+ nowDisplay,
59
+ formatTimestamp,
60
+ parseTimestamp,
61
+ timeSince
62
+ };