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 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
+ [![Test](https://github.com/mherod/applescript-node/actions/workflows/test.yml/badge.svg)](https://github.com/mherod/applescript-node/actions/workflows/test.yml)
4
+ [![npm version](https://badge.fury.io/js/applescript-node.svg)](https://badge.fury.io/js/applescript-node)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0%2B-blue)](https://www.typescriptlang.org/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](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;