simen-keyboard-listener 1.1.8 → 1.1.10
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.d.mts +518 -1
- package/dist/index.d.ts +518 -1
- package/dist/index.js +1400 -11
- package/dist/index.mjs +1371 -8
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -11,7 +11,1344 @@ import * as fs from "fs";
|
|
|
11
11
|
import { execFileSync } from "child_process";
|
|
12
12
|
import { createRequire } from "module";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
|
+
|
|
15
|
+
// src/osascript/executor.ts
|
|
16
|
+
import { execFile } from "child_process";
|
|
17
|
+
import { promisify } from "util";
|
|
18
|
+
var execFileAsync = promisify(execFile);
|
|
14
19
|
var IS_MACOS = process.platform === "darwin";
|
|
20
|
+
var OSASCRIPT_PATH = "/usr/bin/osascript";
|
|
21
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
22
|
+
function isOsascriptAvailable() {
|
|
23
|
+
return IS_MACOS;
|
|
24
|
+
}
|
|
25
|
+
async function executeAppleScript(script, options) {
|
|
26
|
+
if (!IS_MACOS) {
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
error: "osascript is only available on macOS"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
try {
|
|
35
|
+
const { stdout, stderr } = await execFileAsync(
|
|
36
|
+
OSASCRIPT_PATH,
|
|
37
|
+
["-e", script],
|
|
38
|
+
{ timeout }
|
|
39
|
+
);
|
|
40
|
+
const executionTime = Date.now() - startTime;
|
|
41
|
+
if (stderr && stderr.trim()) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: stderr.trim(),
|
|
45
|
+
executionTime
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
data: stdout.trim(),
|
|
51
|
+
executionTime
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const executionTime = Date.now() - startTime;
|
|
55
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: errorMessage,
|
|
59
|
+
executionTime
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function executeAppleScriptLines(lines, options) {
|
|
64
|
+
if (!IS_MACOS) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
error: "osascript is only available on macOS"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
const args = lines.flatMap((line) => ["-e", line]);
|
|
73
|
+
try {
|
|
74
|
+
const { stdout, stderr } = await execFileAsync(
|
|
75
|
+
OSASCRIPT_PATH,
|
|
76
|
+
args,
|
|
77
|
+
{ timeout }
|
|
78
|
+
);
|
|
79
|
+
const executionTime = Date.now() - startTime;
|
|
80
|
+
if (stderr && stderr.trim()) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: stderr.trim(),
|
|
84
|
+
executionTime
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
data: stdout.trim(),
|
|
90
|
+
executionTime
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const executionTime = Date.now() - startTime;
|
|
94
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
error: errorMessage,
|
|
98
|
+
executionTime
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function executeAndParse(script, parser, options) {
|
|
103
|
+
const result = await executeAppleScript(script, options);
|
|
104
|
+
if (!result.success) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: result.error,
|
|
108
|
+
executionTime: result.executionTime
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const parsed = parser(result.data ?? "");
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
data: parsed,
|
|
116
|
+
executionTime: result.executionTime
|
|
117
|
+
};
|
|
118
|
+
} catch (parseError) {
|
|
119
|
+
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
error: `Failed to parse output: ${errorMessage}`,
|
|
123
|
+
executionTime: result.executionTime
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function executeMultilineAndParse(lines, parser, options) {
|
|
128
|
+
const result = await executeAppleScriptLines(lines, options);
|
|
129
|
+
if (!result.success) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: result.error,
|
|
133
|
+
executionTime: result.executionTime
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const parsed = parser(result.data ?? "");
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
data: parsed,
|
|
141
|
+
executionTime: result.executionTime
|
|
142
|
+
};
|
|
143
|
+
} catch (parseError) {
|
|
144
|
+
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
error: `Failed to parse output: ${errorMessage}`,
|
|
148
|
+
executionTime: result.executionTime
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function escapeForAppleScript(str) {
|
|
153
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/osascript/scripts/batchContext.ts
|
|
157
|
+
var SEC = "\xA7\xA7\xA7";
|
|
158
|
+
var FLD = "|||";
|
|
159
|
+
var ITM = ":::";
|
|
160
|
+
var SCRIPT_BASIC = `
|
|
161
|
+
set d to "${FLD}"
|
|
162
|
+
set s to "${SEC}"
|
|
163
|
+
set i to "${ITM}"
|
|
164
|
+
|
|
165
|
+
tell application "System Events"
|
|
166
|
+
set fp to first application process whose frontmost is true
|
|
167
|
+
set appName to displayed name of fp
|
|
168
|
+
set sec0 to appName & d & bundle identifier of fp & d
|
|
169
|
+
try
|
|
170
|
+
set sec0 to sec0 & POSIX path of (file of fp as alias)
|
|
171
|
+
end try
|
|
172
|
+
set finderRunning to (exists process "Finder")
|
|
173
|
+
end tell
|
|
174
|
+
|
|
175
|
+
set sec1 to ""
|
|
176
|
+
set sec2 to ""
|
|
177
|
+
set sec3 to ""
|
|
178
|
+
set sec4 to do shell script "echo $HOME/Desktop/"
|
|
179
|
+
|
|
180
|
+
if not finderRunning then
|
|
181
|
+
-- Skip Finder operations if not running
|
|
182
|
+
else
|
|
183
|
+
-- Get Finder selection (must be in separate tell block to avoid scope issues)
|
|
184
|
+
try
|
|
185
|
+
tell application "Finder"
|
|
186
|
+
set sel to selection
|
|
187
|
+
set sc to count of sel
|
|
188
|
+
if sc > 0 then
|
|
189
|
+
set L to {}
|
|
190
|
+
repeat with j from 1 to sc
|
|
191
|
+
if j > 50 then exit repeat
|
|
192
|
+
set f to item j of sel
|
|
193
|
+
set t to "0"
|
|
194
|
+
if class of f as string contains "folder" then set t to "1"
|
|
195
|
+
set end of L to name of f & d & POSIX path of (f as alias) & d & t
|
|
196
|
+
end repeat
|
|
197
|
+
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, i}
|
|
198
|
+
set sec1 to L as string
|
|
199
|
+
set AppleScript's text item delimiters to tid
|
|
200
|
+
end if
|
|
201
|
+
end tell
|
|
202
|
+
end try
|
|
203
|
+
|
|
204
|
+
-- Get Finder windows (including special views like Recents)
|
|
205
|
+
try
|
|
206
|
+
tell application "Finder"
|
|
207
|
+
set wc to count of Finder windows
|
|
208
|
+
if wc > 0 then
|
|
209
|
+
-- Try to get current folder path (may fail for special views)
|
|
210
|
+
try
|
|
211
|
+
set sec2 to POSIX path of (target of front Finder window as alias)
|
|
212
|
+
on error
|
|
213
|
+
-- Special view (Recents, Search, etc.) - use window name as identifier
|
|
214
|
+
set sec2 to "special:" & (name of front Finder window)
|
|
215
|
+
end try
|
|
216
|
+
set L to {}
|
|
217
|
+
repeat with idx from 1 to wc
|
|
218
|
+
set w to Finder window idx
|
|
219
|
+
set wName to name of w
|
|
220
|
+
set wPath to ""
|
|
221
|
+
try
|
|
222
|
+
set wPath to POSIX path of (target of w as alias)
|
|
223
|
+
on error
|
|
224
|
+
-- Special view - mark with special: prefix
|
|
225
|
+
set wPath to "special:" & wName
|
|
226
|
+
end try
|
|
227
|
+
set end of L to wName & d & wPath & d & (idx as string)
|
|
228
|
+
end repeat
|
|
229
|
+
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, i}
|
|
230
|
+
set sec3 to L as string
|
|
231
|
+
set AppleScript's text item delimiters to tid
|
|
232
|
+
end if
|
|
233
|
+
end tell
|
|
234
|
+
end try
|
|
235
|
+
end if
|
|
236
|
+
|
|
237
|
+
set sec5 to ""
|
|
238
|
+
try
|
|
239
|
+
set c to the clipboard as text
|
|
240
|
+
if (length of c) > 2000 then set c to text 1 thru 2000 of c
|
|
241
|
+
set sec5 to c
|
|
242
|
+
end try
|
|
243
|
+
|
|
244
|
+
set sec6 to ""
|
|
245
|
+
try
|
|
246
|
+
set cf to the clipboard as alias list
|
|
247
|
+
set L to {}
|
|
248
|
+
repeat with f in cf
|
|
249
|
+
set end of L to POSIX path of f
|
|
250
|
+
end repeat
|
|
251
|
+
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, i}
|
|
252
|
+
set sec6 to L as string
|
|
253
|
+
set AppleScript's text item delimiters to tid
|
|
254
|
+
end try
|
|
255
|
+
|
|
256
|
+
set sec7 to "0"
|
|
257
|
+
try
|
|
258
|
+
set imgData to the clipboard as \xABclass PNGf\xBB
|
|
259
|
+
set sec7 to "1"
|
|
260
|
+
on error
|
|
261
|
+
try
|
|
262
|
+
set imgData to the clipboard as TIFF picture
|
|
263
|
+
set sec7 to "1"
|
|
264
|
+
end try
|
|
265
|
+
end try
|
|
266
|
+
|
|
267
|
+
return sec0 & s & sec1 & s & sec2 & s & sec3 & s & sec4 & s & sec5 & s & sec6 & s & sec7
|
|
268
|
+
`;
|
|
269
|
+
var SCRIPT_WITH_METADATA = `
|
|
270
|
+
set d to "${FLD}"
|
|
271
|
+
set s to "${SEC}"
|
|
272
|
+
set i to "${ITM}"
|
|
273
|
+
|
|
274
|
+
tell application "System Events"
|
|
275
|
+
set fp to first application process whose frontmost is true
|
|
276
|
+
set appName to displayed name of fp
|
|
277
|
+
set sec0 to appName & d & bundle identifier of fp & d
|
|
278
|
+
try
|
|
279
|
+
set sec0 to sec0 & POSIX path of (file of fp as alias)
|
|
280
|
+
end try
|
|
281
|
+
set finderRunning to (exists process "Finder")
|
|
282
|
+
end tell
|
|
283
|
+
|
|
284
|
+
set sec1 to ""
|
|
285
|
+
set sec2 to ""
|
|
286
|
+
set sec3 to ""
|
|
287
|
+
set sec4 to do shell script "echo $HOME/Desktop/"
|
|
288
|
+
|
|
289
|
+
if not finderRunning then
|
|
290
|
+
-- Skip Finder operations if not running
|
|
291
|
+
else
|
|
292
|
+
-- Get Finder selection with metadata (must be in separate tell block)
|
|
293
|
+
try
|
|
294
|
+
tell application "Finder"
|
|
295
|
+
set sel to selection
|
|
296
|
+
set sc to count of sel
|
|
297
|
+
if sc > 0 then
|
|
298
|
+
set L to {}
|
|
299
|
+
repeat with j from 1 to sc
|
|
300
|
+
if j > 50 then exit repeat
|
|
301
|
+
set f to item j of sel
|
|
302
|
+
set t to "0"
|
|
303
|
+
if class of f as string contains "folder" then set t to "1"
|
|
304
|
+
set fSize to "0"
|
|
305
|
+
set fMod to ""
|
|
306
|
+
try
|
|
307
|
+
set fSize to size of f as string
|
|
308
|
+
end try
|
|
309
|
+
try
|
|
310
|
+
set fMod to (modification date of f) as string
|
|
311
|
+
end try
|
|
312
|
+
set end of L to name of f & d & POSIX path of (f as alias) & d & t & d & fSize & d & fMod
|
|
313
|
+
end repeat
|
|
314
|
+
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, i}
|
|
315
|
+
set sec1 to L as string
|
|
316
|
+
set AppleScript's text item delimiters to tid
|
|
317
|
+
end if
|
|
318
|
+
end tell
|
|
319
|
+
end try
|
|
320
|
+
|
|
321
|
+
-- Get Finder windows (including special views like Recents)
|
|
322
|
+
try
|
|
323
|
+
tell application "Finder"
|
|
324
|
+
set wc to count of Finder windows
|
|
325
|
+
if wc > 0 then
|
|
326
|
+
-- Try to get current folder path (may fail for special views)
|
|
327
|
+
try
|
|
328
|
+
set sec2 to POSIX path of (target of front Finder window as alias)
|
|
329
|
+
on error
|
|
330
|
+
-- Special view (Recents, Search, etc.) - use window name as identifier
|
|
331
|
+
set sec2 to "special:" & (name of front Finder window)
|
|
332
|
+
end try
|
|
333
|
+
set L to {}
|
|
334
|
+
repeat with idx from 1 to wc
|
|
335
|
+
set w to Finder window idx
|
|
336
|
+
set wName to name of w
|
|
337
|
+
set wPath to ""
|
|
338
|
+
try
|
|
339
|
+
set wPath to POSIX path of (target of w as alias)
|
|
340
|
+
on error
|
|
341
|
+
-- Special view - mark with special: prefix
|
|
342
|
+
set wPath to "special:" & wName
|
|
343
|
+
end try
|
|
344
|
+
set end of L to wName & d & wPath & d & (idx as string)
|
|
345
|
+
end repeat
|
|
346
|
+
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, i}
|
|
347
|
+
set sec3 to L as string
|
|
348
|
+
set AppleScript's text item delimiters to tid
|
|
349
|
+
end if
|
|
350
|
+
end tell
|
|
351
|
+
end try
|
|
352
|
+
end if
|
|
353
|
+
|
|
354
|
+
set sec5 to ""
|
|
355
|
+
try
|
|
356
|
+
set c to the clipboard as text
|
|
357
|
+
if (length of c) > 2000 then set c to text 1 thru 2000 of c
|
|
358
|
+
set sec5 to c
|
|
359
|
+
end try
|
|
360
|
+
|
|
361
|
+
set sec6 to ""
|
|
362
|
+
try
|
|
363
|
+
set cf to the clipboard as alias list
|
|
364
|
+
set L to {}
|
|
365
|
+
repeat with f in cf
|
|
366
|
+
set end of L to POSIX path of f
|
|
367
|
+
end repeat
|
|
368
|
+
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, i}
|
|
369
|
+
set sec6 to L as string
|
|
370
|
+
set AppleScript's text item delimiters to tid
|
|
371
|
+
end try
|
|
372
|
+
|
|
373
|
+
set sec7 to "0"
|
|
374
|
+
try
|
|
375
|
+
set imgData to the clipboard as \xABclass PNGf\xBB
|
|
376
|
+
set sec7 to "1"
|
|
377
|
+
on error
|
|
378
|
+
try
|
|
379
|
+
set imgData to the clipboard as TIFF picture
|
|
380
|
+
set sec7 to "1"
|
|
381
|
+
end try
|
|
382
|
+
end try
|
|
383
|
+
|
|
384
|
+
return sec0 & s & sec1 & s & sec2 & s & sec3 & s & sec4 & s & sec5 & s & sec6 & s & sec7
|
|
385
|
+
`;
|
|
386
|
+
function parseBasic(out) {
|
|
387
|
+
const s = out.split(SEC);
|
|
388
|
+
const app = (s[0] ?? "").split(FLD);
|
|
389
|
+
const items = [];
|
|
390
|
+
const paths = [];
|
|
391
|
+
if (s[1]?.trim()) {
|
|
392
|
+
for (const item of s[1].split(ITM)) {
|
|
393
|
+
const p = item.split(FLD);
|
|
394
|
+
if (p.length >= 3) {
|
|
395
|
+
items.push({ name: p[0], path: p[1], isFolder: p[2] === "1" });
|
|
396
|
+
paths.push(p[1]);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const windows = [];
|
|
401
|
+
if (s[3]?.trim()) {
|
|
402
|
+
for (const win of s[3].split(ITM)) {
|
|
403
|
+
const p = win.split(FLD);
|
|
404
|
+
if (p.length >= 3) {
|
|
405
|
+
const isSpecial = p[1].startsWith("special:");
|
|
406
|
+
windows.push({
|
|
407
|
+
name: p[0],
|
|
408
|
+
path: isSpecial ? null : p[1],
|
|
409
|
+
index: parseInt(p[2], 10) || 0,
|
|
410
|
+
isSpecialView: isSpecial
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const currentFolder = s[2]?.trim() || null;
|
|
416
|
+
const finderCurrentFolder = currentFolder?.startsWith("special:") ? null : currentFolder;
|
|
417
|
+
const clipPaths = s[6]?.trim() ? s[6].split(ITM).filter((p) => p.trim()) : [];
|
|
418
|
+
const hasImage = s[7]?.trim() === "1";
|
|
419
|
+
return {
|
|
420
|
+
frontmostApp: {
|
|
421
|
+
name: app[0] ?? "",
|
|
422
|
+
bundleId: app[1] ?? "",
|
|
423
|
+
isFinder: app[1] === "com.apple.finder",
|
|
424
|
+
path: app[2] ?? ""
|
|
425
|
+
},
|
|
426
|
+
finderSelection: { count: items.length, items, paths },
|
|
427
|
+
finderCurrentFolder,
|
|
428
|
+
finderWindows: windows,
|
|
429
|
+
desktopPath: s[4]?.trim() || "",
|
|
430
|
+
clipboard: {
|
|
431
|
+
text: s[5]?.trim() || null,
|
|
432
|
+
hasFiles: clipPaths.length > 0,
|
|
433
|
+
filePaths: clipPaths,
|
|
434
|
+
hasImage
|
|
435
|
+
},
|
|
436
|
+
timestamp: Date.now()
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function parseWithMetadata(out) {
|
|
440
|
+
const s = out.split(SEC);
|
|
441
|
+
const app = (s[0] ?? "").split(FLD);
|
|
442
|
+
const items = [];
|
|
443
|
+
const paths = [];
|
|
444
|
+
if (s[1]?.trim()) {
|
|
445
|
+
for (const item of s[1].split(ITM)) {
|
|
446
|
+
const p = item.split(FLD);
|
|
447
|
+
if (p.length >= 3) {
|
|
448
|
+
const selectedItem = {
|
|
449
|
+
name: p[0],
|
|
450
|
+
path: p[1],
|
|
451
|
+
isFolder: p[2] === "1"
|
|
452
|
+
};
|
|
453
|
+
if (p.length >= 4 && p[3]) {
|
|
454
|
+
selectedItem.size = parseInt(p[3], 10) || 0;
|
|
455
|
+
}
|
|
456
|
+
if (p.length >= 5 && p[4]) {
|
|
457
|
+
selectedItem.modifiedAt = p[4];
|
|
458
|
+
}
|
|
459
|
+
items.push(selectedItem);
|
|
460
|
+
paths.push(p[1]);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const windows = [];
|
|
465
|
+
if (s[3]?.trim()) {
|
|
466
|
+
for (const win of s[3].split(ITM)) {
|
|
467
|
+
const p = win.split(FLD);
|
|
468
|
+
if (p.length >= 3) {
|
|
469
|
+
const isSpecial = p[1].startsWith("special:");
|
|
470
|
+
windows.push({
|
|
471
|
+
name: p[0],
|
|
472
|
+
path: isSpecial ? null : p[1],
|
|
473
|
+
index: parseInt(p[2], 10) || 0,
|
|
474
|
+
isSpecialView: isSpecial
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const currentFolder = s[2]?.trim() || null;
|
|
480
|
+
const finderCurrentFolder = currentFolder?.startsWith("special:") ? null : currentFolder;
|
|
481
|
+
const clipPaths = s[6]?.trim() ? s[6].split(ITM).filter((p) => p.trim()) : [];
|
|
482
|
+
const hasImage = s[7]?.trim() === "1";
|
|
483
|
+
return {
|
|
484
|
+
frontmostApp: {
|
|
485
|
+
name: app[0] ?? "",
|
|
486
|
+
bundleId: app[1] ?? "",
|
|
487
|
+
isFinder: app[1] === "com.apple.finder",
|
|
488
|
+
path: app[2] ?? ""
|
|
489
|
+
},
|
|
490
|
+
finderSelection: { count: items.length, items, paths },
|
|
491
|
+
finderCurrentFolder,
|
|
492
|
+
finderWindows: windows,
|
|
493
|
+
desktopPath: s[4]?.trim() || "",
|
|
494
|
+
clipboard: {
|
|
495
|
+
text: s[5]?.trim() || null,
|
|
496
|
+
hasFiles: clipPaths.length > 0,
|
|
497
|
+
filePaths: clipPaths,
|
|
498
|
+
hasImage
|
|
499
|
+
},
|
|
500
|
+
timestamp: Date.now()
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
async function getAgentContext(opts) {
|
|
504
|
+
if (!isOsascriptAvailable()) return null;
|
|
505
|
+
const script = opts?.includeMetadata ? SCRIPT_WITH_METADATA : SCRIPT_BASIC;
|
|
506
|
+
const parser = opts?.includeMetadata ? parseWithMetadata : parseBasic;
|
|
507
|
+
const r = await executeAndParse(script, parser, opts);
|
|
508
|
+
return r.success ? r.data ?? null : null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/osascript/scripts/finderContext.ts
|
|
512
|
+
var SEC2 = "\xA7\xA7\xA7";
|
|
513
|
+
var FLD2 = "|||";
|
|
514
|
+
var ITM2 = ":::";
|
|
515
|
+
var SCRIPT = `
|
|
516
|
+
set d to "${FLD2}"
|
|
517
|
+
set s to "${SEC2}"
|
|
518
|
+
set i to "${ITM2}"
|
|
519
|
+
|
|
520
|
+
-- Check if Finder is frontmost
|
|
521
|
+
set isFinderFront to "0"
|
|
522
|
+
tell application "System Events"
|
|
523
|
+
set finderRunning to (exists process "Finder")
|
|
524
|
+
if finderRunning then
|
|
525
|
+
try
|
|
526
|
+
set frontApp to bundle identifier of (first application process whose frontmost is true)
|
|
527
|
+
if frontApp is "com.apple.finder" then
|
|
528
|
+
set isFinderFront to "1"
|
|
529
|
+
end if
|
|
530
|
+
end try
|
|
531
|
+
end if
|
|
532
|
+
end tell
|
|
533
|
+
|
|
534
|
+
set sec0 to isFinderFront
|
|
535
|
+
set sec1 to ""
|
|
536
|
+
set sec2 to ""
|
|
537
|
+
|
|
538
|
+
if not finderRunning then
|
|
539
|
+
return sec0 & s & sec1 & s & sec2
|
|
540
|
+
end if
|
|
541
|
+
|
|
542
|
+
tell application "Finder"
|
|
543
|
+
-- Get all Finder windows
|
|
544
|
+
set wc to count of Finder windows
|
|
545
|
+
if wc > 0 then
|
|
546
|
+
set L to {}
|
|
547
|
+
repeat with idx from 1 to wc
|
|
548
|
+
set w to Finder window idx
|
|
549
|
+
set wName to name of w
|
|
550
|
+
set wPath to ""
|
|
551
|
+
try
|
|
552
|
+
set wPath to POSIX path of (target of w as alias)
|
|
553
|
+
on error
|
|
554
|
+
-- Special view (Recents, AirDrop, Search, Tags, etc.)
|
|
555
|
+
set wPath to "special:" & wName
|
|
556
|
+
end try
|
|
557
|
+
set end of L to wName & d & wPath & d & (idx as string)
|
|
558
|
+
end repeat
|
|
559
|
+
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, i}
|
|
560
|
+
set sec1 to L as string
|
|
561
|
+
set AppleScript's text item delimiters to tid
|
|
562
|
+
end if
|
|
563
|
+
|
|
564
|
+
-- Get selection only if Finder is frontmost
|
|
565
|
+
if isFinderFront is "1" then
|
|
566
|
+
set sel to selection
|
|
567
|
+
set sc to count of sel
|
|
568
|
+
if sc > 0 then
|
|
569
|
+
set L to {}
|
|
570
|
+
repeat with j from 1 to sc
|
|
571
|
+
if j > 100 then exit repeat
|
|
572
|
+
set f to item j of sel
|
|
573
|
+
try
|
|
574
|
+
set end of L to POSIX path of (f as alias)
|
|
575
|
+
end try
|
|
576
|
+
end repeat
|
|
577
|
+
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, i}
|
|
578
|
+
set sec2 to L as string
|
|
579
|
+
set AppleScript's text item delimiters to tid
|
|
580
|
+
end if
|
|
581
|
+
end if
|
|
582
|
+
end tell
|
|
583
|
+
|
|
584
|
+
return sec0 & s & sec1 & s & sec2
|
|
585
|
+
`;
|
|
586
|
+
function parseOutput(out) {
|
|
587
|
+
const sections = out.split(SEC2);
|
|
588
|
+
const isFinderFrontmost = sections[0]?.trim() === "1";
|
|
589
|
+
const windows = [];
|
|
590
|
+
if (sections[1]?.trim()) {
|
|
591
|
+
for (const win of sections[1].split(ITM2)) {
|
|
592
|
+
const parts = win.split(FLD2);
|
|
593
|
+
if (parts.length >= 3) {
|
|
594
|
+
const rawPath = parts[1] ?? "";
|
|
595
|
+
const isSpecial = rawPath.startsWith("special:");
|
|
596
|
+
windows.push({
|
|
597
|
+
name: parts[0] ?? "",
|
|
598
|
+
path: isSpecial ? null : rawPath,
|
|
599
|
+
index: parseInt(parts[2] ?? "0", 10),
|
|
600
|
+
isSpecialView: isSpecial
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const selectedPaths = [];
|
|
606
|
+
if (sections[2]?.trim()) {
|
|
607
|
+
for (const p of sections[2].split(ITM2)) {
|
|
608
|
+
if (p.trim()) {
|
|
609
|
+
selectedPaths.push(p);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
let frontWindow = null;
|
|
614
|
+
if (isFinderFrontmost && windows.length > 0) {
|
|
615
|
+
const first = windows[0];
|
|
616
|
+
frontWindow = {
|
|
617
|
+
name: first.name,
|
|
618
|
+
path: first.path,
|
|
619
|
+
isSpecialView: first.isSpecialView
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
windows,
|
|
624
|
+
frontWindow,
|
|
625
|
+
selectedPaths,
|
|
626
|
+
isFinderFrontmost,
|
|
627
|
+
windowCount: windows.length
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
async function getFinderContext(options) {
|
|
631
|
+
if (!isOsascriptAvailable()) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
const result = await executeAndParse(SCRIPT, parseOutput, options);
|
|
635
|
+
return result.success ? result.data ?? null : null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/osascript/scripts/frontmostApp.ts
|
|
639
|
+
var DELIMITER = "|||";
|
|
640
|
+
var SCRIPT2 = `
|
|
641
|
+
tell application "System Events"
|
|
642
|
+
set frontApp to first application process whose frontmost is true
|
|
643
|
+
set appName to displayed name of frontApp
|
|
644
|
+
set appId to bundle identifier of frontApp
|
|
645
|
+
try
|
|
646
|
+
set appPath to POSIX path of (file of frontApp as alias)
|
|
647
|
+
on error
|
|
648
|
+
set appPath to ""
|
|
649
|
+
end try
|
|
650
|
+
return appName & "${DELIMITER}" & appId & "${DELIMITER}" & appPath
|
|
651
|
+
end tell
|
|
652
|
+
`;
|
|
653
|
+
function parseOutput2(output) {
|
|
654
|
+
const parts = output.split(DELIMITER);
|
|
655
|
+
const name = parts[0] ?? "";
|
|
656
|
+
const bundleId = parts[1] ?? "";
|
|
657
|
+
const path2 = parts[2] ?? "";
|
|
658
|
+
return {
|
|
659
|
+
name,
|
|
660
|
+
bundleId,
|
|
661
|
+
isFinder: bundleId === "com.apple.finder",
|
|
662
|
+
path: path2
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
async function getFrontmostApp(options) {
|
|
666
|
+
if (!isOsascriptAvailable()) {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
const result = await executeAndParse(SCRIPT2, parseOutput2, options);
|
|
670
|
+
if (result.success && result.data) {
|
|
671
|
+
return result.data;
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/osascript/scripts/finderSelection.ts
|
|
677
|
+
var FIELD_DELIMITER = "|||";
|
|
678
|
+
var ITEM_DELIMITER = ":::";
|
|
679
|
+
var SCRIPT3 = `
|
|
680
|
+
tell application "System Events"
|
|
681
|
+
set finderRunning to (exists process "Finder")
|
|
682
|
+
end tell
|
|
683
|
+
|
|
684
|
+
if finderRunning is false then
|
|
685
|
+
return ""
|
|
686
|
+
end if
|
|
687
|
+
|
|
688
|
+
tell application "Finder"
|
|
689
|
+
set sel to selection
|
|
690
|
+
if (count of sel) = 0 then
|
|
691
|
+
return ""
|
|
692
|
+
end if
|
|
693
|
+
set output to ""
|
|
694
|
+
repeat with f in sel
|
|
695
|
+
set p to POSIX path of (f as alias)
|
|
696
|
+
set n to name of f
|
|
697
|
+
set k to class of f as string
|
|
698
|
+
if output is not "" then
|
|
699
|
+
set output to output & "${ITEM_DELIMITER}"
|
|
700
|
+
end if
|
|
701
|
+
set output to output & p & "${FIELD_DELIMITER}" & n & "${FIELD_DELIMITER}" & k
|
|
702
|
+
end repeat
|
|
703
|
+
return output
|
|
704
|
+
end tell
|
|
705
|
+
`;
|
|
706
|
+
function parseOutput3(output) {
|
|
707
|
+
if (!output || output.trim() === "") {
|
|
708
|
+
return {
|
|
709
|
+
paths: [],
|
|
710
|
+
count: 0,
|
|
711
|
+
items: []
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const itemStrings = output.split(ITEM_DELIMITER);
|
|
715
|
+
const items = [];
|
|
716
|
+
const paths = [];
|
|
717
|
+
for (const itemStr of itemStrings) {
|
|
718
|
+
const parts = itemStr.split(FIELD_DELIMITER);
|
|
719
|
+
if (parts.length >= 2) {
|
|
720
|
+
const path2 = parts[0] ?? "";
|
|
721
|
+
const name = parts[1] ?? "";
|
|
722
|
+
const classType = parts[2] ?? "";
|
|
723
|
+
const isFolder = classType.toLowerCase().includes("folder");
|
|
724
|
+
paths.push(path2);
|
|
725
|
+
items.push({
|
|
726
|
+
name,
|
|
727
|
+
path: path2,
|
|
728
|
+
isFolder
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
paths,
|
|
734
|
+
count: items.length,
|
|
735
|
+
items
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
async function getFinderSelection(options) {
|
|
739
|
+
if (!isOsascriptAvailable()) {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
const result = await executeAndParse(SCRIPT3, parseOutput3, options);
|
|
743
|
+
if (result.success && result.data) {
|
|
744
|
+
return result.data;
|
|
745
|
+
}
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
var CURRENT_FOLDER_SCRIPT = `
|
|
749
|
+
tell application "System Events"
|
|
750
|
+
set finderRunning to (exists process "Finder")
|
|
751
|
+
end tell
|
|
752
|
+
|
|
753
|
+
if finderRunning is false then
|
|
754
|
+
return ""
|
|
755
|
+
end if
|
|
756
|
+
|
|
757
|
+
tell application "Finder"
|
|
758
|
+
try
|
|
759
|
+
set frontWindow to front Finder window
|
|
760
|
+
set folderPath to POSIX path of (target of frontWindow as alias)
|
|
761
|
+
return folderPath
|
|
762
|
+
on error
|
|
763
|
+
return ""
|
|
764
|
+
end try
|
|
765
|
+
end tell
|
|
766
|
+
`;
|
|
767
|
+
async function getFinderCurrentFolder(options) {
|
|
768
|
+
if (!isOsascriptAvailable()) {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
const result = await executeAndParse(
|
|
772
|
+
CURRENT_FOLDER_SCRIPT,
|
|
773
|
+
(output) => output.trim() || null,
|
|
774
|
+
options
|
|
775
|
+
);
|
|
776
|
+
if (result.success) {
|
|
777
|
+
return result.data ?? null;
|
|
778
|
+
}
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/osascript/scripts/finderWindows.ts
|
|
783
|
+
var FIELD_DELIMITER2 = "|||";
|
|
784
|
+
var ITEM_DELIMITER2 = ":::";
|
|
785
|
+
var SCRIPT4 = `
|
|
786
|
+
tell application "System Events"
|
|
787
|
+
set finderRunning to (exists process "Finder")
|
|
788
|
+
end tell
|
|
789
|
+
|
|
790
|
+
if finderRunning is false then
|
|
791
|
+
return ""
|
|
792
|
+
end if
|
|
793
|
+
|
|
794
|
+
tell application "Finder"
|
|
795
|
+
set windowList to every Finder window
|
|
796
|
+
if (count of windowList) = 0 then
|
|
797
|
+
return ""
|
|
798
|
+
end if
|
|
799
|
+
set output to ""
|
|
800
|
+
set idx to 1
|
|
801
|
+
repeat with w in windowList
|
|
802
|
+
set windowName to name of w
|
|
803
|
+
set windowPath to ""
|
|
804
|
+
try
|
|
805
|
+
set windowPath to POSIX path of (target of w as alias)
|
|
806
|
+
on error
|
|
807
|
+
-- Special view (Recents, Search, etc.) - mark with special: prefix
|
|
808
|
+
set windowPath to "special:" & windowName
|
|
809
|
+
end try
|
|
810
|
+
if output is not "" then
|
|
811
|
+
set output to output & "${ITEM_DELIMITER2}"
|
|
812
|
+
end if
|
|
813
|
+
set output to output & windowName & "${FIELD_DELIMITER2}" & windowPath & "${FIELD_DELIMITER2}" & idx
|
|
814
|
+
set idx to idx + 1
|
|
815
|
+
end repeat
|
|
816
|
+
return output
|
|
817
|
+
end tell
|
|
818
|
+
`;
|
|
819
|
+
function parseOutput4(output) {
|
|
820
|
+
if (!output || output.trim() === "") {
|
|
821
|
+
return [];
|
|
822
|
+
}
|
|
823
|
+
const itemStrings = output.split(ITEM_DELIMITER2);
|
|
824
|
+
const windows = [];
|
|
825
|
+
for (const itemStr of itemStrings) {
|
|
826
|
+
const parts = itemStr.split(FIELD_DELIMITER2);
|
|
827
|
+
if (parts.length >= 3) {
|
|
828
|
+
const pathValue = parts[1] ?? "";
|
|
829
|
+
const isSpecial = pathValue.startsWith("special:");
|
|
830
|
+
windows.push({
|
|
831
|
+
name: parts[0] ?? "",
|
|
832
|
+
path: isSpecial ? null : pathValue,
|
|
833
|
+
index: parseInt(parts[2] ?? "0", 10),
|
|
834
|
+
isSpecialView: isSpecial
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return windows;
|
|
839
|
+
}
|
|
840
|
+
async function getFinderWindows(options) {
|
|
841
|
+
if (!isOsascriptAvailable()) {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
const result = await executeAndParse(SCRIPT4, parseOutput4, options);
|
|
845
|
+
if (result.success && result.data) {
|
|
846
|
+
return result.data;
|
|
847
|
+
}
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/osascript/scripts/fileMetadata.ts
|
|
852
|
+
var DELIMITER2 = "|||";
|
|
853
|
+
function buildScript(filePath) {
|
|
854
|
+
const escapedPath = escapeForAppleScript(filePath);
|
|
855
|
+
return `
|
|
856
|
+
tell application "System Events"
|
|
857
|
+
set f to disk item (POSIX file "${escapedPath}")
|
|
858
|
+
set n to name of f
|
|
859
|
+
set p to "${escapedPath}"
|
|
860
|
+
try
|
|
861
|
+
set s to size of f
|
|
862
|
+
on error
|
|
863
|
+
set s to 0
|
|
864
|
+
end try
|
|
865
|
+
set isDir to (class of f is folder) as string
|
|
866
|
+
try
|
|
867
|
+
set c to creation date of f as string
|
|
868
|
+
on error
|
|
869
|
+
set c to ""
|
|
870
|
+
end try
|
|
871
|
+
try
|
|
872
|
+
set m to modification date of f as string
|
|
873
|
+
on error
|
|
874
|
+
set m to ""
|
|
875
|
+
end try
|
|
876
|
+
try
|
|
877
|
+
set ext to name extension of f
|
|
878
|
+
on error
|
|
879
|
+
set ext to ""
|
|
880
|
+
end try
|
|
881
|
+
try
|
|
882
|
+
set k to kind of f
|
|
883
|
+
on error
|
|
884
|
+
set k to ""
|
|
885
|
+
end try
|
|
886
|
+
return n & "${DELIMITER2}" & p & "${DELIMITER2}" & s & "${DELIMITER2}" & isDir & "${DELIMITER2}" & c & "${DELIMITER2}" & m & "${DELIMITER2}" & ext & "${DELIMITER2}" & k
|
|
887
|
+
end tell
|
|
888
|
+
`;
|
|
889
|
+
}
|
|
890
|
+
function parseOutput5(output) {
|
|
891
|
+
const parts = output.split(DELIMITER2);
|
|
892
|
+
return {
|
|
893
|
+
name: parts[0] ?? "",
|
|
894
|
+
path: parts[1] ?? "",
|
|
895
|
+
size: parseInt(parts[2] ?? "0", 10) || 0,
|
|
896
|
+
isFolder: (parts[3] ?? "").toLowerCase() === "true",
|
|
897
|
+
createdAt: parts[4] ?? "",
|
|
898
|
+
modifiedAt: parts[5] ?? "",
|
|
899
|
+
extension: parts[6] ?? "",
|
|
900
|
+
kind: parts[7] ?? ""
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
async function getFileMetadata(filePath, options) {
|
|
904
|
+
if (!isOsascriptAvailable()) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
const script = buildScript(filePath);
|
|
908
|
+
const result = await executeAndParse(script, parseOutput5, options);
|
|
909
|
+
if (result.success && result.data) {
|
|
910
|
+
return result.data;
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
async function getFilesMetadata(filePaths, options) {
|
|
915
|
+
if (!isOsascriptAvailable()) {
|
|
916
|
+
return filePaths.map(() => null);
|
|
917
|
+
}
|
|
918
|
+
const results = await Promise.all(
|
|
919
|
+
filePaths.map((path2) => getFileMetadata(path2, options))
|
|
920
|
+
);
|
|
921
|
+
return results;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/osascript/scripts/clipboard.ts
|
|
925
|
+
var DELIMITER3 = "|||";
|
|
926
|
+
var FILE_DELIMITER = ":::";
|
|
927
|
+
var TEXT_SCRIPT = `
|
|
928
|
+
try
|
|
929
|
+
set clipText to the clipboard as text
|
|
930
|
+
return clipText
|
|
931
|
+
on error
|
|
932
|
+
return ""
|
|
933
|
+
end try
|
|
934
|
+
`;
|
|
935
|
+
var FILES_SCRIPT = `
|
|
936
|
+
try
|
|
937
|
+
set clipFiles to the clipboard as alias list
|
|
938
|
+
set output to ""
|
|
939
|
+
repeat with f in clipFiles
|
|
940
|
+
set p to POSIX path of (f as alias)
|
|
941
|
+
if output is not "" then
|
|
942
|
+
set output to output & "${FILE_DELIMITER}"
|
|
943
|
+
end if
|
|
944
|
+
set output to output & p
|
|
945
|
+
end repeat
|
|
946
|
+
if output is "" then
|
|
947
|
+
return "NOFILES${DELIMITER3}"
|
|
948
|
+
end if
|
|
949
|
+
return "FILES${DELIMITER3}" & output
|
|
950
|
+
on error
|
|
951
|
+
return "NOFILES${DELIMITER3}"
|
|
952
|
+
end try
|
|
953
|
+
`;
|
|
954
|
+
var IMAGE_SCRIPT = `
|
|
955
|
+
try
|
|
956
|
+
set imgData to the clipboard as \xABclass PNGf\xBB
|
|
957
|
+
return "1"
|
|
958
|
+
on error
|
|
959
|
+
try
|
|
960
|
+
set imgData to the clipboard as TIFF picture
|
|
961
|
+
return "1"
|
|
962
|
+
on error
|
|
963
|
+
return "0"
|
|
964
|
+
end try
|
|
965
|
+
end try
|
|
966
|
+
`;
|
|
967
|
+
function parseFilesOutput(output) {
|
|
968
|
+
if (output.startsWith("FILES" + DELIMITER3)) {
|
|
969
|
+
const pathsPart = output.substring(("FILES" + DELIMITER3).length);
|
|
970
|
+
if (pathsPart.trim()) {
|
|
971
|
+
const paths = pathsPart.split(FILE_DELIMITER).filter((p) => p.trim());
|
|
972
|
+
return { hasFiles: paths.length > 0, filePaths: paths };
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return { hasFiles: false, filePaths: [] };
|
|
976
|
+
}
|
|
977
|
+
async function getClipboardContent(options) {
|
|
978
|
+
if (!isOsascriptAvailable()) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
const [textResult, filesResult, imageResult] = await Promise.all([
|
|
982
|
+
executeAndParse(TEXT_SCRIPT, (output) => output || null, options),
|
|
983
|
+
executeAndParse(FILES_SCRIPT, parseFilesOutput, options),
|
|
984
|
+
executeAndParse(IMAGE_SCRIPT, (output) => output.trim() === "1", options)
|
|
985
|
+
]);
|
|
986
|
+
const text = textResult.success ? textResult.data : null;
|
|
987
|
+
const filesData = filesResult.success && filesResult.data ? filesResult.data : { hasFiles: false, filePaths: [] };
|
|
988
|
+
const hasImage = imageResult.success ? imageResult.data ?? false : false;
|
|
989
|
+
return {
|
|
990
|
+
text: text ?? null,
|
|
991
|
+
hasFiles: filesData.hasFiles,
|
|
992
|
+
filePaths: filesData.filePaths,
|
|
993
|
+
hasImage
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
async function getClipboardText(options) {
|
|
997
|
+
if (!isOsascriptAvailable()) {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
const result = await executeAndParse(TEXT_SCRIPT, (output) => output || null, options);
|
|
1001
|
+
if (result.success) {
|
|
1002
|
+
return result.data ?? null;
|
|
1003
|
+
}
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/osascript/scripts/desktopItems.ts
|
|
1008
|
+
var FIELD_DELIMITER3 = "|||";
|
|
1009
|
+
var ITEM_DELIMITER3 = ":::";
|
|
1010
|
+
var SCRIPT5 = `
|
|
1011
|
+
tell application "System Events"
|
|
1012
|
+
set finderRunning to (exists process "Finder")
|
|
1013
|
+
end tell
|
|
1014
|
+
|
|
1015
|
+
if finderRunning is false then
|
|
1016
|
+
return ""
|
|
1017
|
+
end if
|
|
1018
|
+
|
|
1019
|
+
tell application "Finder"
|
|
1020
|
+
set desktopItems to every item of desktop
|
|
1021
|
+
if (count of desktopItems) = 0 then
|
|
1022
|
+
return ""
|
|
1023
|
+
end if
|
|
1024
|
+
set output to ""
|
|
1025
|
+
repeat with f in desktopItems
|
|
1026
|
+
set n to name of f
|
|
1027
|
+
set p to POSIX path of (f as alias)
|
|
1028
|
+
set k to class of f as string
|
|
1029
|
+
if output is not "" then
|
|
1030
|
+
set output to output & "${ITEM_DELIMITER3}"
|
|
1031
|
+
end if
|
|
1032
|
+
set output to output & n & "${FIELD_DELIMITER3}" & p & "${FIELD_DELIMITER3}" & k
|
|
1033
|
+
end repeat
|
|
1034
|
+
return output
|
|
1035
|
+
end tell
|
|
1036
|
+
`;
|
|
1037
|
+
function parseOutput6(output) {
|
|
1038
|
+
if (!output || output.trim() === "") {
|
|
1039
|
+
return [];
|
|
1040
|
+
}
|
|
1041
|
+
const itemStrings = output.split(ITEM_DELIMITER3);
|
|
1042
|
+
const items = [];
|
|
1043
|
+
for (const itemStr of itemStrings) {
|
|
1044
|
+
const parts = itemStr.split(FIELD_DELIMITER3);
|
|
1045
|
+
if (parts.length >= 2) {
|
|
1046
|
+
const name = parts[0] ?? "";
|
|
1047
|
+
const path2 = parts[1] ?? "";
|
|
1048
|
+
const classType = parts[2] ?? "";
|
|
1049
|
+
items.push({
|
|
1050
|
+
name,
|
|
1051
|
+
path: path2,
|
|
1052
|
+
isFolder: classType.toLowerCase().includes("folder")
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return items;
|
|
1057
|
+
}
|
|
1058
|
+
async function getDesktopItems(options) {
|
|
1059
|
+
if (!isOsascriptAvailable()) {
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
const result = await executeAndParse(SCRIPT5, parseOutput6, options);
|
|
1063
|
+
if (result.success && result.data) {
|
|
1064
|
+
return result.data;
|
|
1065
|
+
}
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
async function getDesktopPath(options) {
|
|
1069
|
+
if (!isOsascriptAvailable()) {
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
const script = `try
|
|
1073
|
+
return do shell script "echo $HOME/Desktop/"
|
|
1074
|
+
on error
|
|
1075
|
+
return ""
|
|
1076
|
+
end try`;
|
|
1077
|
+
const result = await executeAndParse(script, (output) => output.trim() || null, options);
|
|
1078
|
+
if (result.success) {
|
|
1079
|
+
return result.data ?? null;
|
|
1080
|
+
}
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// src/osascript/scripts/recentFiles.ts
|
|
1085
|
+
var FIELD_DELIMITER4 = "|||";
|
|
1086
|
+
var ITEM_DELIMITER4 = ":::";
|
|
1087
|
+
function buildScript2(limit) {
|
|
1088
|
+
return `
|
|
1089
|
+
tell application "System Events"
|
|
1090
|
+
set finderRunning to (exists process "Finder")
|
|
1091
|
+
end tell
|
|
1092
|
+
|
|
1093
|
+
if finderRunning is false then
|
|
1094
|
+
return ""
|
|
1095
|
+
end if
|
|
1096
|
+
|
|
1097
|
+
tell application "Finder"
|
|
1098
|
+
try
|
|
1099
|
+
set recentFolder to folder "Recents" of home
|
|
1100
|
+
set recentItems to every item of recentFolder
|
|
1101
|
+
set itemCount to count of recentItems
|
|
1102
|
+
if itemCount > ${limit} then
|
|
1103
|
+
set itemCount to ${limit}
|
|
1104
|
+
end if
|
|
1105
|
+
if itemCount = 0 then
|
|
1106
|
+
return ""
|
|
1107
|
+
end if
|
|
1108
|
+
set output to ""
|
|
1109
|
+
repeat with i from 1 to itemCount
|
|
1110
|
+
set f to item i of recentItems
|
|
1111
|
+
try
|
|
1112
|
+
set n to name of f
|
|
1113
|
+
set p to POSIX path of (f as alias)
|
|
1114
|
+
set m to modification date of f as string
|
|
1115
|
+
if output is not "" then
|
|
1116
|
+
set output to output & "${ITEM_DELIMITER4}"
|
|
1117
|
+
end if
|
|
1118
|
+
set output to output & n & "${FIELD_DELIMITER4}" & p & "${FIELD_DELIMITER4}" & m
|
|
1119
|
+
end try
|
|
1120
|
+
end repeat
|
|
1121
|
+
return output
|
|
1122
|
+
on error
|
|
1123
|
+
return ""
|
|
1124
|
+
end try
|
|
1125
|
+
end tell
|
|
1126
|
+
`;
|
|
1127
|
+
}
|
|
1128
|
+
var RECENT_DOCUMENTS_SCRIPT = `
|
|
1129
|
+
try
|
|
1130
|
+
set recentApps to paragraphs of (do shell script "mdfind -onlyin ~ 'kMDItemLastUsedDate >= $time.today(-7)' | head -50")
|
|
1131
|
+
set output to ""
|
|
1132
|
+
repeat with filePath in recentApps
|
|
1133
|
+
if filePath is not "" then
|
|
1134
|
+
try
|
|
1135
|
+
tell application "System Events"
|
|
1136
|
+
set f to disk item (POSIX file filePath)
|
|
1137
|
+
set n to name of f
|
|
1138
|
+
set m to modification date of f as string
|
|
1139
|
+
end tell
|
|
1140
|
+
if output is not "" then
|
|
1141
|
+
set output to output & "${ITEM_DELIMITER4}"
|
|
1142
|
+
end if
|
|
1143
|
+
set output to output & n & "${FIELD_DELIMITER4}" & filePath & "${FIELD_DELIMITER4}" & m
|
|
1144
|
+
end try
|
|
1145
|
+
end if
|
|
1146
|
+
end repeat
|
|
1147
|
+
return output
|
|
1148
|
+
on error
|
|
1149
|
+
return ""
|
|
1150
|
+
end try
|
|
1151
|
+
`;
|
|
1152
|
+
function parseOutput7(output) {
|
|
1153
|
+
if (!output || output.trim() === "") {
|
|
1154
|
+
return [];
|
|
1155
|
+
}
|
|
1156
|
+
const itemStrings = output.split(ITEM_DELIMITER4);
|
|
1157
|
+
const items = [];
|
|
1158
|
+
for (const itemStr of itemStrings) {
|
|
1159
|
+
const parts = itemStr.split(FIELD_DELIMITER4);
|
|
1160
|
+
if (parts.length >= 2) {
|
|
1161
|
+
items.push({
|
|
1162
|
+
name: parts[0] ?? "",
|
|
1163
|
+
path: parts[1] ?? "",
|
|
1164
|
+
accessedAt: parts[2] ?? ""
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return items;
|
|
1169
|
+
}
|
|
1170
|
+
async function getRecentFiles(limit = 20, options) {
|
|
1171
|
+
if (!isOsascriptAvailable()) {
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
const script = buildScript2(limit);
|
|
1175
|
+
const result = await executeAndParse(script, parseOutput7, options);
|
|
1176
|
+
if (result.success && result.data) {
|
|
1177
|
+
return result.data;
|
|
1178
|
+
}
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
async function getRecentDocuments(options) {
|
|
1182
|
+
if (!isOsascriptAvailable()) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
const result = await executeAndParse(RECENT_DOCUMENTS_SCRIPT, parseOutput7, options);
|
|
1186
|
+
if (result.success && result.data) {
|
|
1187
|
+
return result.data;
|
|
1188
|
+
}
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// src/osascript/scripts/runningApps.ts
|
|
1193
|
+
var FIELD_DELIMITER5 = "|||";
|
|
1194
|
+
var ITEM_DELIMITER5 = ":::";
|
|
1195
|
+
var SCRIPT6 = `
|
|
1196
|
+
tell application "System Events"
|
|
1197
|
+
set appList to every application process
|
|
1198
|
+
set output to ""
|
|
1199
|
+
repeat with proc in appList
|
|
1200
|
+
try
|
|
1201
|
+
set appName to name of proc
|
|
1202
|
+
set appId to bundle identifier of proc
|
|
1203
|
+
set isHidden to not (visible of proc)
|
|
1204
|
+
set isFront to frontmost of proc
|
|
1205
|
+
if output is not "" then
|
|
1206
|
+
set output to output & "${ITEM_DELIMITER5}"
|
|
1207
|
+
end if
|
|
1208
|
+
set output to output & appName & "${FIELD_DELIMITER5}" & appId & "${FIELD_DELIMITER5}" & (isHidden as string) & "${FIELD_DELIMITER5}" & (isFront as string)
|
|
1209
|
+
end try
|
|
1210
|
+
end repeat
|
|
1211
|
+
return output
|
|
1212
|
+
end tell
|
|
1213
|
+
`;
|
|
1214
|
+
function parseOutput8(output) {
|
|
1215
|
+
if (!output || output.trim() === "") {
|
|
1216
|
+
return [];
|
|
1217
|
+
}
|
|
1218
|
+
const itemStrings = output.split(ITEM_DELIMITER5);
|
|
1219
|
+
const apps = [];
|
|
1220
|
+
for (const itemStr of itemStrings) {
|
|
1221
|
+
const parts = itemStr.split(FIELD_DELIMITER5);
|
|
1222
|
+
if (parts.length >= 4) {
|
|
1223
|
+
apps.push({
|
|
1224
|
+
name: parts[0] ?? "",
|
|
1225
|
+
bundleId: parts[1] ?? "",
|
|
1226
|
+
isHidden: (parts[2] ?? "").toLowerCase() === "true",
|
|
1227
|
+
isFrontmost: (parts[3] ?? "").toLowerCase() === "true"
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return apps;
|
|
1232
|
+
}
|
|
1233
|
+
async function getRunningApps(options) {
|
|
1234
|
+
if (!isOsascriptAvailable()) {
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
const result = await executeAndParse(SCRIPT6, parseOutput8, options);
|
|
1238
|
+
if (result.success && result.data) {
|
|
1239
|
+
return result.data;
|
|
1240
|
+
}
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
async function isAppRunning(appName, options) {
|
|
1244
|
+
const apps = await getRunningApps(options);
|
|
1245
|
+
if (!apps) {
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
const lowerName = appName.toLowerCase();
|
|
1249
|
+
return apps.some(
|
|
1250
|
+
(app) => app.name.toLowerCase() === lowerName || app.bundleId.toLowerCase() === lowerName
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
async function getFrontmostFromRunning(options) {
|
|
1254
|
+
const apps = await getRunningApps(options);
|
|
1255
|
+
if (!apps) {
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
return apps.find((app) => app.isFrontmost) ?? null;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/osascript/scripts/fileContent.ts
|
|
1262
|
+
var DELIMITER4 = "\xA7\xA7\xA7";
|
|
1263
|
+
async function readFileContent(filePath, opts) {
|
|
1264
|
+
if (!isOsascriptAvailable()) return null;
|
|
1265
|
+
const maxBytes = opts?.maxBytes ?? 1048576;
|
|
1266
|
+
const encoding = opts?.encoding ?? "utf8";
|
|
1267
|
+
const script = `
|
|
1268
|
+
set filePath to "${filePath.replace(/"/g, '\\"')}"
|
|
1269
|
+
set maxSize to ${maxBytes}
|
|
1270
|
+
set d to "${DELIMITER4}"
|
|
1271
|
+
|
|
1272
|
+
try
|
|
1273
|
+
set fileRef to POSIX file filePath
|
|
1274
|
+
|
|
1275
|
+
-- Get file size first
|
|
1276
|
+
tell application "System Events"
|
|
1277
|
+
set fileSize to size of (disk item filePath)
|
|
1278
|
+
end tell
|
|
1279
|
+
|
|
1280
|
+
-- Check size limit
|
|
1281
|
+
if fileSize > maxSize then
|
|
1282
|
+
return "ERROR" & d & "File too large: " & fileSize & " bytes (max: " & maxSize & ")"
|
|
1283
|
+
end if
|
|
1284
|
+
|
|
1285
|
+
-- Read file content
|
|
1286
|
+
set fileContent to read fileRef as \xABclass utf8\xBB
|
|
1287
|
+
|
|
1288
|
+
return "OK" & d & fileSize & d & fileContent
|
|
1289
|
+
on error errMsg
|
|
1290
|
+
return "ERROR" & d & errMsg
|
|
1291
|
+
end try
|
|
1292
|
+
`;
|
|
1293
|
+
const parser = (out) => {
|
|
1294
|
+
const parts = out.split(DELIMITER4);
|
|
1295
|
+
const status = parts[0]?.trim();
|
|
1296
|
+
if (status === "ERROR") {
|
|
1297
|
+
return {
|
|
1298
|
+
path: filePath,
|
|
1299
|
+
content: "",
|
|
1300
|
+
size: 0,
|
|
1301
|
+
success: false,
|
|
1302
|
+
error: parts[1]?.trim() || "Unknown error"
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
return {
|
|
1306
|
+
path: filePath,
|
|
1307
|
+
content: parts.slice(2).join(DELIMITER4),
|
|
1308
|
+
// Content may contain delimiter
|
|
1309
|
+
size: parseInt(parts[1] ?? "0", 10),
|
|
1310
|
+
success: true
|
|
1311
|
+
};
|
|
1312
|
+
};
|
|
1313
|
+
const r = await executeAndParse(script, parser, opts);
|
|
1314
|
+
return r.success ? r.data ?? null : null;
|
|
1315
|
+
}
|
|
1316
|
+
async function readMultipleFiles(filePaths, opts) {
|
|
1317
|
+
if (!isOsascriptAvailable()) return [];
|
|
1318
|
+
const results = await Promise.all(
|
|
1319
|
+
filePaths.map((path2) => readFileContent(path2, opts))
|
|
1320
|
+
);
|
|
1321
|
+
return results.filter((r) => r !== null);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// src/osascript/index.ts
|
|
1325
|
+
async function getSystemContext(options) {
|
|
1326
|
+
if (!isOsascriptAvailable()) {
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
const frontmostApp = await getFrontmostApp(options);
|
|
1330
|
+
if (!frontmostApp) {
|
|
1331
|
+
return null;
|
|
1332
|
+
}
|
|
1333
|
+
let finderSelection = null;
|
|
1334
|
+
let finderCurrentFolder = null;
|
|
1335
|
+
if (frontmostApp.isFinder) {
|
|
1336
|
+
[finderSelection, finderCurrentFolder] = await Promise.all([
|
|
1337
|
+
getFinderSelection(options),
|
|
1338
|
+
getFinderCurrentFolder(options)
|
|
1339
|
+
]);
|
|
1340
|
+
}
|
|
1341
|
+
const clipboard = await getClipboardContent(options);
|
|
1342
|
+
return {
|
|
1343
|
+
frontmostApp,
|
|
1344
|
+
finderSelection,
|
|
1345
|
+
finderCurrentFolder,
|
|
1346
|
+
clipboard: clipboard ?? { text: null, hasFiles: false, filePaths: [], hasImage: false }
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// src/index.ts
|
|
1351
|
+
var IS_MACOS2 = process.platform === "darwin";
|
|
15
1352
|
var IS_WINDOWS = process.platform === "win32";
|
|
16
1353
|
var PLATFORM_PACKAGES = {
|
|
17
1354
|
"darwin-arm64": "@simen-keyboard-listener/darwin-arm64",
|
|
@@ -48,7 +1385,7 @@ function getNativeAddon() {
|
|
|
48
1385
|
try {
|
|
49
1386
|
const candidate = localRequire(packageName);
|
|
50
1387
|
const hasCoreFunctions = candidate && typeof candidate.start === "function" && typeof candidate.stop === "function" && typeof candidate.isRunning === "function" && typeof candidate.checkPermission === "function";
|
|
51
|
-
const hasMacOSFunctions =
|
|
1388
|
+
const hasMacOSFunctions = IS_MACOS2 ? typeof candidate.getFocusedInputValue === "function" && typeof candidate.getFocusedInputSelectedText === "function" : true;
|
|
52
1389
|
if (hasCoreFunctions && hasMacOSFunctions) {
|
|
53
1390
|
nativeAddon = candidate;
|
|
54
1391
|
return nativeAddon;
|
|
@@ -165,7 +1502,7 @@ var NativeKeyboardListener = class _NativeKeyboardListener {
|
|
|
165
1502
|
}
|
|
166
1503
|
};
|
|
167
1504
|
function getGlobalKeyboardListener() {
|
|
168
|
-
if (!
|
|
1505
|
+
if (!IS_MACOS2 && !IS_WINDOWS) {
|
|
169
1506
|
throw new Error(`Unsupported platform for global keyboard listener: ${process.platform}`);
|
|
170
1507
|
}
|
|
171
1508
|
return NativeKeyboardListener.getInstance();
|
|
@@ -174,7 +1511,7 @@ function createGlobalKeyboardListener() {
|
|
|
174
1511
|
return getGlobalKeyboardListener();
|
|
175
1512
|
}
|
|
176
1513
|
function checkKeyboardPermission() {
|
|
177
|
-
if (!
|
|
1514
|
+
if (!IS_MACOS2 && !IS_WINDOWS) {
|
|
178
1515
|
return false;
|
|
179
1516
|
}
|
|
180
1517
|
try {
|
|
@@ -186,7 +1523,7 @@ function checkKeyboardPermission() {
|
|
|
186
1523
|
}
|
|
187
1524
|
var lastMacAccessibilitySettingsOpenTs = 0;
|
|
188
1525
|
function openMacAccessibilitySettings() {
|
|
189
|
-
if (!
|
|
1526
|
+
if (!IS_MACOS2) {
|
|
190
1527
|
return;
|
|
191
1528
|
}
|
|
192
1529
|
const now = Date.now();
|
|
@@ -212,7 +1549,7 @@ function ensureAccessibilityPermission(addon) {
|
|
|
212
1549
|
return false;
|
|
213
1550
|
}
|
|
214
1551
|
function getFocusedInputValue() {
|
|
215
|
-
if (!
|
|
1552
|
+
if (!IS_MACOS2) {
|
|
216
1553
|
return null;
|
|
217
1554
|
}
|
|
218
1555
|
const addon = getNativeAddon();
|
|
@@ -226,7 +1563,7 @@ function getFocusedInputValue() {
|
|
|
226
1563
|
}
|
|
227
1564
|
}
|
|
228
1565
|
function getFocusedInputSelectedText() {
|
|
229
|
-
if (!
|
|
1566
|
+
if (!IS_MACOS2) {
|
|
230
1567
|
return null;
|
|
231
1568
|
}
|
|
232
1569
|
const addon = getNativeAddon();
|
|
@@ -240,7 +1577,7 @@ function getFocusedInputSelectedText() {
|
|
|
240
1577
|
}
|
|
241
1578
|
}
|
|
242
1579
|
function getContextJSON() {
|
|
243
|
-
if (!
|
|
1580
|
+
if (!IS_MACOS2) return null;
|
|
244
1581
|
const addon = getNativeAddon();
|
|
245
1582
|
if (!ensureAccessibilityPermission(addon)) return null;
|
|
246
1583
|
try {
|
|
@@ -250,7 +1587,7 @@ function getContextJSON() {
|
|
|
250
1587
|
}
|
|
251
1588
|
}
|
|
252
1589
|
function getSelectedTextSmart() {
|
|
253
|
-
if (!
|
|
1590
|
+
if (!IS_MACOS2) return null;
|
|
254
1591
|
const addon = getNativeAddon();
|
|
255
1592
|
if (!ensureAccessibilityPermission(addon)) return null;
|
|
256
1593
|
try {
|
|
@@ -272,10 +1609,36 @@ function setBlockSystemHotkeys(block) {
|
|
|
272
1609
|
export {
|
|
273
1610
|
checkKeyboardPermission,
|
|
274
1611
|
createGlobalKeyboardListener,
|
|
1612
|
+
escapeForAppleScript,
|
|
1613
|
+
executeAndParse,
|
|
1614
|
+
executeAppleScript,
|
|
1615
|
+
executeAppleScriptLines,
|
|
1616
|
+
executeMultilineAndParse,
|
|
1617
|
+
getAgentContext,
|
|
1618
|
+
getClipboardContent,
|
|
1619
|
+
getClipboardText,
|
|
275
1620
|
getContextJSON,
|
|
1621
|
+
getDesktopItems,
|
|
1622
|
+
getDesktopPath,
|
|
1623
|
+
getFileMetadata,
|
|
1624
|
+
getFilesMetadata,
|
|
1625
|
+
getFinderContext,
|
|
1626
|
+
getFinderCurrentFolder,
|
|
1627
|
+
getFinderSelection,
|
|
1628
|
+
getFinderWindows,
|
|
276
1629
|
getFocusedInputSelectedText,
|
|
277
1630
|
getFocusedInputValue,
|
|
1631
|
+
getFrontmostApp,
|
|
1632
|
+
getFrontmostFromRunning,
|
|
278
1633
|
getGlobalKeyboardListener,
|
|
1634
|
+
getRecentDocuments,
|
|
1635
|
+
getRecentFiles,
|
|
1636
|
+
getRunningApps,
|
|
279
1637
|
getSelectedTextSmart,
|
|
1638
|
+
getSystemContext,
|
|
1639
|
+
isAppRunning,
|
|
1640
|
+
isOsascriptAvailable,
|
|
1641
|
+
readFileContent,
|
|
1642
|
+
readMultipleFiles,
|
|
280
1643
|
setBlockSystemHotkeys
|
|
281
1644
|
};
|