afterpresets 0.0.0-stage → 0.1.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.
Files changed (129) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +20 -2
  3. package/bin/ap.js +2 -0
  4. package/package.json +39 -4
  5. package/runtime/docs/README.md +63 -0
  6. package/runtime/docs/assets.md +74 -0
  7. package/runtime/docs/boot.md +103 -0
  8. package/runtime/docs/host.md +110 -0
  9. package/runtime/docs/licensing.md +261 -0
  10. package/runtime/docs/panels.md +114 -0
  11. package/runtime/docs/storage.md +82 -0
  12. package/runtime/docs/updates.md +251 -0
  13. package/runtime/package.json +5 -0
  14. package/runtime/runtime-files.json +14 -0
  15. package/runtime/src/boot.js +1 -0
  16. package/runtime/src/computer-id.js +1 -0
  17. package/runtime/src/core.js +1 -0
  18. package/runtime/src/events.js +1 -0
  19. package/runtime/src/host.js +1 -0
  20. package/runtime/src/license.js +1 -0
  21. package/runtime/src/panels.js +1 -0
  22. package/runtime/src/storage.js +1 -0
  23. package/runtime/src/updates.js +1 -0
  24. package/runtime/src/vendor/CSInterface.js +1 -0
  25. package/runtime/src/vendor/json2.jsx +506 -0
  26. package/runtime/src/zip.js +1 -0
  27. package/src/api/client.js +1 -0
  28. package/src/api/error-messages.js +1 -0
  29. package/src/api/session.js +1 -0
  30. package/src/args.js +1 -0
  31. package/src/auth/credentials.js +2 -0
  32. package/src/auth/logout.js +1 -0
  33. package/src/build/assets.js +1 -0
  34. package/src/build/build.js +9 -0
  35. package/src/build/bundle.js +5 -0
  36. package/src/build/html.js +4 -0
  37. package/src/build/jsx.js +3 -0
  38. package/src/build/jsxbin.js +1 -0
  39. package/src/build/runtime.js +4 -0
  40. package/src/check/check.js +3 -0
  41. package/src/check/extendscript-globals.d.ts +6 -0
  42. package/src/check/pricing.js +1 -0
  43. package/src/check/types.js +3 -0
  44. package/src/cli.js +5 -0
  45. package/src/commands/add.js +6 -0
  46. package/src/commands/build.js +6 -0
  47. package/src/commands/check.js +13 -0
  48. package/src/commands/config.js +7 -0
  49. package/src/commands/dev.js +6 -0
  50. package/src/commands/docs.js +1 -0
  51. package/src/commands/init.js +3 -0
  52. package/src/commands/login.js +3 -0
  53. package/src/commands/logout.js +1 -0
  54. package/src/commands/mcp.js +7 -0
  55. package/src/commands/push.js +7 -0
  56. package/src/commands/releases.js +5 -0
  57. package/src/commands/testers.js +1 -0
  58. package/src/commands/upgrade.js +2 -0
  59. package/src/commands/whoami.js +1 -0
  60. package/src/config.js +1 -0
  61. package/src/context.js +1 -0
  62. package/src/dev/cep-link.js +1 -0
  63. package/src/dev/debug-file.js +10 -0
  64. package/src/dev/debug-mode.js +1 -0
  65. package/src/dev/session.js +4 -0
  66. package/src/errors.js +1 -0
  67. package/src/mcp/capture.js +1 -0
  68. package/src/mcp/extendscript.js +7 -0
  69. package/src/mcp/panels.js +1 -0
  70. package/src/mcp/protocol.js +4 -0
  71. package/src/mcp/redact.js +1 -0
  72. package/src/mcp/server.js +2 -0
  73. package/src/mcp/snapshot.js +2 -0
  74. package/src/mcp/tools/account.js +3 -0
  75. package/src/mcp/tools/host.js +1 -0
  76. package/src/mcp/tools/panel.js +7 -0
  77. package/src/mcp/tools/project.js +40 -0
  78. package/src/mcp/tools/releases.js +29 -0
  79. package/src/output.js +6 -0
  80. package/src/project/add-panel.js +11 -0
  81. package/src/project/docs.js +1 -0
  82. package/src/project/manifest.js +5 -0
  83. package/src/project/panels.js +1 -0
  84. package/src/project/project.js +2 -0
  85. package/src/project/semver.js +1 -0
  86. package/src/project/template.js +1 -0
  87. package/src/prompt.js +1 -0
  88. package/src/release/installers.js +4 -0
  89. package/src/release/package.js +1 -0
  90. package/src/release/publish.js +1 -0
  91. package/src/system/run-command.js +1 -0
  92. package/src/system/update-check.js +2 -0
  93. package/src/user-config.js +2 -0
  94. package/template/.mcp.json +8 -0
  95. package/template/AGENTS.md +168 -0
  96. package/template/README.md +78 -0
  97. package/template/docs/initial_setup.md +69 -0
  98. package/template/gitignore +12 -0
  99. package/template/jsx/home.jsx +17 -0
  100. package/template/package.json +14 -0
  101. package/template/src/features/home/home.css +67 -0
  102. package/template/src/features/home/home.html +23 -0
  103. package/template/src/features/home/home.js +41 -0
  104. package/template/src/features/license-screen/license-screen.css +80 -0
  105. package/template/src/features/license-screen/license-screen.html +72 -0
  106. package/template/src/features/license-screen/license-screen.js +60 -0
  107. package/template/src/features/update-screen/update-screen.css +173 -0
  108. package/template/src/features/update-screen/update-screen.html +57 -0
  109. package/template/src/features/update-screen/update-screen.js +196 -0
  110. package/template/src/panels/main/index.html +21 -0
  111. package/template/src/panels/main/main.js +39 -0
  112. package/template/src/shared/lib/dom.js +29 -0
  113. package/template/src/shared/lib/host-theme.js +17 -0
  114. package/template/src/shared/lib/markdown.js +109 -0
  115. package/template/src/shared/lib/view.js +82 -0
  116. package/template/src/shared/ui/base.css +60 -0
  117. package/template/src/shared/ui/button.css +61 -0
  118. package/template/src/shared/ui/card.css +22 -0
  119. package/template/src/shared/ui/field.css +25 -0
  120. package/template/src/shared/ui/index.css +7 -0
  121. package/template/src/shared/ui/tokens.css +21 -0
  122. package/vendor/types-for-adobe/AfterEffects/26.0/index.d.ts +3403 -0
  123. package/vendor/types-for-adobe/LICENSE.md +21 -0
  124. package/vendor/types-for-adobe/README.md +9 -0
  125. package/vendor/types-for-adobe/shared/JavaScript.d.ts +2761 -0
  126. package/vendor/types-for-adobe/shared/PlugPlugExternalObject.d.ts +91 -0
  127. package/vendor/types-for-adobe/shared/ScriptUI.d.ts +3012 -0
  128. package/vendor/types-for-adobe/shared/XMPScript.d.ts +72 -0
  129. package/vendor/types-for-adobe/shared/global.d.ts +152 -0
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ AfterPresets CLI and Runtime License
2
+
3
+ Copyright (c) AfterPresets. All rights reserved.
4
+
5
+ This package ("the Software") contains the AfterPresets command-line tool and
6
+ the AfterPresets runtime.
7
+
8
+ You may install and use the Software only to create, test, build and publish
9
+ Adobe After Effects extensions that are distributed through AfterPresets
10
+ (afterpresets.com), and you may include the runtime in those extensions as
11
+ the Software's build process does.
12
+
13
+ You may not, except as the law expressly allows:
14
+ - modify, decompile, deobfuscate or reverse engineer the runtime, or remove,
15
+ disable or work around its licensing, signing or update checks;
16
+ - copy or redistribute the Software, or the runtime outside an extension
17
+ built with the Software;
18
+ - use the Software to build or distribute extensions outside AfterPresets.
19
+
20
+ Use of AfterPresets services is also governed by the AfterPresets Terms of
21
+ Service at https://afterpresets.com.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED. IN NO EVENT SHALL AFTERPRESETS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25
+ OTHER LIABILITY ARISING FROM THE SOFTWARE OR ITS USE.
package/README.md CHANGED
@@ -1,3 +1,21 @@
1
- # Temporary Holding Version
1
+ # AfterPresets CLI
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Make Adobe After Effects extensions with an AI agent, and sell them on
4
+ [AfterPresets](https://afterpresets.com) with licensing, signed auto-updates
5
+ and checkout handled.
6
+
7
+ You do not need to write code. Open an empty folder in your agent (the Code
8
+ tab of the Claude desktop app, Claude Code, Codex, Cursor...) and follow the
9
+ **[Quickstart](https://docs.afterpresets.com/get-started/quickstart/)**.
10
+
11
+ ```sh
12
+ npx afterpresets login
13
+ npx afterpresets init my-extension
14
+ ```
15
+
16
+ Inside a project, `ap dev` puts the extension into After Effects and reloads
17
+ it on every save, `ap push` publishes a release, and `ap upgrade` updates
18
+ this package. Every project includes `AGENTS.md` and an MCP server
19
+ (`ap mcp`) so your agent can see, click and test the live panel.
20
+
21
+ Docs: https://docs.afterpresets.com
package/bin/ap.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ "use strict";const{main:r}=require("../src/cli"),{createContext:s}=require("../src/context");r(process.argv.slice(2),s()).then(e=>{process.exitCode=e});
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "afterpresets",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Create, develop, and publish Adobe CEP extensions on AfterPresets.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "homepage": "https://docs.afterpresets.com",
7
+ "keywords": [
8
+ "after effects",
9
+ "adobe",
10
+ "cep",
11
+ "extension",
12
+ "afterpresets",
13
+ "mcp"
14
+ ],
15
+ "bin": {
16
+ "ap": "bin/ap.js",
17
+ "afterpresets": "bin/ap.js"
18
+ },
19
+ "files": [
20
+ "bin",
21
+ "src",
22
+ "runtime",
23
+ "template",
24
+ "vendor",
25
+ "LICENSE"
26
+ ],
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "dependencies": {
31
+ "adm-zip": "0.5.16",
32
+ "chokidar": "3.6.0",
33
+ "esbuild": "0.25.12",
34
+ "jsxbin": "2.3.0",
35
+ "playwright-core": "1.64.1",
36
+ "typescript": "5.9.3"
37
+ },
38
+ "afterpresets": {
39
+ "runtimeVersion": "0.3.0"
40
+ }
41
+ }
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: AfterPresets runtime reference
3
+ description: What the AfterPresets runtime does, and which reference file to read before you use any AP.* API.
4
+ ---
5
+
6
+ # AfterPresets runtime reference
7
+
8
+ These files are for the AI coding agent that builds an After Effects extension
9
+ with AfterPresets. They describe the `AP.*` API that every panel gets.
10
+
11
+ ## What the runtime does
12
+
13
+ The runtime is a small script that the AfterPresets CLI puts in front of each
14
+ panel's JavaScript. It is logic only. It draws nothing: your project owns every
15
+ screen, style, and word the user sees.
16
+
17
+ It gives you:
18
+
19
+ - `AP.boot` to start the panel.
20
+ - Licensing, off by default. Paid extensions must turn it on.
21
+ - Signed updates with changelogs.
22
+ - `AP.host.eval` to call After Effects (ExtendScript) functions.
23
+ - `AP.panels` and `AP.events` for extensions with more than one panel.
24
+ - `AP.paths` to find the images, videos and other files you ship.
25
+ - `AP.storage` to save settings and other small data on this computer.
26
+
27
+ Do not edit the runtime and do not copy it into your source files. The CLI
28
+ adds it for you. `ap docs` refreshes this folder from the installed runtime.
29
+
30
+ ## The rule
31
+
32
+ **Read the relevant file below before you write or change code that uses
33
+ `AP.*`.** Do not guess option names, statuses, or callback shapes. If a file
34
+ and your memory disagree, the file wins.
35
+
36
+ ## Files
37
+
38
+ | File | Read it before you |
39
+ |---|---|
40
+ | [boot.md](boot.md) | call `AP.boot`, use `ctx`, or change when the app starts |
41
+ | [licensing.md](licensing.md) | turn licensing on, write `renderLicense`, or change the license screen |
42
+ | [updates.md](updates.md) | change update options or the update screen, or show a changelog |
43
+ | [host.md](host.md) | call After Effects from the panel or write `jsx/` code |
44
+ | [panels.md](panels.md) | add a panel or modal, open/close panels, or send data between panels |
45
+ | [assets.md](assets.md) | add images, fonts, videos or other files, or import a file into After Effects |
46
+ | [storage.md](storage.md) | save settings, recent items or any other data between launches |
47
+
48
+ ## Commands you will use
49
+
50
+ ```sh
51
+ ap dev # build, link into After Effects, rebuild on change
52
+ ap check # static checks; also run by ap dev and ap push
53
+ ap add panel settings --type modal
54
+ ap push # publish a release (asks for version bump and changelog)
55
+ ap docs # refresh docs/afterpresets/ from the runtime
56
+ ```
57
+
58
+ ## Compatibility
59
+
60
+ Panels run in Adobe CEP 10 or later (After Effects 2020+). Its browser is old
61
+ (Chromium 74 in CEP 10). Write plain ES5 in panel code: `var`, `function`, no
62
+ arrow functions, no `let`/`const`, no `class`, no template strings, no
63
+ `fetch`-only features. ExtendScript in `jsx/` is ES3 (see [host.md](host.md)).
@@ -0,0 +1,74 @@
1
+ ---
2
+ title: Media and other files
3
+ description: Where to put images, fonts, videos, presets and other files your extension ships, and how to use them from HTML, CSS, JavaScript and ExtendScript.
4
+ ---
5
+
6
+ # Media and other files
7
+
8
+ Put files your extension ships in these folders at the **project root**
9
+ (next to `afterpresets.json`):
10
+
11
+ ```
12
+ assets/ fonts/ icons/ images/ media/
13
+ ```
14
+
15
+ They are copied into the build unchanged, at the same place: `media/intro.mp4`
16
+ in the project is `media/intro.mp4` inside the installed extension. Other
17
+ folders are not shipped. To choose your own list, set it in
18
+ `afterpresets.json` (this replaces the defaults):
19
+
20
+ ```json
21
+ "assets": ["media", "presets", "fonts"]
22
+ ```
23
+
24
+ These files are **not** obfuscated or hidden. Anything the buyer installs, they
25
+ can open. Keep secrets out of them. The whole release must stay under 200 MB.
26
+
27
+ ## From HTML and CSS
28
+
29
+ Write paths relative to the file you are in, as usual. They must point into
30
+ an asset folder; the build rewrites them for the installed extension and fails
31
+ on a missing file.
32
+
33
+ ```html
34
+ <!-- src/panels/main/index.html -->
35
+ <img src="../../../images/logo.png" alt="">
36
+ ```
37
+
38
+ ```css
39
+ /* src/features/home/home.css */
40
+ .hero { background-image: url(../../../images/hero.jpg); }
41
+ @font-face { font-family: "Brand"; src: url(../../../fonts/Brand.woff2); }
42
+ ```
43
+
44
+ ## From JavaScript
45
+
46
+ Use `AP.paths`; never build paths by hand.
47
+
48
+ | Call | Returns |
49
+ |---|---|
50
+ | `AP.paths.asset("media/intro.mp4")` | Absolute path of a shipped file. Relative path with `/`; `..` and absolute paths throw. |
51
+ | `AP.paths.extension()` | Absolute path of the installed extension folder. |
52
+ | `AP.paths.data()` | A writable folder for your own files. It survives updates. Never write into the extension folder: updates replace it. For settings use `AP.storage`; see [storage.md](storage.md). |
53
+
54
+ ## From ExtendScript (importing into After Effects)
55
+
56
+ ExtendScript cannot see the panel's folders by itself. Pass the absolute path
57
+ from the panel:
58
+
59
+ ```js
60
+ // panel
61
+ ctx.host.eval("importClip", [AP.paths.asset("media/intro.mp4")], function (result) {
62
+ if (result && result.error) { /* show result.error */ }
63
+ });
64
+ ```
65
+
66
+ ```js
67
+ // jsx/media.jsx (loaded automatically, like every .jsx file in jsx/)
68
+ function importClip(path) {
69
+ var file = new File(path);
70
+ if (!file.exists) return JSON.stringify({ error: "Missing file: " + path });
71
+ var item = app.project.importFile(new ImportOptions(file));
72
+ return JSON.stringify({ ok: true, name: item.name });
73
+ }
74
+ ```
@@ -0,0 +1,103 @@
1
+ ---
2
+ title: AP.boot
3
+ description: Start a panel with AP.boot. Options, the ctx object passed to start, and the boot lifecycle.
4
+ ---
5
+
6
+ # AP.boot
7
+
8
+ Every panel calls `AP.boot` exactly once, from its `src/panels/<id>/main.js`
9
+ code. `ap check` fails when a panel never calls it.
10
+
11
+ ```js
12
+ AP.boot({
13
+ start: function (ctx) {
14
+ // Build your UI here.
15
+ },
16
+ license: false, // default. true, or { offlineGraceDays, checkInBackground }
17
+ renderLicense: function (api, root) {}, // required when license is on
18
+ updates: { check: "auto", mode: "background" } // defaults
19
+ });
20
+ ```
21
+
22
+ ## Options
23
+
24
+ | Option | Type | Default | Meaning |
25
+ |---|---|---|---|
26
+ | `start` | `function (ctx)` | required | Your app. Called once, when the panel may run. |
27
+ | `license` | `false`, `true`, or `{ offlineGraceDays, checkInBackground }` | `false` | Turns licensing on. See [licensing.md](licensing.md). |
28
+ | `renderLicense` | `function (api, root)` | none | Draws the license screen. Required when `license` is on. |
29
+ | `updates.check` | `"auto"` or `"manual"` | `"auto"` | `"auto"` checks at start and every 6 hours. See [updates.md](updates.md). |
30
+ | `updates.mode` | `"background"`, `"prompt"` or `"silent"` | `"background"` | What happens when an update is found. `"background"` installs it without asking, then your update screen offers **Reload now** (`AP.updates.reload()`). `"prompt"` waits for your Install button. `"silent"` installs and shows nothing. See [updates.md](updates.md). |
31
+
32
+ Unknown values are replaced by the default with a console warning. If
33
+ `license` is on and `renderLicense` is missing, `AP.boot` logs an error and
34
+ throws; the panel stays empty.
35
+
36
+ ## ctx
37
+
38
+ `start(ctx)` receives:
39
+
40
+ ```js
41
+ ctx = {
42
+ product: { bundleId: "com.afterpresets.seller.tool", version: "1.2.0" },
43
+ panel: { id: "main" }, // this panel's id from afterpresets.json
44
+ license: AP.license, // license api; status is "disabled" when licensing is off
45
+ updates: AP.updates, // see updates.md
46
+ host: AP.host // see host.md
47
+ };
48
+ ```
49
+
50
+ The same objects are also available as globals (`AP.license`, `AP.updates`,
51
+ `AP.host`, `AP.panels`, `AP.events`) for code that runs outside `start`.
52
+
53
+ ## Lifecycle
54
+
55
+ 1. The page loads. The runtime is already defined; nothing has run yet.
56
+ 2. Your entry code calls `AP.boot(options)`.
57
+ 3. When the DOM is ready:
58
+ - **License off:** `AP.license.state.status` is `"disabled"` and `start(ctx)`
59
+ runs immediately.
60
+ - **License on:** the runtime adds an empty `<div id="ap-license-root">` to
61
+ `<body>`, calls `renderLicense(api, root)`, and validates the license.
62
+ `start(ctx)` runs once the status becomes `active` or `offline_grace`.
63
+ It never runs for any other status.
64
+ 4. After `start`, if `updates.check` is `"auto"`, the runtime checks for updates
65
+ now and every 6 hours. In the default `"background"` mode a found update is
66
+ installed right away and `AP.updates.onInstalled` fires; the panel keeps
67
+ running the old version until `AP.updates.reload()` or the next launch.
68
+
69
+ `start` runs at most once per panel load. If the license later becomes invalid
70
+ (for example the buyer signs out), the license root is shown again over your
71
+ app; `start` is not called a second time.
72
+
73
+ ## Example: a free extension
74
+
75
+ ```js
76
+ AP.boot({
77
+ start: function (ctx) {
78
+ var app = document.getElementById("app");
79
+ app.textContent = "Hello from " + ctx.product.bundleId + " (" + ctx.panel.id + ")";
80
+ }
81
+ });
82
+ ```
83
+
84
+ ## Example: a paid extension
85
+
86
+ ```js
87
+ AP.boot({
88
+ start: function (ctx) {
89
+ renderHome(document.getElementById("app"), ctx); // imported from src/features/home/home.js
90
+ },
91
+ license: true,
92
+ renderLicense: renderLicense, // imported; see licensing.md
93
+ updates: { check: "auto", mode: "background" }
94
+ });
95
+ ```
96
+
97
+ ## Mistakes to avoid
98
+
99
+ - Do not build app UI before `start` runs. Put it inside `start`.
100
+ - Do not call `start` yourself, and do not call `AP.boot` twice (the second call
101
+ is ignored with a warning).
102
+ - Do not wrap `AP.boot` in `try/catch` to hide the missing-`renderLicense`
103
+ error. Fix the options instead.
@@ -0,0 +1,110 @@
1
+ ---
2
+ title: Calling After Effects
3
+ description: Call ExtendScript functions from a panel with AP.host.eval, write jsx/ functions in ES3 that return JSON, and handle host errors.
4
+ ---
5
+
6
+ # Calling After Effects
7
+
8
+ Panel code runs in a browser. Anything that touches After Effects (project,
9
+ comps, layers, effects, files through `File`/`Folder`) runs in ExtendScript, in
10
+ `jsx/`. `AP.host.eval` connects the two.
11
+
12
+ ## Where JSX lives
13
+
14
+ Put ExtendScript in `jsx/`, one file per feature (for example
15
+ `jsx/layers.jsx`; subfolders are fine). Every `.jsx` file there is loaded into
16
+ After Effects once, before your app starts, and its functions are shared by
17
+ all panels. Call one from panel code with `ctx.host.eval("fnName", [args], cb)`.
18
+
19
+ - There is no list to keep: the CLI joins every `.jsx` file in alphabetical
20
+ path order, with JSON support (json2) first, embeds the result in every
21
+ panel's `main.js` (readable in `ap dev`, compiled to JSXBIN for releases),
22
+ and the runtime evaluates it as soon as `AP.boot` runs, before `start`.
23
+ Every `AP.host.eval` waits for that load.
24
+ - Order only matters for code that runs at the top level of a file while it
25
+ loads (not inside a function). Keep files to function declarations.
26
+
27
+ ## Writing a JSX function
28
+
29
+ ExtendScript is **ES3**: `var` only, no `let`/`const`, no arrow functions, no
30
+ `Array.prototype.forEach`/`map`/`indexOf`, no `trim()`, no template strings.
31
+ `JSON` is always available (the CLI adds json2).
32
+
33
+ Rules:
34
+
35
+ 1. Declare a named top-level function. Avoid generic names; prefix them
36
+ (`myTool_addNull`), because every extension shares one ExtendScript engine.
37
+ 2. Take JSON-compatible arguments (strings, numbers, booleans, arrays, plain
38
+ objects).
39
+ 3. Return `JSON.stringify(...)`. Return a result object such as `{ ok: true, ... }`
40
+ or `{ ok: false, message: "..." }` for expected problems.
41
+ 4. Wrap changes to the project in `app.beginUndoGroup("Name")` /
42
+ `app.endUndoGroup()`.
43
+
44
+ ```js
45
+ /* jsx/layers.jsx */
46
+ function myTool_addNull(name) {
47
+ var comp = app.project.activeItem;
48
+ if (!comp || !(comp instanceof CompItem)) {
49
+ return JSON.stringify({ ok: false, message: "Select a composition first." });
50
+ }
51
+ app.beginUndoGroup("Add null");
52
+ try {
53
+ var layer = comp.layers.addNull();
54
+ layer.name = name;
55
+ return JSON.stringify({ ok: true, index: layer.index });
56
+ } finally {
57
+ app.endUndoGroup();
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## Calling it from the panel
63
+
64
+ ```js
65
+ AP.host.eval("myTool_addNull", ["Controller"], function (result) {
66
+ if (!result || result.error) {
67
+ showError(result ? result.error : "No answer from After Effects.");
68
+ return;
69
+ }
70
+ if (!result.ok) {
71
+ showMessage(result.message);
72
+ return;
73
+ }
74
+ showMessage("Added layer " + result.index);
75
+ });
76
+ ```
77
+
78
+ `AP.host.eval(fnName, args, cb)`:
79
+
80
+ - `fnName` is a function name or dotted path (`"myTool.addNull"`). Anything else
81
+ is refused with `{ error }`.
82
+ - `args` is an array. Each value is passed as a JSON literal, so quotes,
83
+ newlines, and code in strings are safe. `undefined` becomes `null`.
84
+ - `cb(result)` receives the parsed JSON your function returned, `null` when it
85
+ returned nothing, or `{ error: "message" }` when anything failed.
86
+
87
+ Never build ExtendScript source by joining strings with user input. Always pass
88
+ values through `args`.
89
+
90
+ ## Errors
91
+
92
+ | `result` | Cause |
93
+ |---|---|
94
+ | `{ error: "<message>" }` | Your function threw; the message is the thrown error's message. |
95
+ | `{ error: "...is undefined" }` or similar | The function does not exist: check the name, and that its file is in `jsx/` and ends in `.jsx`. |
96
+ | `{ error: "The host returned a non-JSON result.", raw }` | Your function returned a plain string. Return `JSON.stringify(...)`. |
97
+ | `{ error: "The host script failed to run." }` | ExtendScript syntax error or an ES5+ feature in `jsx/`. |
98
+ | `{ error: "Could not load host script: ..." }` | The embedded JSX failed to load. Read the message; usually a syntax error. |
99
+
100
+ The JSX loads once per page. During `ap dev` every save rebuilds and reloads
101
+ the open panels, which loads the new JSX too; after an update,
102
+ `AP.updates.reload()` does the same. The new functions replace the old ones.
103
+ Functions removed from the new code stay defined in After Effects until it
104
+ restarts, so never rely on a function being absent.
105
+
106
+ ## Testing
107
+
108
+ Call the function from the live panel and check the result and the project
109
+ state. Use a disposable project or a duplicate composition for anything that
110
+ changes layers, effects, files, or project data.