shortcutkit 0.8.1 → 1.0.0

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/README.md CHANGED
@@ -1,274 +1,148 @@
1
1
  # shortcutkit
2
2
 
3
- Build, validate and sign Apple Shortcuts (`.shortcut`) files from TypeScript or Python.
3
+ Build, validate, and sign Apple Shortcuts (`.shortcut`) files from TypeScript or Python.
4
4
 
5
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
  [![CI](https://img.shields.io/github/actions/workflow/status/frontboat/shortcutkit/ci.yml?branch=main)](https://github.com/frontboat/shortcutkit/actions)
7
+ [![npm](https://img.shields.io/npm/v/shortcutkit)](https://www.npmjs.com/package/shortcutkit)
8
+ [![PyPI](https://img.shields.io/pypi/v/shortcutkit)](https://pypi.org/project/shortcutkit/)
7
9
 
8
- ## About
9
-
10
- Apple has never documented the `.shortcut` format or what its actions accept. shortcutkit
11
- does not guess at either. The tools in this repository load WorkflowKit and ActionKit, the private
12
- frameworks behind the Shortcuts app, and ask their own classes to describe and serialize
13
- every built-in action. Apple's own apps' actions come from the Shortcuts app's action index,
14
- the only readable copy of the App Intents registry. Every action identifier, parameter key, value encoding, icon colour, and even the
15
- UTF-16 offset rule for embedded references came out of the engine, not from a person reading
16
- files. The package cannot disagree with the app.
17
-
18
- That turns an undocumented format into a typed one. In TypeScript, a wrong parameter key or a
19
- string where a boolean belongs is a compile error. In Python the same checks run when you call
20
- `action()`. A file built from plain code signs, imports and runs.
21
-
22
- The data stays current the same way it was made. After a macOS update, one command re-extracts
23
- everything in about a minute and prints a diff of what Apple changed, along with the semver
24
- bump it deserves. `data/provenance.json` records which macOS and Shortcuts build produced the
25
- files you are using.
26
-
27
- ## Features
28
-
29
- - **Every built-in action, typed.** `actions.*` lists all 434 identifiers, with each action's
30
- name, description, summary, output and parameter keys as hover documentation.
31
- - **Apple's apps too.** 1,441 App Intents actions from 77 Apple apps and system components,
32
- Create Reminder, Create Note, Send Message, the System Settings toggles, under the keys the
33
- Shortcuts app itself assigns them: `actions.reminders_create_reminder`. Enumeration cases are
34
- typed, and the `AppIntentDescriptor` the file needs is added for you.
35
- - **Parameters checked before you run anything.** `{ WFStoredContentGlobalValue: "yes" }` is a
36
- compile error; a switch takes a boolean or a reference. Enumeration choices are suggested.
37
- A plain value slot also accepts a reference where the engine reads one, and the type says
38
- which: a Filter action's sort key takes none, a variable picker takes any except Ask, and the
39
- engine was asked about every key (`PARAM_VARIABLE_TYPES`). Every action has a named
40
- parameter type (`ShowresultParams`, `RemindersCreateReminderParams`), and its output content
41
- types are on `ACTIONS[id].outputTypes` and on the returned `Action`.
42
- - **Value helpers that match the engine's serialization.** `ref()` to another action's output,
43
- `variable()`, `shortcutInput()`, `clipboard()`, `currentDate()`, `ask()`, `deviceDetails()`,
44
- `currentApp()`, `text()` for strings with embedded references. A bare `ref()` is what every
45
- picker parameter takes; for a text parameter it is wrapped into the token string the engine
46
- writes, and for an If subject into the form the app writes.
47
- - **Control flow.** `if()` / `otherwise()` / `endIf()`, `repeatEach()` / `endRepeatEach()`,
48
- `repeatCount()` / `endRepeatCount()` and `chooseFromMenu()` / `menuItem()` / `endMenu()`
49
- manage the grouping identifiers for you.
50
- - **App Intents from installed apps.** Any identifier outside the built-in set is accepted;
51
- built-in identifiers and their parameter keys are also validated at run time.
52
- - **Definitions and provenance.** `getAction(id)` describes any identifier, built-in or App
53
- Intent: name, parameter keys and kinds, output, descriptor. `getDefinition(id)` returns the
54
- engine's full record for a built-in (icon, keywords, parameter classes, required resources);
55
- `provenance` says which build produced the data.
56
- - **Signing.** `Shortcut.sign()` wraps `shortcuts sign` so the output imports on any device.
57
-
58
- ## How it compares
59
-
60
- Every earlier library for writing Shortcuts as code kept its action list by hand, and that is
61
- how the two most used ones ended: [shortcuts-js](https://github.com/joshfarrant/shortcuts-js)
62
- (TypeScript, 129 actions, archived 2023) and
63
- [python-shortcuts](https://github.com/alexander-akhmetov/python-shortcuts) (Python, archived
64
- 2024) both stopped once keeping up with Apple became a chore.
65
- [Cherri](https://github.com/electrikmilk/cherri) is the active project in this space and takes
66
- a different shape: a programming language with its own compiler, editor extension and package
67
- manager, whose actions are defined in that language.
68
-
69
- shortcutkit is a library, not a language. You write TypeScript or Python you already know, and
70
- the action catalogue comes from the Shortcuts engine rather than from a maintainer. All 434
71
- built-in actions and 1,441 Apple App Intents are covered today, and the next macOS update is
72
- one `bun run extract` away.
73
-
74
- ## Installation
10
+ A shortcut that asks your name and says hello:
75
11
 
76
- ```bash
77
- npm install shortcutkit # or: bun add shortcutkit
78
- ```
12
+ ```ts
13
+ import { Shortcut, actions, ref, text } from "shortcutkit";
79
14
 
80
- Python:
15
+ const s = new Shortcut("Greeting");
16
+ const name = s.action(actions.ask, { WFAskActionPrompt: "What's your name?" });
17
+ s.action(actions.showresult, { Text: text("Hello, ", ref(name), "!") });
81
18
 
82
- ```bash
83
- pip install shortcutkit
19
+ await s.write(); // Greeting.shortcut
20
+ Shortcut.sign("Greeting.shortcut", "Greeting-signed.shortcut");
84
21
  ```
85
22
 
86
- ### Requirements
23
+ Open `Greeting-signed.shortcut` and Shortcuts adds it to your library. Shortcuts only imports
24
+ signed files, and signing needs a Mac with an iCloud login; everything else runs on any OS.
87
25
 
88
- - Node 20 or newer, or Bun, for the TypeScript package. The published package is plain ES
89
- module JavaScript with type declarations. Python 3.9 or newer for the Python package.
90
- - A Mac signed into iCloud for `sign()`. It runs `shortcuts sign`, which refuses to work
91
- without an iCloud login even in `anyone` mode, so it cannot run on CI runners. `write()`
92
- converts the plist to binary with `plutil` when present and otherwise leaves the XML form.
93
- Building, validating and writing the unsigned file works anywhere.
26
+ ## Why
94
27
 
95
- ## Usage
28
+ The action catalog is extracted from the Shortcuts app's own frameworks, not written by hand,
29
+ and one command refreshes it after a macOS update. Earlier libraries kept hand-written lists and
30
+ are archived ([shortcuts-js](https://github.com/joshfarrant/shortcuts-js),
31
+ [python-shortcuts](https://github.com/alexander-akhmetov/python-shortcuts));
32
+ [Cherri](https://github.com/electrikmilk/cherri) is a separate language with its own compiler.
96
33
 
97
- ```ts
98
- import { Shortcut, actions, ref, text, shortcutInput } from "shortcutkit";
34
+ - All 434 built-in actions, typed, with documentation on hover.
35
+ - 1,449 actions from Apple's apps: Reminders, Notes, Messages, Settings, and 73 more.
36
+ - Third-party app actions, given their descriptor.
37
+ - If, Repeat, Repeat with Each, and Choose from Menu blocks.
38
+ - A wrong key or value fails at compile time in TypeScript, and at `action()` in Python.
99
39
 
100
- const s = new Shortcut("Greeting", { color: "Teal" });
101
- const got = s.action(actions.getstoredcontent, { WFStoredContentKey: "greeting" });
102
- s.action(actions.showresult, { Text: text("Stored: ", ref(got)) });
103
- await s.write(); // Greeting.shortcut
104
- Shortcut.sign("Greeting.shortcut", "Greeting-signed.shortcut");
40
+ ## Install
41
+
42
+ ```bash
43
+ npm install shortcutkit # Node 20+ or Bun
44
+ pip install shortcutkit # Python 3.9+
105
45
  ```
106
46
 
107
- Open the signed file and Shortcuts imports it.
47
+ ## Usage
48
+
49
+ These examples build on the one at the top.
108
50
 
109
51
  ### Share sheet
110
52
 
111
- A shortcut offered from the share sheet declares its surfaces and the input it accepts, and
112
- reads that input with `shortcutInput()`:
113
-
114
53
  ```ts
54
+ import { shortcutInput } from "shortcutkit";
55
+
115
56
  const s = new Shortcut("Page Links", { types: ["ActionExtension"], inputClasses: ["WFSafariWebPageContentItem"] });
116
57
  const links = s.action(actions.runjavascriptonwebpage, { WFInput: shortcutInput(), WFJavaScript: "completion(document.links.length)" });
117
58
  s.action(actions.showresult, { Text: text("Links: ", ref(links)) });
118
59
  ```
119
60
 
120
- `types` takes `ActionExtension` (share sheet), `QuickActions` (Finder and Services menus),
121
- `MenuBar`, `NCWidget`, `WatchKit`, `Sleep` and `ReceivesOnScreenContent`.
122
-
123
61
  ### Control flow
124
62
 
125
- `if()` returns a grouping identifier that the matching `otherwise()` and `endIf()` calls take
126
- back:
127
-
128
63
  ```ts
129
- const gid = s.if(ref(got), "has_any_value");
130
- s.action(actions.showresult, { Text: text("Stored value: ", ref(got)) });
64
+ const gid = s.if(ref(name), "has_any_value");
65
+ s.action(actions.showresult, { Text: text("Hello, ", ref(name)) });
131
66
  s.otherwise(gid);
132
- s.action(actions.showresult, { Text: text("Nothing stored") });
67
+ s.action(actions.showresult, { Text: text("Hello, stranger") });
133
68
  s.endIf(gid);
134
69
  ```
135
70
 
136
71
  ### Apple's apps
137
72
 
138
- Apple's App Intents actions are in the catalogue under the Shortcuts app's own keys. Their
139
- parameters are typed like the built-ins, and the `AppIntentDescriptor` every such action
140
- carries in the file is added automatically:
141
-
142
73
  ```ts
143
74
  const r = s.action(actions.reminders_create_reminder, { title: text("Buy milk"), priorityLevel: "high" });
144
75
  s.action(actions.notes_create_note, { name: "Shopping", content: text("Added ", ref(r)) });
145
76
  ```
146
77
 
147
- ### Actions from other apps
78
+ ### Other apps
148
79
 
149
- Third-party App Intents actions are not in the catalogue, so pass the identifier as a string
150
- and the descriptor yourself. Parameter keys are then typed as `Record<string, Value>` and
151
- passed through as given:
80
+ Pass the identifier and descriptor yourself; the parameters are untyped.
152
81
 
153
82
  ```ts
154
- s.action("com.example.app.CreateNote", { title: text("Hello"), body: ref(got) });
83
+ s.action("com.example.app.CreateNoteIntent", {
84
+ AppIntentDescriptor: {
85
+ BundleIdentifier: "com.example.app", Name: "Example", TeamIdentifier: "ABCDE12345",
86
+ AppIntentIdentifier: "CreateNoteIntent", ActionRequiresAppInstallation: true,
87
+ },
88
+ title: text("Hello"),
89
+ body: ref(name),
90
+ });
155
91
  ```
156
92
 
157
93
  ### Python
158
94
 
159
- Same API and same bundled data, with snake_case names and run-time value checks instead of
160
- compile-time ones:
95
+ Same API in snake_case. See [`python/README.md`](python/README.md).
161
96
 
162
97
  ```python
163
98
  from shortcutkit import Shortcut, actions, ref, text
164
99
 
165
- s = Shortcut("Greeting", color="Teal")
166
- got = s.action(actions.GETSTOREDCONTENT, WFStoredContentKey="greeting")
167
- s.action(actions.SHOWRESULT, Text=text("Stored: ", ref(got)))
100
+ s = Shortcut("Greeting")
101
+ name = s.action(actions.ASK, WFAskActionPrompt="What's your name?")
102
+ s.action(actions.SHOWRESULT, Text=text("Hello, ", ref(name), "!"))
103
+
168
104
  s.write()
169
105
  Shortcut.sign("Greeting.shortcut", "Greeting-signed.shortcut")
170
106
  ```
171
107
 
172
- See [`python/README.md`](python/README.md) for the full Python surface.
173
-
174
- ### Demo
175
-
176
- Both packages ship a demo that builds a working shortcut with storage, an If/Otherwise block
177
- and output:
178
-
179
- ```bash
180
- bun run demo out.shortcut
181
- python -m shortcutkit demo out.shortcut
182
- ```
183
-
184
- ## Documentation
185
-
186
- - [`docs/shortcut-file-format.md`](docs/shortcut-file-format.md): the `.shortcut` format end
187
- to end, every field.
188
- - [`docs/builtin-actions-reference.md`](docs/builtin-actions-reference.md): all 434 built-in
189
- actions with their parameters.
190
- - [`docs/apple-app-intents-reference.md`](docs/apple-app-intents-reference.md): the 1,441 App
191
- Intents actions of Apple's apps, by app, with parameter kinds and enumeration cases.
192
- - [`docs/parameter-encodings.md`](docs/parameter-encodings.md): how each parameter class is
193
- serialized.
194
- - [`docs/extraction.md`](docs/extraction.md): how the data was obtained, what did not work,
195
- and what remains approximate. A handful of condition codes rest on community documentation.
196
-
197
- ## Development
198
-
199
- ```bash
200
- git clone https://github.com/frontboat/shortcutkit.git && cd shortcutkit
201
- bun install
202
- bun test # all tests
203
- bun run typecheck # tsc --noEmit; also verifies the @ts-expect-error assertions in tests
204
- bun run build # bundles src/ into dist/index.js for Node and emits the .d.ts files
205
-
206
- cd python && python3 -m venv .venv && .venv/bin/pip install -e .
207
- .venv/bin/python -m shortcutkit demo /tmp/Demo.shortcut
208
- ```
209
-
210
- CI runs the type check, the tests, a Node import of the built package, a Python install, the
211
- engine verification below and an unsigned build of the demo shortcut on macOS. Signing is not
212
- exercised in CI because the runner has no iCloud login.
213
-
214
- ### Verifying against the engine
215
-
216
- ```bash
217
- bun run verify # ~3 min on a Mac that has opened Shortcuts.app once
218
- ```
219
-
220
- Both packages are driven over every action, every parameter key and every value form their
221
- types admit, and the results are diffed against each other and then loaded through the
222
- Shortcuts engine's own action objects, which report whether each value was read. Forms the
223
- library refuses are written raw too, and the engine must refuse them as well. Whole shortcuts
224
- built through every block helper are imported the way the app imports a file. The run fails on
225
- any case the engine does not read; see `docs/extraction.md` for what it reports instead of
226
- failing.
108
+ ## API
227
109
 
228
- ### Repository layout
229
-
230
- | Path | Contents |
110
+ | Export | What it does |
231
111
  |---|---|
232
- | `src/` | The TypeScript package source. `src/generated/actions.ts` is produced by the tools. `bun run build` bundles it into `dist/`, which is what npm ships. |
233
- | `python/` | The Python package. `actions.py` and `data/` are produced by the tools. |
234
- | `data/` | Everything extracted from the engine: definitions, parameter encodings, serialization table, `apple-app-intents.json` from the Shortcuts app's registry, and `provenance.json`. |
235
- | `docs/` | The format reference, action reference, encodings reference and extraction notes. |
236
- | `tools/` | The extraction pipeline. |
237
-
238
- Never hand-edit the generated files. Change the generator or the data and re-run the pipeline.
239
-
240
- ### Regenerating the data
112
+ | `new Shortcut(name, { color, glyph, types, inputClasses })` | A shortcut. |
113
+ | `s.action(id, params)` | Appends an action and returns it. |
114
+ | `s.if(subject, condition, value?)`, `s.otherwise(gid)`, `s.endIf(gid)` | An If block. |
115
+ | `s.repeatEach(items)`, `s.endRepeatEach(gid)` | A Repeat with Each block. |
116
+ | `s.repeatCount(n)`, `s.endRepeatCount(gid)` | A Repeat block. |
117
+ | `s.chooseFromMenu(prompt, items)`, `s.menuItem(gid, title)`, `s.endMenu(gid)` | A Choose from Menu block. |
118
+ | `s.write(path?)` | Writes the unsigned file and returns its path. |
119
+ | `Shortcut.sign(input, output, mode?)` | Signs a file with `shortcuts sign`. |
120
+ | `s.toPlist()`, `s.toXml()` | The shortcut as a plist object or XML. |
121
+ | `ref(action)` | An action's output. |
122
+ | `text(...parts)` | A string with references in it. |
123
+ | `variable(name)`, `repeatItem()`, `repeatIndex()` | Named and repeat variables. |
124
+ | `shortcutInput()`, `clipboard()`, `currentDate()`, `ask()`, `deviceDetails()`, `currentApp()` | Special variables. |
125
+ | `actions`, `getAction(id)`, `getDefinition(id)` | Action identifiers and their catalog entries. |
126
+ | `PARAM_KINDS`, `PARAM_CHOICES`, `PARAM_VARIABLE_TYPES` | What each parameter accepts. |
127
+ | `CONDITION`, `ICON_COLORS`, `provenance` | If conditions, icon colors, and the build the data came from. |
241
128
 
242
- The extracted data is committed on purpose. It can only be produced on a Mac, through private
243
- API that changes between releases, so committing it is what makes the package reproducible.
244
- To refresh after a macOS update, on any Mac that has opened Shortcuts.app at least once. No
245
- Xcode is needed: the probes are JavaScript for Automation scripts run by `osascript`, because
246
- ActionKit, which defines about a quarter of the built-in actions, only loads into Apple's own
247
- platform binaries. Apple's App Intents come from the Shortcuts app's action index, which
248
- Shortcuts writes on launch.
129
+ ## Documentation
249
130
 
250
- ```bash
251
- bun run extract # ~10 s: loads WorkflowKit and ActionKit in osascript, dumps and serializes everything
252
- bun install && bun test
253
- bun run diff-data # what changed vs the committed data, and the semver bump it implies
254
- bun run changelog # the same as a CHANGELOG.md section
255
- ```
131
+ - [Shortcut file format](docs/shortcut-file-format.md)
132
+ - [Built-in actions](docs/builtin-actions-reference.md)
133
+ - [Apple app actions](docs/apple-app-intents-reference.md)
134
+ - [Parameter encodings](docs/parameter-encodings.md)
135
+ - [How the data was extracted](docs/extraction.md), and what is still approximate
256
136
 
257
- Both packages and the data are versioned together. A refresh that only adds actions or
258
- parameters is a minor release. One that removes or retypes any is a major release, because the
259
- generated catalogue is part of the type surface. Bump `package.json` and
260
- `python/pyproject.toml` together and add the `bun run changelog` output to `CHANGELOG.md`.
137
+ ## Status
261
138
 
262
- The tools also dump Apple's gallery workflows, installed apps' App Intents actions and the raw
263
- identifier list. Those files are local and gitignored.
139
+ Extracted from macOS 27.2 and Shortcuts 10.0. A future macOS can break re-extraction, but not
140
+ the published packages, which ship the data.
264
141
 
265
- ## Status
142
+ ## Contributing
266
143
 
267
- Data extracted on macOS 27.0 with Shortcuts 10.0 (see `data/provenance.json`). Generated files
268
- declare minimum client version 900, which any current Shortcuts accepts. The private classes
269
- the tools call can change with any release; the package itself depends only on the committed
270
- data. Generated shortcuts import and run.
144
+ Issues and pull requests are welcome. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
271
145
 
272
146
  ## License
273
147
 
274
- shortcutkit is licensed under the MIT license. See [`LICENSE`](LICENSE) for details.
148
+ [MIT](LICENSE).