applescript-node 1.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.
- package/LICENSE +21 -0
- package/README.md +441 -0
- package/dist/builder-utils.d.ts +19 -0
- package/dist/builder.d.ts +725 -0
- package/dist/compiler.d.ts +15 -0
- package/dist/decompiler.d.ts +6 -0
- package/dist/executor.d.ts +97 -0
- package/dist/expressions.d.ts +252 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +3220 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3195 -0
- package/dist/index.mjs.map +1 -0
- package/dist/languages.d.ts +61 -0
- package/dist/loader.d.ts +24 -0
- package/dist/sdef.d.ts +50 -0
- package/dist/sources/applications.d.ts +102 -0
- package/dist/sources/index.d.ts +29 -0
- package/dist/sources/system.d.ts +178 -0
- package/dist/sources/windows.d.ts +61 -0
- package/dist/types.d.ts +787 -0
- package/dist/validator.d.ts +106 -0
- package/package.json +130 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Matthew Herod
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# applescript-node
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mherod/applescript-node/actions/workflows/test.yml)
|
|
4
|
+
[](https://badge.fury.io/js/applescript-node)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://mherod.github.io/applescript-node/)
|
|
8
|
+
|
|
9
|
+
**Type-safe macOS automation from Node.js.** Control apps, manage windows, and automate workflows with a fluent API.
|
|
10
|
+
|
|
11
|
+
📖 **[Read the full documentation →](https://mherod.github.io/applescript-node/)**
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { sources } from 'applescript-node';
|
|
15
|
+
|
|
16
|
+
// Get all open windows across apps
|
|
17
|
+
const windows = await sources.windows.getAll();
|
|
18
|
+
console.log(`Found ${windows.length} open windows`);
|
|
19
|
+
|
|
20
|
+
// Get the frontmost app
|
|
21
|
+
const frontmost = await sources.applications.getFrontmost();
|
|
22
|
+
console.log(`Active: ${frontmost.name}`);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install applescript-node
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Requirements:** macOS 10.10+, Node.js 20+
|
|
32
|
+
|
|
33
|
+
## Quick Examples
|
|
34
|
+
|
|
35
|
+
### Get System Info
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { sources } from 'applescript-node';
|
|
39
|
+
|
|
40
|
+
const info = await sources.system.getInfo();
|
|
41
|
+
console.log(`${info.computerName} running macOS ${info.osVersion}`);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### List Running Apps
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { sources } from 'applescript-node';
|
|
48
|
+
|
|
49
|
+
const apps = await sources.applications.getAll();
|
|
50
|
+
apps.forEach((app) => {
|
|
51
|
+
console.log(`${app.name} - ${app.windowCount} windows (PID: ${app.pid})`);
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Control Applications
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { sources } from 'applescript-node';
|
|
59
|
+
|
|
60
|
+
// Activate an app (bring to front)
|
|
61
|
+
await sources.applications.activate('Finder');
|
|
62
|
+
|
|
63
|
+
// Check if running
|
|
64
|
+
const isRunning = await sources.applications.isRunning('Safari');
|
|
65
|
+
|
|
66
|
+
// Quit an app
|
|
67
|
+
await sources.applications.quit('TextEdit');
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Window Management
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { sources } from 'applescript-node';
|
|
74
|
+
|
|
75
|
+
// Get windows for a specific app
|
|
76
|
+
const safariWindows = await sources.windows.getByApp('Safari');
|
|
77
|
+
|
|
78
|
+
// Get the frontmost window
|
|
79
|
+
const active = await sources.windows.getFrontmost();
|
|
80
|
+
console.log(`Active: ${active?.name} (${active?.app})`);
|
|
81
|
+
|
|
82
|
+
// Get window counts per app
|
|
83
|
+
const counts = await sources.windows.getCountByApp();
|
|
84
|
+
// { "Finder": 3, "Safari": 2, ... }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Builder API
|
|
90
|
+
|
|
91
|
+
For custom automation scripts, use the fluent builder:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { createScript, runScript } from 'applescript-node';
|
|
95
|
+
|
|
96
|
+
const script = createScript().tellApp('Finder', (finder) => finder.get('name of every disk'));
|
|
97
|
+
|
|
98
|
+
const result = await runScript(script);
|
|
99
|
+
if (result.success) {
|
|
100
|
+
console.log('Disks:', result.output);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Keyboard Automation
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const script = createScript().tellApp(
|
|
108
|
+
'System Events',
|
|
109
|
+
(app) =>
|
|
110
|
+
app
|
|
111
|
+
.keystroke('n', ['command']) // Cmd+N
|
|
112
|
+
.delay(0.5)
|
|
113
|
+
.keystroke('Hello World!')
|
|
114
|
+
.keystroke('s', ['command']), // Cmd+S
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
await runScript(script);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Conditional Logic
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const script = createScript()
|
|
124
|
+
.set('temp', 75)
|
|
125
|
+
.ifThenElse(
|
|
126
|
+
(e) => e.gt('temp', 80),
|
|
127
|
+
(then_) => then_.displayDialog('Hot!'),
|
|
128
|
+
(else_) => else_.displayDialog('Nice weather'),
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Error Handling
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const script = createScript().tryCatch(
|
|
136
|
+
(try_) => try_.tellApp('Notes', (notes) => notes.raw('get name of first note')),
|
|
137
|
+
(catch_) => catch_.displayDialog('Could not access Notes'),
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Loops
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const script = createScript()
|
|
145
|
+
.set('results', [])
|
|
146
|
+
.forEach('item', '{1, 2, 3, 4, 5}', (loop) => loop.setEndRaw('results', 'item * 2'));
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Data Extraction
|
|
152
|
+
|
|
153
|
+
### Extract to JSON
|
|
154
|
+
|
|
155
|
+
The `mapToJson()` method makes data extraction simple:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { createScript, runScript } from 'applescript-node';
|
|
159
|
+
|
|
160
|
+
const script = createScript()
|
|
161
|
+
.tell('Notes')
|
|
162
|
+
.mapToJson(
|
|
163
|
+
'aNote',
|
|
164
|
+
'every note',
|
|
165
|
+
{
|
|
166
|
+
id: 'id',
|
|
167
|
+
name: 'name',
|
|
168
|
+
content: 'plaintext',
|
|
169
|
+
created: 'creation date of aNote as string',
|
|
170
|
+
},
|
|
171
|
+
{ limit: 10, skipErrors: true },
|
|
172
|
+
)
|
|
173
|
+
.endtell();
|
|
174
|
+
|
|
175
|
+
const result = await runScript(script);
|
|
176
|
+
const notes = JSON.parse(result.output);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Handle Optional Fields
|
|
180
|
+
|
|
181
|
+
Use `PropertyExtractor` for fields that might not exist:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const script = createScript()
|
|
185
|
+
.tell('Contacts')
|
|
186
|
+
.mapToJson(
|
|
187
|
+
'person',
|
|
188
|
+
'every person',
|
|
189
|
+
{
|
|
190
|
+
// Simple properties
|
|
191
|
+
id: 'id',
|
|
192
|
+
name: 'name',
|
|
193
|
+
|
|
194
|
+
// Get first email (multi-value field)
|
|
195
|
+
email: {
|
|
196
|
+
property: (e) => e.property('person', 'emails'),
|
|
197
|
+
firstOf: true,
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
// Optional field with type conversion
|
|
201
|
+
birthday: {
|
|
202
|
+
property: 'birth date',
|
|
203
|
+
ifExists: true,
|
|
204
|
+
asType: 'string',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{ limit: 50, skipErrors: true },
|
|
208
|
+
)
|
|
209
|
+
.endtell();
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Type Safety
|
|
215
|
+
|
|
216
|
+
### Typed Results
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
interface DiskInfo {
|
|
220
|
+
name: string;
|
|
221
|
+
capacity: number;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const result = await runScript<DiskInfo[]>(
|
|
225
|
+
'tell application "Finder" to get {name, capacity} of every disk',
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (result.success) {
|
|
229
|
+
result.output.forEach((disk) => {
|
|
230
|
+
console.log(`${disk.name}: ${disk.capacity} bytes`);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### ExprBuilder for Conditions
|
|
236
|
+
|
|
237
|
+
Type-safe condition building with autocomplete:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { createScript } from 'applescript-node';
|
|
241
|
+
|
|
242
|
+
const script = createScript()
|
|
243
|
+
.set('count', 10)
|
|
244
|
+
.ifThen(
|
|
245
|
+
(e) => e.and(e.gt('count', 5), e.lt('count', 20)),
|
|
246
|
+
(then_) => then_.displayDialog('In range!'),
|
|
247
|
+
);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Available operators:**
|
|
251
|
+
|
|
252
|
+
- Comparison: `gt`, `lt`, `gte`, `lte`, `eq`, `ne`
|
|
253
|
+
- Logical: `and`, `or`, `not`
|
|
254
|
+
- String: `contains`, `startsWith`, `endsWith`, `length`
|
|
255
|
+
- Objects: `exists`, `count`, `property`
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Script Compilation
|
|
260
|
+
|
|
261
|
+
Compile scripts to `.scpt` files or stay-open applications:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { compileScript } from 'applescript-node';
|
|
265
|
+
|
|
266
|
+
// Compile a stay-open app
|
|
267
|
+
await compileScript(
|
|
268
|
+
`
|
|
269
|
+
on idle
|
|
270
|
+
display notification "Still running!"
|
|
271
|
+
return 60
|
|
272
|
+
end idle
|
|
273
|
+
`,
|
|
274
|
+
{
|
|
275
|
+
outputPath: 'MyApp.app',
|
|
276
|
+
stayOpen: true,
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## App Introspection
|
|
284
|
+
|
|
285
|
+
Discover what commands an app supports:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { getApplicationDictionary, findCommand } from 'applescript-node';
|
|
289
|
+
|
|
290
|
+
const dict = await getApplicationDictionary('/System/Applications/Messages.app');
|
|
291
|
+
|
|
292
|
+
// Find a specific command
|
|
293
|
+
const sendCmd = findCommand(dict, 'send');
|
|
294
|
+
if (sendCmd) {
|
|
295
|
+
console.log(
|
|
296
|
+
'Parameters:',
|
|
297
|
+
sendCmd.parameters.map((p) => p.name),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// List all available commands
|
|
302
|
+
const commands = getAllCommands(dict);
|
|
303
|
+
console.log(`Messages.app has ${commands.length} commands`);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Validation
|
|
309
|
+
|
|
310
|
+
Validate scripts before running:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { ScriptValidator, createScript } from 'applescript-node';
|
|
314
|
+
|
|
315
|
+
const validator = await ScriptValidator.forApplication('/System/Applications/Messages.app');
|
|
316
|
+
|
|
317
|
+
const script = createScript()
|
|
318
|
+
.tell('Messages')
|
|
319
|
+
.raw('sen "Hello"') // Typo!
|
|
320
|
+
.end();
|
|
321
|
+
|
|
322
|
+
const result = validator.validate(script.build());
|
|
323
|
+
if (!result.valid) {
|
|
324
|
+
result.errors.forEach((err) => {
|
|
325
|
+
console.log(`Error: ${err.message}`);
|
|
326
|
+
if (err.suggestion) {
|
|
327
|
+
console.log(` Did you mean: ${err.suggestion}?`);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## API Reference
|
|
336
|
+
|
|
337
|
+
### High-Level Sources
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { sources } from 'applescript-node';
|
|
341
|
+
|
|
342
|
+
// System
|
|
343
|
+
sources.system.getInfo();
|
|
344
|
+
|
|
345
|
+
// Applications
|
|
346
|
+
sources.applications.getAll();
|
|
347
|
+
sources.applications.getFrontmost();
|
|
348
|
+
sources.applications.getByName(name);
|
|
349
|
+
sources.applications.isRunning(name);
|
|
350
|
+
sources.applications.activate(name);
|
|
351
|
+
sources.applications.hide(name);
|
|
352
|
+
sources.applications.quit(name);
|
|
353
|
+
|
|
354
|
+
// Windows
|
|
355
|
+
sources.windows.getAll();
|
|
356
|
+
sources.windows.getByApp(appName);
|
|
357
|
+
sources.windows.getFrontmost();
|
|
358
|
+
sources.windows.getCountByApp();
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Script Execution
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { runScript, runScriptFile, createScript } from 'applescript-node';
|
|
365
|
+
|
|
366
|
+
// Run a string
|
|
367
|
+
const result = await runScript('tell app "Finder" to activate');
|
|
368
|
+
|
|
369
|
+
// Run from file
|
|
370
|
+
const result = await runScriptFile('./my-script.applescript');
|
|
371
|
+
|
|
372
|
+
// Run a builder
|
|
373
|
+
const script = createScript().tell('Finder').activate().end();
|
|
374
|
+
const result = await runScript(script);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Builder Methods
|
|
378
|
+
|
|
379
|
+
| Category | Methods |
|
|
380
|
+
| ------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
381
|
+
| **Blocks** | `tell`, `tellApp`, `tellProcess`, `end`, `if`, `then`, `else`, `elseIf`, `repeat`, `repeatWith`, `forEach`, `try`, `onError` |
|
|
382
|
+
| **Apps** | `activate`, `quit`, `launch`, `running` |
|
|
383
|
+
| **Windows** | `closeWindow`, `minimizeWindow`, `zoomWindow`, `moveWindow`, `resizeWindow` |
|
|
384
|
+
| **UI** | `click`, `keystroke`, `delay`, `pressKey`, `displayDialog`, `displayNotification` |
|
|
385
|
+
| **Variables** | `set`, `setExpression`, `get`, `copy`, `count`, `exists` |
|
|
386
|
+
| **Data** | `mapToJson`, `setEndRecord`, `pickEndRecord`, `returnAsJson` |
|
|
387
|
+
| **Utility** | `raw`, `build`, `reset` |
|
|
388
|
+
|
|
389
|
+
### Execution Options
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
const result = await runScript(script, {
|
|
393
|
+
language: 'AppleScript', // or 'JavaScript'
|
|
394
|
+
humanReadable: true, // Format output
|
|
395
|
+
errorToStdout: false, // Redirect errors
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Examples
|
|
402
|
+
|
|
403
|
+
Run the included examples:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
# Basics
|
|
407
|
+
pnpm run example:basic
|
|
408
|
+
pnpm run example:builder
|
|
409
|
+
|
|
410
|
+
# Data extraction
|
|
411
|
+
pnpm run example:messages
|
|
412
|
+
pnpm run example:contacts
|
|
413
|
+
|
|
414
|
+
# Advanced
|
|
415
|
+
pnpm run example:sdef
|
|
416
|
+
pnpm run example:validation
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Development
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
# Setup
|
|
425
|
+
git clone https://github.com/mherod/applescript-node.git
|
|
426
|
+
cd applescript-node
|
|
427
|
+
pnpm install
|
|
428
|
+
|
|
429
|
+
# Commands
|
|
430
|
+
pnpm build # Build
|
|
431
|
+
pnpm test # Run tests
|
|
432
|
+
pnpm test:watch # Watch mode
|
|
433
|
+
pnpm lint # Lint
|
|
434
|
+
pnpm examples # Run all examples
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## License
|
|
440
|
+
|
|
441
|
+
MIT © [mherod](https://github.com/mherod)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ExprBuilder } from './expressions.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves an expression that can be either a string or a function that takes an ExprBuilder.
|
|
4
|
+
* This is a common pattern throughout the builder for accepting flexible expression inputs.
|
|
5
|
+
*
|
|
6
|
+
* @template TScope - Union of variable names available in the current scope (for type-safe ExprBuilder)
|
|
7
|
+
* @param expression - Either a string expression or a function that builds an expression using ExprBuilder
|
|
8
|
+
* @returns The resolved string expression
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // String expression
|
|
13
|
+
* resolveExpression('counter > 10') // => 'counter > 10'
|
|
14
|
+
*
|
|
15
|
+
* // Function expression
|
|
16
|
+
* resolveExpression((e) => e.gt('counter', 10)) // => 'counter > 10'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveExpression<TScope extends string = never>(expression: string | ((expr: ExprBuilder<TScope>) => string)): string;
|