@pulso/companion 0.1.4 → 0.1.6
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/dist/index.js +322 -50
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45,54 +45,111 @@ function runShell(cmd, timeout = 1e4) {
|
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const res = await fetch("https://open.spotify.com/embed/track/4u7EnebtmKWzUH433cf5Qv", {
|
|
54
|
-
headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" }
|
|
48
|
+
function runSwift(code, timeout = 1e4) {
|
|
49
|
+
return new Promise((resolve2, reject) => {
|
|
50
|
+
const child = exec(`swift -`, { timeout }, (err, stdout, stderr) => {
|
|
51
|
+
if (err) reject(new Error(stderr || err.message));
|
|
52
|
+
else resolve2(stdout.trim());
|
|
55
53
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
spotifyToken = match[1];
|
|
60
|
-
spotifyTokenExpiry = Date.now() + 55 * 60 * 1e3;
|
|
61
|
-
return spotifyToken;
|
|
62
|
-
} catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
54
|
+
child.stdin?.write(code);
|
|
55
|
+
child.stdin?.end();
|
|
56
|
+
});
|
|
65
57
|
}
|
|
58
|
+
var UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
59
|
+
var searchCache = /* @__PURE__ */ new Map();
|
|
60
|
+
var CACHE_TTL = 7 * 24 * 3600 * 1e3;
|
|
66
61
|
async function spotifySearch(query) {
|
|
67
|
-
const
|
|
68
|
-
|
|
62
|
+
const key = query.toLowerCase().trim();
|
|
63
|
+
const cached = searchCache.get(key);
|
|
64
|
+
if (cached && Date.now() - cached.ts < CACHE_TTL) {
|
|
65
|
+
return cached;
|
|
66
|
+
}
|
|
69
67
|
try {
|
|
70
68
|
const res = await fetch(
|
|
71
|
-
|
|
72
|
-
{ headers: { Authorization: `Bearer ${
|
|
69
|
+
`${API_URL}/tools/spotify/search?q=${encodeURIComponent(query)}`,
|
|
70
|
+
{ headers: { Authorization: `Bearer ${TOKEN}` } }
|
|
73
71
|
);
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!newToken) return null;
|
|
80
|
-
const retry = await fetch(
|
|
81
|
-
`https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=track&limit=1`,
|
|
82
|
-
{ headers: { Authorization: `Bearer ${newToken}` } }
|
|
83
|
-
);
|
|
84
|
-
if (!retry.ok) return null;
|
|
85
|
-
const retryData = await retry.json();
|
|
86
|
-
return retryData.tracks?.items?.[0]?.uri ?? null;
|
|
72
|
+
if (res.ok) {
|
|
73
|
+
const data = await res.json();
|
|
74
|
+
if (data.uri) {
|
|
75
|
+
searchCache.set(key, { ...data, ts: Date.now() });
|
|
76
|
+
return data;
|
|
87
77
|
}
|
|
88
|
-
return null;
|
|
89
78
|
}
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
const trackIds = await searchBraveForSpotifyTracks(query);
|
|
82
|
+
if (trackIds.length === 0) return null;
|
|
83
|
+
const meta = await getTrackMetadata(trackIds[0]);
|
|
84
|
+
const result = {
|
|
85
|
+
uri: `spotify:track:${trackIds[0]}`,
|
|
86
|
+
name: meta?.name ?? query,
|
|
87
|
+
artist: meta?.artist ?? "Unknown"
|
|
88
|
+
};
|
|
89
|
+
searchCache.set(key, { ...result, ts: Date.now() });
|
|
90
|
+
pushToServerCache(query, result).catch(() => {
|
|
91
|
+
});
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
async function searchBraveForSpotifyTracks(query) {
|
|
95
|
+
try {
|
|
96
|
+
const searchQuery = `spotify track ${query} site:open.spotify.com`;
|
|
97
|
+
const url = `https://search.brave.com/search?q=${encodeURIComponent(searchQuery)}&source=web`;
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
const timeout = setTimeout(() => controller.abort(), 8e3);
|
|
100
|
+
const res = await fetch(url, {
|
|
101
|
+
headers: { "User-Agent": UA, Accept: "text/html" },
|
|
102
|
+
signal: controller.signal
|
|
103
|
+
});
|
|
104
|
+
clearTimeout(timeout);
|
|
105
|
+
if (!res.ok) return [];
|
|
106
|
+
const html = await res.text();
|
|
107
|
+
const trackIds = /* @__PURE__ */ new Set();
|
|
108
|
+
for (const m of html.matchAll(/https?:\/\/open\.spotify\.com\/track\/([a-zA-Z0-9]{22})/g)) {
|
|
109
|
+
trackIds.add(m[1]);
|
|
110
|
+
}
|
|
111
|
+
return [...trackIds];
|
|
112
|
+
} catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function getTrackMetadata(trackId) {
|
|
117
|
+
try {
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
120
|
+
const res = await fetch(`https://open.spotify.com/embed/track/${trackId}`, {
|
|
121
|
+
headers: { "User-Agent": UA, Accept: "text/html" },
|
|
122
|
+
signal: controller.signal
|
|
123
|
+
});
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
if (!res.ok) return null;
|
|
126
|
+
const html = await res.text();
|
|
127
|
+
const scriptMatch = html.match(/<script[^>]*>(\{"props":\{"pageProps".*?\})<\/script>/s);
|
|
128
|
+
if (!scriptMatch) return null;
|
|
129
|
+
const data = JSON.parse(scriptMatch[1]);
|
|
130
|
+
const entity = data?.props?.pageProps?.state?.data?.entity;
|
|
131
|
+
if (!entity?.name) return null;
|
|
132
|
+
return {
|
|
133
|
+
name: entity.name,
|
|
134
|
+
artist: entity.artists?.map((a) => a.name).join(", ") ?? "Unknown"
|
|
135
|
+
};
|
|
92
136
|
} catch {
|
|
93
137
|
return null;
|
|
94
138
|
}
|
|
95
139
|
}
|
|
140
|
+
async function pushToServerCache(query, result) {
|
|
141
|
+
try {
|
|
142
|
+
await fetch(`${API_URL}/tools/spotify/cache`, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: {
|
|
145
|
+
Authorization: `Bearer ${TOKEN}`,
|
|
146
|
+
"Content-Type": "application/json"
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify({ query, ...result })
|
|
149
|
+
});
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
96
153
|
async function handleCommand(command, params) {
|
|
97
154
|
try {
|
|
98
155
|
switch (command) {
|
|
@@ -160,20 +217,36 @@ async function handleCommand(command, params) {
|
|
|
160
217
|
case "search_play": {
|
|
161
218
|
const query = params.query;
|
|
162
219
|
if (!query) return { success: false, error: "Missing search query" };
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
165
|
-
await runAppleScript(`tell application "Spotify" to play track "${
|
|
220
|
+
const result = await spotifySearch(query);
|
|
221
|
+
if (result) {
|
|
222
|
+
await runAppleScript(`tell application "Spotify" to play track "${result.uri}"`);
|
|
166
223
|
await new Promise((r) => setTimeout(r, 1500));
|
|
167
224
|
try {
|
|
168
225
|
const track = await runAppleScript('tell application "Spotify" to name of current track');
|
|
169
226
|
const artist = await runAppleScript('tell application "Spotify" to artist of current track');
|
|
170
|
-
|
|
227
|
+
const state = await runAppleScript('tell application "Spotify" to player state as string');
|
|
228
|
+
return {
|
|
229
|
+
success: true,
|
|
230
|
+
data: {
|
|
231
|
+
searched: query,
|
|
232
|
+
resolved: `${result.name} - ${result.artist}`,
|
|
233
|
+
nowPlaying: `${track} - ${artist}`,
|
|
234
|
+
state
|
|
235
|
+
}
|
|
236
|
+
};
|
|
171
237
|
} catch {
|
|
172
|
-
return { success: true, data: { searched: query, note: "Playing track" } };
|
|
238
|
+
return { success: true, data: { searched: query, resolved: `${result.name} - ${result.artist}`, note: "Playing track" } };
|
|
173
239
|
}
|
|
174
240
|
}
|
|
175
241
|
await runShell(`open "spotify:search:${encodeURIComponent(query)}"`);
|
|
176
|
-
return {
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
data: {
|
|
245
|
+
searched: query,
|
|
246
|
+
note: "Search engine unavailable. Opened Spotify search \u2014 use computer-use (screenshot + click) to select the correct song.",
|
|
247
|
+
requiresComputerUse: true
|
|
248
|
+
}
|
|
249
|
+
};
|
|
177
250
|
}
|
|
178
251
|
case "volume": {
|
|
179
252
|
const level = params.level;
|
|
@@ -226,20 +299,219 @@ async function handleCommand(command, params) {
|
|
|
226
299
|
return { success: true, data: { path, written: content.length } };
|
|
227
300
|
}
|
|
228
301
|
case "sys_screenshot": {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
302
|
+
const ts = Date.now();
|
|
303
|
+
const pngPath = `/tmp/pulso-ss-${ts}.png`;
|
|
304
|
+
const jpgPath = `/tmp/pulso-ss-${ts}.jpg`;
|
|
305
|
+
await runShell(`screencapture -x ${pngPath}`, 15e3);
|
|
306
|
+
if (!existsSync(pngPath)) return { success: false, error: "Screenshot failed" };
|
|
307
|
+
try {
|
|
308
|
+
await runShell(`sips --resampleWidth 1280 --setProperty format jpeg --setProperty formatOptions 60 ${pngPath} --out ${jpgPath}`, 1e4);
|
|
309
|
+
} catch {
|
|
310
|
+
const buf2 = readFileSync(pngPath);
|
|
311
|
+
exec(`rm -f ${pngPath}`);
|
|
312
|
+
return { success: true, data: { image: `data:image/png;base64,${buf2.toString("base64")}`, format: "png", note: "Full screen screenshot" } };
|
|
313
|
+
}
|
|
314
|
+
const buf = readFileSync(jpgPath);
|
|
233
315
|
const base64 = buf.toString("base64");
|
|
234
|
-
exec(`rm -f ${
|
|
316
|
+
exec(`rm -f ${pngPath} ${jpgPath}`);
|
|
317
|
+
let screenSize = "unknown";
|
|
318
|
+
try {
|
|
319
|
+
screenSize = await runShell(`system_profiler SPDisplaysDataType 2>/dev/null | grep Resolution | head -1 | sed 's/.*: //'`);
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
235
322
|
return {
|
|
236
323
|
success: true,
|
|
237
324
|
data: {
|
|
238
|
-
image: `data:image/
|
|
239
|
-
|
|
325
|
+
image: `data:image/jpeg;base64,${base64}`,
|
|
326
|
+
format: "jpeg",
|
|
327
|
+
resolution: screenSize.trim(),
|
|
328
|
+
imageWidth: 1280,
|
|
329
|
+
note: "Screenshot captured. Coordinates in this image are scaled \u2014 multiply x by (screen_width/1280) for actual clicks."
|
|
240
330
|
}
|
|
241
331
|
};
|
|
242
332
|
}
|
|
333
|
+
// ── Computer-Use: Mouse & Keyboard ────────────────────
|
|
334
|
+
case "sys_mouse_click": {
|
|
335
|
+
const x = Number(params.x);
|
|
336
|
+
const y = Number(params.y);
|
|
337
|
+
const button = params.button || "left";
|
|
338
|
+
if (isNaN(x) || isNaN(y)) return { success: false, error: "Missing x, y coordinates" };
|
|
339
|
+
const mouseType = button === "right" ? "rightMouseDown" : "leftMouseDown";
|
|
340
|
+
const mouseTypeUp = button === "right" ? "rightMouseUp" : "leftMouseUp";
|
|
341
|
+
const mouseButton = button === "right" ? ".right" : ".left";
|
|
342
|
+
const swift = `
|
|
343
|
+
import Cocoa
|
|
344
|
+
let p = CGPoint(x: ${x}, y: ${y})
|
|
345
|
+
let d = CGEvent(mouseEventSource: nil, mouseType: .${mouseType}, mouseCursorPosition: p, mouseButton: ${mouseButton})!
|
|
346
|
+
d.post(tap: .cghidEventTap)
|
|
347
|
+
usleep(50000)
|
|
348
|
+
let u = CGEvent(mouseEventSource: nil, mouseType: .${mouseTypeUp}, mouseCursorPosition: p, mouseButton: ${mouseButton})!
|
|
349
|
+
u.post(tap: .cghidEventTap)
|
|
350
|
+
print("clicked")`;
|
|
351
|
+
await runSwift(swift);
|
|
352
|
+
return { success: true, data: { clicked: { x, y }, button } };
|
|
353
|
+
}
|
|
354
|
+
case "sys_mouse_double_click": {
|
|
355
|
+
const x = Number(params.x);
|
|
356
|
+
const y = Number(params.y);
|
|
357
|
+
if (isNaN(x) || isNaN(y)) return { success: false, error: "Missing x, y coordinates" };
|
|
358
|
+
const swift = `
|
|
359
|
+
import Cocoa
|
|
360
|
+
let p = CGPoint(x: ${x}, y: ${y})
|
|
361
|
+
let d1 = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: p, mouseButton: .left)!
|
|
362
|
+
d1.setIntegerValueField(.mouseEventClickState, value: 1)
|
|
363
|
+
d1.post(tap: .cghidEventTap)
|
|
364
|
+
usleep(30000)
|
|
365
|
+
let u1 = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: p, mouseButton: .left)!
|
|
366
|
+
u1.setIntegerValueField(.mouseEventClickState, value: 1)
|
|
367
|
+
u1.post(tap: .cghidEventTap)
|
|
368
|
+
usleep(80000)
|
|
369
|
+
let d2 = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: p, mouseButton: .left)!
|
|
370
|
+
d2.setIntegerValueField(.mouseEventClickState, value: 2)
|
|
371
|
+
d2.post(tap: .cghidEventTap)
|
|
372
|
+
usleep(30000)
|
|
373
|
+
let u2 = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: p, mouseButton: .left)!
|
|
374
|
+
u2.setIntegerValueField(.mouseEventClickState, value: 2)
|
|
375
|
+
u2.post(tap: .cghidEventTap)
|
|
376
|
+
print("double-clicked")`;
|
|
377
|
+
await runSwift(swift);
|
|
378
|
+
return { success: true, data: { doubleClicked: { x, y } } };
|
|
379
|
+
}
|
|
380
|
+
case "sys_mouse_scroll": {
|
|
381
|
+
const x = Number(params.x) || 0;
|
|
382
|
+
const y = Number(params.y) || 0;
|
|
383
|
+
const scrollY = Number(params.scrollY) || 0;
|
|
384
|
+
const scrollX = Number(params.scrollX) || 0;
|
|
385
|
+
if (!scrollY && !scrollX) return { success: false, error: "Missing scrollY or scrollX" };
|
|
386
|
+
const swift = `
|
|
387
|
+
import Cocoa
|
|
388
|
+
let p = CGPoint(x: ${x}, y: ${y})
|
|
389
|
+
let move = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: p, mouseButton: .left)!
|
|
390
|
+
move.post(tap: .cghidEventTap)
|
|
391
|
+
usleep(50000)
|
|
392
|
+
let scroll = CGEvent(scrollWheelEvent2Source: nil, units: .pixel, wheelCount: 2, wheel1: Int32(${-scrollY}), wheel2: Int32(${-scrollX}))!
|
|
393
|
+
scroll.post(tap: .cghidEventTap)
|
|
394
|
+
print("scrolled")`;
|
|
395
|
+
await runSwift(swift);
|
|
396
|
+
return { success: true, data: { scrolled: { x, y, scrollY, scrollX } } };
|
|
397
|
+
}
|
|
398
|
+
case "sys_keyboard_type": {
|
|
399
|
+
const text = params.text;
|
|
400
|
+
if (!text) return { success: false, error: "Missing text" };
|
|
401
|
+
await runAppleScript(`tell application "System Events" to keystroke "${text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
|
|
402
|
+
return { success: true, data: { typed: text.slice(0, 100) } };
|
|
403
|
+
}
|
|
404
|
+
case "sys_key_press": {
|
|
405
|
+
const key = params.key;
|
|
406
|
+
if (!key) return { success: false, error: "Missing key" };
|
|
407
|
+
const rawMods = params.modifiers;
|
|
408
|
+
const modifiers = Array.isArray(rawMods) ? rawMods : typeof rawMods === "string" ? rawMods.split(",").map((m) => m.trim()).filter(Boolean) : [];
|
|
409
|
+
const modMap = {
|
|
410
|
+
cmd: "command down",
|
|
411
|
+
command: "command down",
|
|
412
|
+
shift: "shift down",
|
|
413
|
+
alt: "option down",
|
|
414
|
+
option: "option down",
|
|
415
|
+
ctrl: "control down",
|
|
416
|
+
control: "control down"
|
|
417
|
+
};
|
|
418
|
+
const modStr = modifiers.map((m) => modMap[m.toLowerCase()] || "").filter(Boolean);
|
|
419
|
+
const keyCodeMap = {
|
|
420
|
+
enter: 36,
|
|
421
|
+
return: 36,
|
|
422
|
+
tab: 48,
|
|
423
|
+
escape: 27,
|
|
424
|
+
esc: 27,
|
|
425
|
+
delete: 51,
|
|
426
|
+
backspace: 51,
|
|
427
|
+
space: 49,
|
|
428
|
+
up: 126,
|
|
429
|
+
down: 125,
|
|
430
|
+
left: 123,
|
|
431
|
+
right: 124,
|
|
432
|
+
f1: 122,
|
|
433
|
+
f2: 120,
|
|
434
|
+
f3: 99,
|
|
435
|
+
f4: 118,
|
|
436
|
+
f5: 96,
|
|
437
|
+
f6: 97,
|
|
438
|
+
f7: 98,
|
|
439
|
+
f8: 100,
|
|
440
|
+
f9: 101,
|
|
441
|
+
f10: 109,
|
|
442
|
+
f11: 103,
|
|
443
|
+
f12: 111,
|
|
444
|
+
home: 115,
|
|
445
|
+
end: 119,
|
|
446
|
+
pageup: 116,
|
|
447
|
+
pagedown: 121
|
|
448
|
+
};
|
|
449
|
+
const keyCode = keyCodeMap[key.toLowerCase()];
|
|
450
|
+
if (keyCode !== void 0) {
|
|
451
|
+
const using = modStr.length > 0 ? ` using {${modStr.join(", ")}}` : "";
|
|
452
|
+
await runAppleScript(`tell application "System Events" to key code ${keyCode}${using}`);
|
|
453
|
+
} else if (key.length === 1) {
|
|
454
|
+
const using = modStr.length > 0 ? ` using {${modStr.join(", ")}}` : "";
|
|
455
|
+
await runAppleScript(`tell application "System Events" to keystroke "${key}"${using}`);
|
|
456
|
+
} else {
|
|
457
|
+
return { success: false, error: `Unknown key: ${key}. Use single characters or: enter, tab, escape, delete, space, up, down, left, right, f1-f12, home, end, pageup, pagedown` };
|
|
458
|
+
}
|
|
459
|
+
return { success: true, data: { pressed: key, modifiers } };
|
|
460
|
+
}
|
|
461
|
+
case "sys_mouse_move": {
|
|
462
|
+
const x = Number(params.x);
|
|
463
|
+
const y = Number(params.y);
|
|
464
|
+
if (isNaN(x) || isNaN(y)) return { success: false, error: "Missing x, y coordinates" };
|
|
465
|
+
const swift = `
|
|
466
|
+
import Cocoa
|
|
467
|
+
let p = CGPoint(x: ${x}, y: ${y})
|
|
468
|
+
let m = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: p, mouseButton: .left)!
|
|
469
|
+
m.post(tap: .cghidEventTap)
|
|
470
|
+
print("moved")`;
|
|
471
|
+
await runSwift(swift);
|
|
472
|
+
return { success: true, data: { movedTo: { x, y } } };
|
|
473
|
+
}
|
|
474
|
+
case "sys_drag": {
|
|
475
|
+
const fromX = Number(params.fromX);
|
|
476
|
+
const fromY = Number(params.fromY);
|
|
477
|
+
const toX = Number(params.toX);
|
|
478
|
+
const toY = Number(params.toY);
|
|
479
|
+
if ([fromX, fromY, toX, toY].some(isNaN)) return { success: false, error: "Missing fromX, fromY, toX, toY" };
|
|
480
|
+
const swift = `
|
|
481
|
+
import Cocoa
|
|
482
|
+
let from = CGPoint(x: ${fromX}, y: ${fromY})
|
|
483
|
+
let to = CGPoint(x: ${toX}, y: ${toY})
|
|
484
|
+
let d = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: from, mouseButton: .left)!
|
|
485
|
+
d.post(tap: .cghidEventTap)
|
|
486
|
+
usleep(100000)
|
|
487
|
+
let steps = 10
|
|
488
|
+
for i in 1...steps {
|
|
489
|
+
let frac = Double(i) / Double(steps)
|
|
490
|
+
let p = CGPoint(x: from.x + (to.x - from.x) * frac, y: from.y + (to.y - from.y) * frac)
|
|
491
|
+
let drag = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDragged, mouseCursorPosition: p, mouseButton: .left)!
|
|
492
|
+
drag.post(tap: .cghidEventTap)
|
|
493
|
+
usleep(20000)
|
|
494
|
+
}
|
|
495
|
+
let u = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: to, mouseButton: .left)!
|
|
496
|
+
u.post(tap: .cghidEventTap)
|
|
497
|
+
print("dragged")`;
|
|
498
|
+
await runSwift(swift);
|
|
499
|
+
return { success: true, data: { dragged: { from: { x: fromX, y: fromY }, to: { x: toX, y: toY } } } };
|
|
500
|
+
}
|
|
501
|
+
case "sys_get_cursor_position": {
|
|
502
|
+
const swift = `
|
|
503
|
+
import Cocoa
|
|
504
|
+
let loc = NSEvent.mouseLocation
|
|
505
|
+
let screens = NSScreen.screens
|
|
506
|
+
let mainHeight = screens.first?.frame.height ?? 0
|
|
507
|
+
// Convert from bottom-left to top-left coordinate system
|
|
508
|
+
let x = Int(loc.x)
|
|
509
|
+
let y = Int(mainHeight - loc.y)
|
|
510
|
+
print("\\(x),\\(y)")`;
|
|
511
|
+
const pos = await runSwift(swift);
|
|
512
|
+
const [cx, cy] = pos.trim().split(",").map(Number);
|
|
513
|
+
return { success: true, data: { x: cx, y: cy } };
|
|
514
|
+
}
|
|
243
515
|
case "sys_run_shortcut": {
|
|
244
516
|
const name = params.name;
|
|
245
517
|
const input = params.input;
|
|
@@ -334,7 +606,7 @@ function scheduleReconnect() {
|
|
|
334
606
|
}
|
|
335
607
|
console.log("");
|
|
336
608
|
console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
337
|
-
console.log(" \u2551 \u{1FAC0} Pulso Mac Companion v0.1.
|
|
609
|
+
console.log(" \u2551 \u{1FAC0} Pulso Mac Companion v0.1.5 \u2551");
|
|
338
610
|
console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
339
611
|
console.log("");
|
|
340
612
|
connect();
|